From 0041a30088283ff8a6db75cc72fa80f601aaac40 Mon Sep 17 00:00:00 2001 From: Mika Vilpas Date: Sun, 5 Jan 2025 18:39:30 +0200 Subject: [PATCH] refactor: make completion.accept async --- lua/blink/cmp/completion/accept/init.lua | 23 ++--- lua/blink/cmp/completion/accept/preview.lua | 20 ++--- lua/blink/cmp/completion/list.lua | 13 +-- lua/blink/cmp/lib/feedkeys/feedkeys.lua | 50 ++++------- lua/blink/cmp/lib/text_edits.lua | 95 ++++++++++++--------- 5 files changed, 98 insertions(+), 103 deletions(-) diff --git a/lua/blink/cmp/completion/accept/init.lua b/lua/blink/cmp/completion/accept/init.lua index 8309f32e..17a96889 100644 --- a/lua/blink/cmp/completion/accept/init.lua +++ b/lua/blink/cmp/completion/accept/init.lua @@ -71,19 +71,22 @@ local function accept(ctx, item, callback) local temp_text_edit = vim.deepcopy(item.textEdit) temp_text_edit.newText = '' table.insert(all_text_edits, temp_text_edit) - text_edits_lib.apply(all_text_edits) - - -- Expand the snippet - require('blink.cmp.config').snippets.expand(item.textEdit.newText) - - -- OR Normal: Apply the text edit and move the cursor + return text_edits_lib.apply(all_text_edits):map(function() + -- Expand the snippet + require('blink.cmp.config').snippets.expand(item.textEdit.newText) + return brackets_status + end) else + -- OR Normal: Apply the text edit and move the cursor table.insert(all_text_edits, item.textEdit) - text_edits_lib.apply(all_text_edits) - -- TODO: should move the cursor only by the offset since text edit handles everything else? - ctx.set_cursor({ ctx.get_cursor()[1], item.textEdit.range.start.character + #item.textEdit.newText + offset }) + return text_edits_lib.apply(all_text_edits):map(function() + -- TODO: should move the cursor only by the offset since text edit handles everything else? + ctx.set_cursor({ ctx.get_cursor()[1], item.textEdit.range.start.character + #item.textEdit.newText + offset }) + return brackets_status + end) end - + end) + :map(function(brackets_status) -- Let the source execute the item itself sources.execute(ctx, item):map(function() -- Check semantic tokens for brackets, if needed, and apply additional text edits diff --git a/lua/blink/cmp/completion/accept/preview.lua b/lua/blink/cmp/completion/accept/preview.lua index 88b46e2a..5e86900f 100644 --- a/lua/blink/cmp/completion/accept/preview.lua +++ b/lua/blink/cmp/completion/accept/preview.lua @@ -18,18 +18,18 @@ local function preview(item) text_edit.range.start.character + #text_edit.newText, } - text_edits_lib.apply({ text_edit }) + text_edits_lib.apply({ text_edit }):map(function() + local original_cursor = vim.api.nvim_win_get_cursor(0) + local cursor_moved = false - local original_cursor = vim.api.nvim_win_get_cursor(0) - local cursor_moved = false + -- TODO: remove when text_edits_lib.apply begins setting cursor position + if vim.api.nvim_get_mode().mode ~= 'c' then + vim.api.nvim_win_set_cursor(0, cursor_pos) + cursor_moved = true + end - -- TODO: remove when text_edits_lib.apply begins setting cursor position - if vim.api.nvim_get_mode().mode ~= 'c' then - vim.api.nvim_win_set_cursor(0, cursor_pos) - cursor_moved = true - end - - return undo_text_edit, cursor_moved and original_cursor or nil + return undo_text_edit, cursor_moved and original_cursor or nil + end) end return preview diff --git a/lua/blink/cmp/completion/list.lua b/lua/blink/cmp/completion/list.lua index 61fa2c32..eeae0fed 100644 --- a/lua/blink/cmp/completion/list.lua +++ b/lua/blink/cmp/completion/list.lua @@ -216,11 +216,12 @@ end function list.undo_preview() if list.preview_undo == nil then return end - require('blink.cmp.lib.text_edits').apply({ list.preview_undo.text_edit }) - if list.preview_undo.cursor then - require('blink.cmp.completion.trigger.context').set_cursor(list.preview_undo.cursor) - end - list.preview_undo = nil + require('blink.cmp.lib.text_edits').apply({ list.preview_undo.text_edit }):map(function() + if list.preview_undo.cursor then + require('blink.cmp.completion.trigger.context').set_cursor(list.preview_undo.cursor) + end + list.preview_undo = nil + end) end function list.apply_preview(item) @@ -240,7 +241,7 @@ function list.accept(opts) list.undo_preview() local accept = require('blink.cmp.completion.accept') - accept(list.context, item, function() + local _ = accept(list.context, item, function() list.accept_emitter:emit({ item = item, context = list.context }) if opts.callback then opts.callback() end end) diff --git a/lua/blink/cmp/lib/feedkeys/feedkeys.lua b/lua/blink/cmp/lib/feedkeys/feedkeys.lua index fcc3c3c8..048f56fe 100644 --- a/lua/blink/cmp/lib/feedkeys/feedkeys.lua +++ b/lua/blink/cmp/lib/feedkeys/feedkeys.lua @@ -1,9 +1,18 @@ +--- Provides a way to enter keys in a sequence. We want to do this because it +--- also appends these keys to the `.` register, which is used with +--- dot-repeating. +--- The implementation is based on the one in nvim-cmp, except that it's using +--- blink's async utilities. +--- +--- vim.api.nvim_feedkeys is blocking, but to truly wait for the keys to +--- actually have been inserted, 'x' (immediate mode) should be used. + local feedkeys = {} -feedkeys.call = setmetatable({ - callbacks = {}, -}, { - __call = function(self, keys, mode, callback) +---@nodiscard +feedkeys.call_async = function(keys, mode) + local task = require('blink.cmp.lib.async').task + return task.new(function(resolve) local is_insert = string.match(mode, 'i') ~= nil local is_immediate = string.match(mode, 'x') ~= nil @@ -18,17 +27,6 @@ feedkeys.call = setmetatable({ table.insert(queue, { feedkeys.t('setlocal backspace=%s'):format(vim.go.backspace or 2), 'n' }) end - if callback then - -- since we run inserting keys in a queue, we need to run the callback in - -- a queue as well so it runs after the keys have been inserted - local id = feedkeys.id('blink.feedkeys.call') - self.callbacks[id] = callback - table.insert( - queue, - { feedkeys.t('lua require"blink.cmp.lib.feedkeys.feedkeys".run(%s)'):format(id), 'n', true } - ) - end - if is_insert then for i = #queue, 1, -1 do vim.api.nvim_feedkeys(queue[i][1], queue[i][2] .. 'i', queue[i][3]) @@ -40,29 +38,11 @@ feedkeys.call = setmetatable({ end if is_immediate then vim.api.nvim_feedkeys('', 'x', true) end - end, -}) -feedkeys.run = function(id) - if feedkeys.call.callbacks[id] then - local ok, err = pcall(feedkeys.call.callbacks[id]) - if not ok then vim.notify(err, vim.log.levels.ERROR) end - feedkeys.call.callbacks[id] = nil - end - return '' + return resolve() + end) end ----Generate id for group name -feedkeys.id = setmetatable({ - group = {}, -}, { - __call = function(_, group) - feedkeys.id.group[group] = feedkeys.id.group[group] or 0 - feedkeys.id.group[group] = feedkeys.id.group[group] + 1 - return feedkeys.id.group[group] - end, -}) - ---Shortcut for nvim_replace_termcodes ---@param keys string ---@return string diff --git a/lua/blink/cmp/lib/text_edits.lua b/lua/blink/cmp/lib/text_edits.lua index 2f0d7d5e..34b7a594 100644 --- a/lua/blink/cmp/lib/text_edits.lua +++ b/lua/blink/cmp/lib/text_edits.lua @@ -1,55 +1,66 @@ local config = require('blink.cmp.config') local context = require('blink.cmp.completion.trigger.context') local feedkeys = require('blink.cmp.lib.feedkeys.feedkeys') +local async = require('blink.cmp.lib.async') local text_edits = {} --- Applies one or more text edits to the current buffer, assuming utf-8 encoding +--- @async --- @param edits lsp.TextEdit[] +--- @return blink.cmp.Task +--- @nodiscard function text_edits.apply(edits) - local mode = context.get_mode() - if mode == 'default' then - local fuzzy = require('blink.cmp.fuzzy') - -- Fill the `.` register so that dot-repeat works. This also changes the - -- text in the buffer - currently there is no way to do this in Neovim - -- (only adding new text is supported, but we also want to replace the - -- current word). See the tracking issue for this feature at - -- https://github.com/neovim/neovim/issues/19806#issuecomment-2365146298 - - -- only redoing the first edit is supported - local edit = edits[1] - local cursor = context.get_cursor() - local kwstart, kwend = fuzzy.get_keyword_range(context.get_line(), cursor[2], 'prefix') - local repeat_keys = {} - local original_line = context.get_line() - table.insert(repeat_keys, feedkeys.backspace(kwend - kwstart)) - table.insert(repeat_keys, edit.newText) - - local repeat_str = table.concat(repeat_keys, '') - - feedkeys.call(repeat_str, 'in', function() - -- undo the changes to the buffer (but keep them in the `.` register for - -- repeating) - vim.api.nvim_set_current_line(original_line) - vim.lsp.util.apply_text_edits(edits, vim.api.nvim_get_current_buf(), 'utf-8') - -- TODO need to move the cursor to the left once for some reason - - vim.cmd('normal! h') - end) - return - end + return async.task.new(function(resolve) + local mode = context.get_mode() + if mode == 'default' then + local fuzzy = require('blink.cmp.fuzzy') + -- Fill the `.` register so that dot-repeat works. This also changes the + -- text in the buffer - currently there is no way to do this in Neovim + -- (only adding new text is supported, but we also want to replace the + -- current word). See the tracking issue for this feature at + -- https://github.com/neovim/neovim/issues/19806#issuecomment-2365146298 + + -- only redoing the first edit is supported + local edit = edits[1] + local original_cursor = context.get_cursor() + local kwstart, kwend = fuzzy.get_keyword_range(context.get_line(), original_cursor[2], 'prefix') + local repeat_keys = {} + local original_line = context.get_line() + table.insert(repeat_keys, feedkeys.backspace(kwend - kwstart)) + table.insert(repeat_keys, edit.newText) + + local repeat_str = table.concat(repeat_keys, '') + + -- setting the undolevels forces neovim to create an undo point + vim.o.undolevels = vim.o.undolevels + feedkeys.call_async(repeat_str, 'in'):map(function() + local row = original_cursor[1] + local end_col = kwstart + #edit.newText + local old_text = original_line:sub(1, end_col - 1) + vim.api.nvim_buf_set_text(context.bufnr, row, 0, row, end_col, { old_text }) + -- undo the changes to the buffer (but keep them in the `.` register for + -- repeating) + vim.lsp.util.apply_text_edits(edits, vim.api.nvim_get_current_buf(), 'utf-8') + resolve() + end) + + return + end - assert(mode == 'cmdline', 'Unsupported mode for text edits: ' .. mode) - assert(#edits == 1, 'Cmdline mode only supports one text edit. Contributions welcome!') - - local edit = edits[1] - local line = context.get_line() - local edited_line = line:sub(1, edit.range.start.character) - .. edit.newText - .. line:sub(edit.range['end'].character + 1) - -- FIXME: for some reason, we have to set the cursor here, instead of later, - -- because this will override the cursor position set later - vim.fn.setcmdline(edited_line, edit.range.start.character + #edit.newText + 1) + assert(mode == 'cmdline', 'Unsupported mode for text edits: ' .. mode) + assert(#edits == 1, 'Cmdline mode only supports one text edit. Contributions welcome!') + + local edit = edits[1] + local line = context.get_line() + local edited_line = line:sub(1, edit.range.start.character) + .. edit.newText + .. line:sub(edit.range['end'].character + 1) + -- FIXME: for some reason, we have to set the cursor here, instead of later, + -- because this will override the cursor position set later + vim.fn.setcmdline(edited_line, edit.range.start.character + #edit.newText + 1) + resolve() + end) end ------- Undo -------