From e477d4c374890198ebc7200f5d462c040e864885 Mon Sep 17 00:00:00 2001 From: Liam Dyer Date: Thu, 24 Jul 2025 12:16:46 -0400 Subject: [PATCH] fix: accept after moving cursor while resolving --- lua/blink/cmp/completion/accept/init.lua | 9 ++++++++- lua/blink/cmp/lib/text_edits.lua | 23 ++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lua/blink/cmp/completion/accept/init.lua b/lua/blink/cmp/completion/accept/init.lua index 1562734a4..e020bba10 100644 --- a/lua/blink/cmp/completion/accept/init.lua +++ b/lua/blink/cmp/completion/accept/init.lua @@ -58,6 +58,12 @@ local function apply_item(ctx, item) temp_text_edit.newText = '' text_edits_lib.apply(temp_text_edit, all_text_edits) + -- Because we resolve the item asynchronously, we must ensure we're still in insert mode + -- and set the cursor back to its position before accepting the item, since the snippet expansion + -- inserts at the cursor position + if not vim.api.nvim_get_mode().mode:match('i') then vim.cmd('startinsert') end + vim.api.nvim_win_set_cursor(0, { ctx.cursor[1], ctx.cursor[2] }) + -- Expand the snippet require('blink.cmp.config').snippets.expand(item.textEdit.newText) @@ -105,8 +111,9 @@ local function accept(ctx, item, callback) :catch(function() return item end) :map(function(resolved_item) -- Updates the text edit based on the cursor position and converts it to utf-8 + -- Use the cursor at the time of resolving the item, since it may have moved since then resolved_item = vim.deepcopy(resolved_item) - resolved_item.textEdit = text_edits_lib.get_from_item(resolved_item) + resolved_item.textEdit = text_edits_lib.get_from_item(resolved_item, ctx.cursor) return sources.execute( ctx, diff --git a/lua/blink/cmp/lib/text_edits.lua b/lua/blink/cmp/lib/text_edits.lua index bfa5ce00e..2abb93dfd 100644 --- a/lua/blink/cmp/lib/text_edits.lua +++ b/lua/blink/cmp/lib/text_edits.lua @@ -130,12 +130,14 @@ end --- Gets the text edit from an item, handling insert/replace ranges and converts --- offset encodings (utf-16 | utf-32) to utf-8 --- @param item blink.cmp.CompletionItem +--- @param cursor? [number, number] --- @return lsp.TextEdit -function text_edits.get_from_item(item) +function text_edits.get_from_item(item, cursor) + cursor = cursor or context.get_cursor() local text_edit = vim.deepcopy(item.textEdit) -- Guess the text edit if the item doesn't define it - if text_edit == nil then return text_edits.guess(item) end + if text_edit == nil then return text_edits.guess(item, cursor) end -- FIXME: temporarily convert insertReplaceEdit to regular textEdit if text_edit.range == nil then @@ -150,7 +152,7 @@ function text_edits.get_from_item(item) --- @cast text_edit lsp.TextEdit local offset_encoding = text_edits.offset_encoding_from_item(item) - text_edit = text_edits.compensate_for_cursor_movement(text_edit, item.cursor_column, context.get_cursor()[2]) + text_edit = text_edits.compensate_for_cursor_movement(text_edit, item.cursor_column, cursor[2]) -- convert the offset encoding to utf-8 -- TODO: we have to do this last because it applies a max on the position based on the length of the line @@ -194,17 +196,14 @@ end --- Uses the keyword_regex to guess the text edit ranges --- @param item blink.cmp.CompletionItem +--- @param cursor [number, number] --- TODO: doesnt work when the item contains characters not included in the context regex -function text_edits.guess(item) +function text_edits.guess(item, cursor) local word = item.insertText or item.label - local start_col, end_col = require('blink.cmp.fuzzy').guess_edit_range( - item, - context.get_line(), - context.get_cursor()[2], - config.completion.keyword.range - ) - local current_line = context.get_cursor()[1] + local start_col, end_col = + require('blink.cmp.fuzzy').guess_edit_range(item, context.get_line(), cursor[2], config.completion.keyword.range) + local current_line = cursor[1] -- convert to 0-index return { @@ -344,6 +343,8 @@ end --- https://github.com/neovim/neovim/issues/19806#issuecomment-2365146298 --- @param text_edit lsp.TextEdit function text_edits.write_to_dot_repeat(text_edit) + if not vim.api.nvim_get_mode().mode:match('i') then return end + local chars_to_delete = #table.concat( vim.api.nvim_buf_get_text( 0,