소스 검색

fix(cmp/autopairs): prevent out of bounds jump and re-enable jump after confirm (#1708)

Chase Colman 3 년 전
부모
커밋
82b7a35858
2개의 변경된 파일116개의 추가작업 그리고 24개의 파일을 삭제
  1. 3 9
      lua/core/autopairs.lua
  2. 113 15
      lua/core/cmp.lua

+ 3 - 9
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 } }

+ 113 - 15
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),
     },