diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index f8df714278..119d1d77bb 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -778,6 +778,28 @@ local function get_augroup(bufnr) return string.format('nvim.lsp.completion_%d', bufnr) end +--- @param client_id integer +--- @param bufnr integer +local function disable_completions(client_id, bufnr) + local handle = buf_handles[bufnr] + if not handle then + return + end + + handle.clients[client_id] = nil + if not next(handle.clients) then + buf_handles[bufnr] = nil + api.nvim_del_augroup_by_name(get_augroup(bufnr)) + else + for char, clients in pairs(handle.triggers) do + --- @param c vim.lsp.Client + handle.triggers[char] = vim.tbl_filter(function(c) + return c.id ~= client_id + end, clients) + end + end +end + --- @inlinedoc --- @class vim.lsp.completion.BufferOpts --- @field autotrigger? boolean (default: false) When true, completion triggers automatically based on the server's `triggerCharacters`. @@ -805,6 +827,14 @@ local function enable_completions(client_id, bufnr, opts) -- Set up autocommands. local group = api.nvim_create_augroup(get_augroup(bufnr), { clear = true }) + api.nvim_create_autocmd('LspDetach', { + group = group, + buffer = bufnr, + desc = 'vim.lsp.completion: clean up client on detach', + callback = function(args) + disable_completions(args.data.client_id, args.buf) + end, + }) api.nvim_create_autocmd('CompleteDone', { group = group, buffer = bufnr, @@ -861,28 +891,6 @@ local function enable_completions(client_id, bufnr, opts) end end ---- @param client_id integer ---- @param bufnr integer -local function disable_completions(client_id, bufnr) - local handle = buf_handles[bufnr] - if not handle then - return - end - - handle.clients[client_id] = nil - if not next(handle.clients) then - buf_handles[bufnr] = nil - api.nvim_del_augroup_by_name(get_augroup(bufnr)) - else - for char, clients in pairs(handle.triggers) do - --- @param c vim.lsp.Client - handle.triggers[char] = vim.tbl_filter(function(c) - return c.id ~= client_id - end, clients) - end - end -end - --- Enables or disables completions from the given language client in the given --- buffer. Effects of enabling completions are: --- diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 1ab22f89b9..486d9f1686 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -1567,6 +1567,45 @@ describe('vim.lsp.completion: integration', function() feed('') eq('w-1/2', n.api.nvim_get_current_line()) end) + it('removes client from triggers and clients table on LspDetach', function() + local list1 = { + isIncomplete = false, + items = { { label = 'foo' } }, + } + local list2 = { + isIncomplete = false, + items = { { label = 'bar' } }, + } + local id1 = create_server('dummy1', list1, { trigger_chars = { '.' } }) + local id2 = create_server('dummy2', list2, { trigger_chars = { '.' } }) + n.command('set cot=menuone,menu') + local function assert_matches(expected) + retry(nil, nil, function() + eq( + 1, + exec_lua(function() + return vim.fn.pumvisible() + end) + ) + end) + eq(expected, n.api.nvim_get_current_line()) + end + feed('Sw.') + assert_matches('w.foo') + exec_lua('vim.lsp.buf_detach_client(0, ' .. id1 .. ')') + feed('Sw.') + assert_matches('w.bar') + exec_lua('vim.lsp.buf_detach_client(0, ' .. id2 .. ')') + feed('Sw.') + retry(nil, nil, function() + eq( + 0, + exec_lua(function() + return vim.fn.pumvisible() + end) + ) + end) + end) end) describe("vim.lsp.completion: omnifunc + 'autocomplete'", function()