mirror of
https://github.com/neovim/neovim.git
synced 2026-06-17 17:21:16 +00:00
fix(lsp): requery empty isIncomplete completion lists #40100
Problem:
an empty `{ isIncomplete = true, items = {} }` ends completion
instead of requerying.
Solution:
keep isIncomplete on empty lists and retrigger on keypress while incomplete.
Reset on <C-e> so it doesn't immediately re-query.
This commit is contained in:
@@ -937,6 +937,9 @@ local function register_completedone(bufnr)
|
||||
local reason = api.nvim_get_vvar('event').reason ---@type string
|
||||
if reason == 'accept' then
|
||||
on_complete_done()
|
||||
elseif reason == 'cancel' then
|
||||
-- <C-e> dismissed the pum; stop re-querying an incomplete list.
|
||||
Context:reset()
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -1002,23 +1005,27 @@ local function trigger(bufnr, clients, ctx)
|
||||
client and client.name or 'UNKNOWN'
|
||||
)
|
||||
)
|
||||
elseif not vim.isnil(result) and #(result.items or result) > 0 then
|
||||
-- result is CompletionItem[] or CompletionList; result.items may be empty, and
|
||||
-- an empty incomplete list means request again when needed.
|
||||
elseif not vim.isnil(result) then
|
||||
Context.isIncomplete = Context.isIncomplete or result.isIncomplete
|
||||
local encoding = client and client.offset_encoding or 'utf-16'
|
||||
local client_matches, tmp_server_start_boundary
|
||||
client_matches, tmp_server_start_boundary = M._convert_results(
|
||||
line,
|
||||
cursor_row - 1,
|
||||
cursor_col,
|
||||
client_id,
|
||||
word_boundary,
|
||||
nil,
|
||||
result,
|
||||
encoding
|
||||
)
|
||||
if #(result.items or result) > 0 then
|
||||
local encoding = client and client.offset_encoding or 'utf-16'
|
||||
local client_matches, tmp_server_start_boundary
|
||||
client_matches, tmp_server_start_boundary = M._convert_results(
|
||||
line,
|
||||
cursor_row - 1,
|
||||
cursor_col,
|
||||
client_id,
|
||||
word_boundary,
|
||||
nil,
|
||||
result,
|
||||
encoding
|
||||
)
|
||||
|
||||
server_start_boundary = tmp_server_start_boundary or server_start_boundary
|
||||
vim.list_extend(matches, client_matches)
|
||||
server_start_boundary = tmp_server_start_boundary or server_start_boundary
|
||||
vim.list_extend(matches, client_matches)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1056,31 +1063,33 @@ end
|
||||
|
||||
--- @param handle vim.lsp.completion.BufHandle
|
||||
local function on_insert_char_pre(handle)
|
||||
if vim.fn.pumvisible() ~= 0 then
|
||||
if Context.isIncomplete then
|
||||
reset_timer()
|
||||
if Context.isIncomplete then
|
||||
reset_timer()
|
||||
|
||||
local debounce_ms = adaptive_debounce(Context.last_request_time, rtt_ms)
|
||||
local ctx = { triggerKind = protocol.CompletionTriggerKind.TriggerForIncompleteCompletions }
|
||||
if debounce_ms == 0 then
|
||||
vim.schedule(function()
|
||||
local debounce_ms = adaptive_debounce(Context.last_request_time, rtt_ms)
|
||||
local ctx = { triggerKind = protocol.CompletionTriggerKind.TriggerForIncompleteCompletions }
|
||||
if debounce_ms == 0 then
|
||||
vim.schedule(function()
|
||||
M.get({ ctx = ctx })
|
||||
end)
|
||||
else
|
||||
completion_timer = new_timer()
|
||||
completion_timer:start(
|
||||
math.floor(debounce_ms),
|
||||
0,
|
||||
vim.schedule_wrap(function()
|
||||
M.get({ ctx = ctx })
|
||||
end)
|
||||
else
|
||||
completion_timer = new_timer()
|
||||
completion_timer:start(
|
||||
math.floor(debounce_ms),
|
||||
0,
|
||||
vim.schedule_wrap(function()
|
||||
M.get({ ctx = ctx })
|
||||
end)
|
||||
)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if vim.fn.pumvisible() ~= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local char = vim.v.char
|
||||
local matched_clients = handle.triggers[char]
|
||||
-- Discard pending trigger char, complete the "latest" one.
|
||||
|
||||
@@ -1241,6 +1241,44 @@ describe('vim.lsp.completion: protocol', function()
|
||||
end)
|
||||
t.matches('items=null', err)
|
||||
end)
|
||||
|
||||
it('keeps requerying while the completion list is incomplete #40096', function()
|
||||
exec_lua(function()
|
||||
_G.contexts = {}
|
||||
local server = _G._create_server({
|
||||
capabilities = {
|
||||
completionProvider = { triggerCharacters = { 'h' } },
|
||||
},
|
||||
handlers = {
|
||||
['textDocument/completion'] = function(_, params, callback)
|
||||
_G.contexts[#_G.contexts + 1] = params.context
|
||||
callback(nil, { isIncomplete = true, items = { { label = 'hello' } } })
|
||||
end,
|
||||
},
|
||||
})
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
vim.api.nvim_win_set_buf(0, bufnr)
|
||||
vim.lsp.start({
|
||||
name = 'dummy',
|
||||
cmd = server.cmd,
|
||||
on_attach = function(client, bufnr0)
|
||||
vim.lsp.completion.enable(true, client.id, bufnr0, { autotrigger = true })
|
||||
end,
|
||||
})
|
||||
end)
|
||||
feed('ih')
|
||||
assert_matches(function(matches)
|
||||
eq('hello', matches[1].word)
|
||||
end)
|
||||
eq({ triggerKind = 2, triggerCharacter = 'h' }, exec_lua('return _G.contexts[1]'))
|
||||
|
||||
exec_lua('_G.capture = {}')
|
||||
feed('e')
|
||||
assert_matches(function(matches)
|
||||
eq('hello', matches[1].word)
|
||||
end)
|
||||
eq({ triggerKind = 3 }, exec_lua('return _G.contexts[2]'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('vim.lsp.completion: integration', function()
|
||||
|
||||
Reference in New Issue
Block a user