mirror of
https://github.com/neovim/neovim.git
synced 2026-08-24 16:11:52 +00:00
fix(lsp): advance inline completion range over matching input #41434
Problem: `Completor:accept()` applies the item against the range the server answered with. Characters typed after the request but before accepting lie inside neither that range nor the replacement, so they are left behind by the insertion: typing `a` after `foob` and accepting `foobar` within the 200 ms debounce yields `foobara`. Solution: Grow the item's range over the characters typed since the request, so that `accept()` replaces them. Only while the candidate still matches, decided by the longest common prefix `show()` already computes, so input that contradicts it is left alone. Snippet items are unaffected, since `accept()` ignores the range for them.
This commit is contained in:
@@ -63,6 +63,7 @@ local namespace = api.nvim_create_namespace('nvim.lsp.inline_completion')
|
||||
---@field active table<integer, vim.lsp.inline_completion.Completor?>
|
||||
---@field timer? uv.uv_timer_t Timer for debouncing automatic requests
|
||||
---@field current? vim.lsp.inline_completion.Item Currently selected item
|
||||
---@field _overlay? boolean Whether `current` replaces buffer text, per its original range
|
||||
---@field client_state table<integer, vim.lsp.inline_completion.ClientState>
|
||||
local Completor = {
|
||||
name = 'inline_completion',
|
||||
@@ -184,6 +185,8 @@ function Completor:select(index, show_index)
|
||||
_filter_text = item.filterText,
|
||||
command = item.command,
|
||||
}
|
||||
-- Recorded here because `show()` may grow the range over typed characters.
|
||||
self._overlay = range ~= nil and not range:is_empty()
|
||||
|
||||
local hint = show_index and (' (%d/%d)'):format(index, self:count_items()) or nil
|
||||
self:show(hint)
|
||||
@@ -240,6 +243,14 @@ function Completor:show(hint)
|
||||
local cursor_row, cursor_col =
|
||||
vim.pos.cursor(self.bufnr, api.nvim_win_get_cursor(winid)):to_extmark()
|
||||
if row == cursor_row then
|
||||
-- Characters typed since the request lie past the item's range, so `accept()`
|
||||
-- would leave them behind. `skip` stops where the buffer stops matching.
|
||||
if current.range and col + skip - 1 >= cursor_col then
|
||||
local start_row, start_col, _, end_col = current.range:to_extmark()
|
||||
if end_col < cursor_col then
|
||||
current.range = vim.range.extmark(self.bufnr, start_row, start_col, row, cursor_col)
|
||||
end
|
||||
end
|
||||
skip = math.max(skip, cursor_col - col + 1)
|
||||
end
|
||||
end
|
||||
@@ -256,7 +267,7 @@ function Completor:show(hint)
|
||||
api.nvim_buf_set_extmark(self.bufnr, namespace, row, col, {
|
||||
virt_text = virt_text,
|
||||
virt_lines = virt_lines,
|
||||
virt_text_pos = (current.range and not current.range:is_empty() and 'overlay') or 'inline',
|
||||
virt_text_pos = (self._overlay and 'overlay') or 'inline',
|
||||
hl_mode = 'combine',
|
||||
})
|
||||
end
|
||||
|
||||
@@ -84,6 +84,11 @@ describe('vim.lsp.inline_completion', function()
|
||||
},
|
||||
handlers = {
|
||||
['textDocument/inlineCompletion'] = function(_, _, callback)
|
||||
if _G.items then
|
||||
callback(nil, { items = _G.items })
|
||||
return
|
||||
end
|
||||
|
||||
if _G.empty then
|
||||
callback(nil, {
|
||||
items = {
|
||||
@@ -293,6 +298,38 @@ describe('vim.lsp.inline_completion', function()
|
||||
feed('<Esc>')
|
||||
screen:expect({ grid = grid_applied_candidates })
|
||||
end)
|
||||
|
||||
it('does not leave behind text typed since the request', function()
|
||||
exec_lua(function()
|
||||
_G.items = {
|
||||
{
|
||||
insertText = 'foobar',
|
||||
range = {
|
||||
start = { line = 1, character = 0 },
|
||||
['end'] = { line = 1, character = 1 },
|
||||
},
|
||||
},
|
||||
}
|
||||
end)
|
||||
feed('ifo')
|
||||
screen:expect([[
|
||||
function fibonacci() |
|
||||
fo{1:^obar} |
|
||||
{1:~ }|*11
|
||||
{3:-- INSERT --} |
|
||||
]])
|
||||
exec_lua(function()
|
||||
vim.lsp.inline_completion.get()
|
||||
end)
|
||||
n.poke_eventloop()
|
||||
feed('<Esc>')
|
||||
screen:expect([[
|
||||
function fibonacci() |
|
||||
fooba^r |
|
||||
{1:~ }|*11
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('select()', function()
|
||||
|
||||
Reference in New Issue
Block a user