diff options
| author | Chase Colman <[email protected]> | 2021-10-10 02:47:01 +0800 | 
|---|---|---|
| committer | GitHub <[email protected]> | 2021-10-09 20:47:01 +0200 | 
| commit | 82b7a35858479223c1e34bea2f64451ecf1e5f66 (patch) | |
| tree | c0aaaed1e0eefe10320044236bdabaa545935f0b | |
| parent | 484c618d09150980746afd37b34b96f1ce15b81d (diff) | |
fix(cmp/autopairs): prevent out of bounds jump and re-enable jump after confirm (#1708)
| -rw-r--r-- | lua/core/autopairs.lua | 12 | ||||
| -rw-r--r-- | lua/core/cmp.lua | 128 | 
2 files changed, 116 insertions, 24 deletions
| diff --git a/lua/core/autopairs.lua b/lua/core/autopairs.lua index a67f3b07..eb080fb1 100644 --- a/lua/core/autopairs.lua +++ b/lua/core/autopairs.lua @@ -4,14 +4,8 @@ function M.config()    lvim.builtin.autopairs = {      active = true,      on_config_done = nil, -    ---@usage  map <CR> on insert mode -    map_cr = true,      ---@usage auto insert after select function or method item      map_complete = true, -    ---@usage automatically select the first item -    auto_select = true, -    ---@usage use insert confirm behavior instead of replace -    insert = false,      ---@usage  -- modifies the function or method delimiter by filetypes      map_char = {        all = "(", @@ -60,12 +54,12 @@ M.setup = function()    if package.loaded["cmp"] then      require("nvim-autopairs.completion.cmp").setup { -      map_cr = lvim.builtin.autopairs.map_cr, +      map_cr = false,        map_complete = lvim.builtin.autopairs.map_complete, -      auto_select = lvim.builtin.autopairs.auto_select, -      insert = lvim.builtin.autopairs.insert,        map_char = lvim.builtin.autopairs.map_char,      } +    -- we map CR explicitly in cmp.lua but we still need to setup the autopairs CR keymap +    vim.api.nvim_set_keymap("i", "<CR>", "v:lua.MPairs.autopairs_cr()", { expr = true, noremap = true })    end    require("nvim-treesitter.configs").setup { autopairs = { enable = true } } diff --git a/lua/core/cmp.lua b/lua/core/cmp.lua index b058bd6a..d5d92314 100644 --- a/lua/core/cmp.lua +++ b/lua/core/cmp.lua @@ -29,14 +29,106 @@ M.config = function()    if not status_luasnip_ok then      return    end +  local win_get_cursor = vim.api.nvim_win_get_cursor +  local get_current_buf = vim.api.nvim_get_current_buf + +  local function inside_snippet() +    -- for outdated versions of luasnip +    if not luasnip.session.current_nodes then +      return false +    end + +    local node = luasnip.session.current_nodes[get_current_buf()] +    if not node then +      return false +    end + +    local snip_begin_pos, snip_end_pos = node.parent.snippet.mark:pos_begin_end() +    local pos = win_get_cursor(0) +    pos[1] = pos[1] - 1 -- LuaSnip is 0-based not 1-based like nvim for rows +    return pos[1] >= snip_begin_pos[1] and pos[1] <= snip_end_pos[1] +  end + +  ---sets the current buffer's luasnip to the one nearest the cursor +  ---@return boolean true if a node is found, false otherwise +  local function seek_luasnip_cursor_node() +    -- for outdated versions of luasnip +    if not luasnip.session.current_nodes then +      return false +    end + +    local pos = win_get_cursor(0) +    pos[1] = pos[1] - 1 +    local node = luasnip.session.current_nodes[get_current_buf()] +    if not node then +      return false +    end + +    local snippet = node.parent.snippet +    local exit_node = snippet.insert_nodes[0] + +    -- exit early if we're past the exit node +    if exit_node then +      local exit_pos_end = exit_node.mark:pos_end() +      if (pos[1] > exit_pos_end[1]) or (pos[1] == exit_pos_end[1] and pos[2] > exit_pos_end[2]) then +        snippet:remove_from_jumplist() +        luasnip.session.current_nodes[get_current_buf()] = nil + +        return false +      end +    end + +    node = snippet.inner_first:jump_into(1, true) +    while node ~= nil and node.next ~= nil and node ~= snippet do +      local n_next = node.next +      local next_pos = n_next and n_next.mark:pos_begin() +      local candidate = n_next ~= snippet and next_pos and (pos[1] < next_pos[1]) +        or (pos[1] == next_pos[1] and pos[2] < next_pos[2]) + +      -- Past unmarked exit node, exit early +      if n_next == nil or n_next == snippet.next then +        snippet:remove_from_jumplist() +        luasnip.session.current_nodes[get_current_buf()] = nil + +        return false +      end + +      if candidate then +        luasnip.session.current_nodes[get_current_buf()] = node +        return true +      end + +      local ok +      ok, node = pcall(node.jump_from, node, 1, true) -- no_move until last stop +      if not ok then +        snippet:remove_from_jumplist() +        luasnip.session.current_nodes[get_current_buf()] = nil + +        return false +      end +    end + +    -- No candidate, but have an exit node +    if exit_node then +      -- to jump to the exit node, seek to snippet +      luasnip.session.current_nodes[get_current_buf()] = snippet +      return true +    end + +    -- No exit node, exit from snippet +    snippet:remove_from_jumplist() +    luasnip.session.current_nodes[get_current_buf()] = nil +    return false +  end +    lvim.builtin.cmp = {      confirm_opts = {        behavior = cmp.ConfirmBehavior.Replace, -      select = true, +      select = false,      },      experimental = { -      ghost_text = false, -      native_menu = true, +      ghost_text = true, +      native_menu = false,      },      formatting = {        kind_icons = { @@ -111,10 +203,12 @@ M.config = function()        ["<C-f>"] = cmp.mapping.scroll_docs(4),        -- TODO: potentially fix emmet nonsense        ["<Tab>"] = cmp.mapping(function() -        if vim.fn.pumvisible() == 1 then -          vim.fn.feedkeys(T "<down>", "n") -        elseif luasnip.expand_or_jumpable() then -          vim.fn.feedkeys(T "<Plug>luasnip-expand-or-jump", "") +        if cmp.visible() then +          cmp.select_next_item() +        elseif luasnip.expandable() then +          luasnip.expand() +        elseif inside_snippet() and seek_luasnip_cursor_node() and luasnip.jumpable() then +          luasnip.jump(1)          elseif check_backspace() then            vim.fn.feedkeys(T "<Tab>", "n")          elseif is_emmet_active() then @@ -127,10 +221,10 @@ M.config = function()          "s",        }),        ["<S-Tab>"] = cmp.mapping(function(fallback) -        if vim.fn.pumvisible() == 1 then -          vim.fn.feedkeys(T "<up>", "n") -        elseif luasnip.jumpable(-1) then -          vim.fn.feedkeys(T "<Plug>luasnip-jump-prev", "") +        if cmp.visible() then +          cmp.select_prev_item() +        elseif inside_snippet() and luasnip.jumpable(-1) then +          luasnip.jump(-1)          else            fallback()          end @@ -142,12 +236,16 @@ M.config = function()        ["<C-Space>"] = cmp.mapping.complete(),        ["<C-e>"] = cmp.mapping.close(),        ["<CR>"] = cmp.mapping(function(fallback) -        if not require("cmp").confirm(lvim.builtin.cmp.confirm_opts) then -          if luasnip.jumpable() then -            vim.fn.feedkeys(T "<Plug>luasnip-jump-next", "") -          else +        if cmp.visible() and cmp.confirm(lvim.builtin.cmp.confirm_opts) then +          return +        end + +        if inside_snippet() and seek_luasnip_cursor_node() and luasnip.jumpable() then +          if not luasnip.jump(1) then              fallback()            end +        else +          fallback()          end        end),      }, | 
