Skip to content

Commit

Permalink
refactor: make completion.accept async
Browse files Browse the repository at this point in the history
  • Loading branch information
mikavilpas committed Jan 12, 2025
1 parent 7307d3c commit 37ae30b
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 103 deletions.
23 changes: 13 additions & 10 deletions lua/blink/cmp/completion/accept/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 10 additions & 10 deletions lua/blink/cmp/completion/accept/preview.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 7 additions & 6 deletions lua/blink/cmp/completion/list.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
50 changes: 15 additions & 35 deletions lua/blink/cmp/lib/feedkeys/feedkeys.lua
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -18,17 +27,6 @@ feedkeys.call = setmetatable({
table.insert(queue, { feedkeys.t('<Cmd>setlocal backspace=%s<CR>'):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('<Cmd>lua require"blink.cmp.lib.feedkeys.feedkeys".run(%s)<CR>'):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])
Expand All @@ -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
Expand Down
95 changes: 53 additions & 42 deletions lua/blink/cmp/lib/text_edits.lua
Original file line number Diff line number Diff line change
@@ -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 -------
Expand Down

0 comments on commit 37ae30b

Please sign in to comment.