From 4bc7bac8842582cc1239373994327d5537155ec0 Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Wed, 30 Apr 2025 16:57:29 -0700 Subject: [PATCH] feat(lsp): start/stop LSPs as necessary during vim.lsp.enable() #33702 Problem: enable() could be more flexible, so that it works even if called "late". Solution: - enable(true) calls `doautoall nvim.lsp.enable FileType`. - enable(false) calls `client:stop()` on matching clients. This will be useful for e.g. :LspStop/:LspStart also. --- runtime/lua/vim/lsp.lua | 29 ++++++++----- test/functional/plugin/lsp_spec.lua | 63 ++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 158db3f904..b501ba4ea6 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -605,21 +605,32 @@ function lsp.enable(name, enable) end if not next(lsp._enabled_configs) then + -- If there are no remaining LSPs enabled, remove the enable autocmd. if lsp_enable_autocmd_id then api.nvim_del_autocmd(lsp_enable_autocmd_id) lsp_enable_autocmd_id = nil end - return + else + -- Only ever create autocmd once to reuse computation of config merging. + lsp_enable_autocmd_id = lsp_enable_autocmd_id + or api.nvim_create_autocmd('FileType', { + group = api.nvim_create_augroup('nvim.lsp.enable', {}), + callback = function(args) + lsp_enable_callback(args.buf) + end, + }) end - -- Only ever create autocmd once to reuse computation of config merging. - lsp_enable_autocmd_id = lsp_enable_autocmd_id - or api.nvim_create_autocmd('FileType', { - group = api.nvim_create_augroup('nvim.lsp.enable', {}), - callback = function(args) - lsp_enable_callback(args.buf) - end, - }) + -- Ensure any pre-existing buffers start/stop their LSP clients. + if enable ~= false then + vim.api.nvim_command('doautoall nvim.lsp.enable FileType') + else + for _, nm in ipairs(names) do + for _, client in ipairs(lsp.get_clients({ name = nm })) do + client:stop() + end + end + end end --- @class vim.lsp.start.Opts diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 7b34e858c8..58e8e5c3fc 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -6324,7 +6324,7 @@ describe('LSP', function() ) end) - it('attaches to buffers', function() + it('attaches to buffers when they are opened', function() exec_lua(create_server_definition) local tmp1 = t.tmpname(true) @@ -6373,6 +6373,67 @@ describe('LSP', function() ) end) + it('attaches/detaches preexisting buffers', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + local tmp2 = t.tmpname(true) + + exec_lua(function() + vim.cmd.edit(tmp1) + vim.bo.filetype = 'foo' + _G.foo_buf = vim.api.nvim_get_current_buf() + + vim.cmd.edit(tmp2) + vim.bo.filetype = 'bar' + _G.bar_buf = vim.api.nvim_get_current_buf() + + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.config('bar', { + cmd = server.cmd, + filetypes = { 'bar' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.enable('foo') + vim.lsp.enable('bar') + end) + + eq( + { 1, 'foo', 1, 'bar' }, + exec_lua(function() + local foos = vim.lsp.get_clients({ bufnr = assert(_G.foo_buf) }) + local bars = vim.lsp.get_clients({ bufnr = assert(_G.bar_buf) }) + return { #foos, foos[1].name, #bars, bars[1].name } + end) + ) + + -- Now disable the 'foo' lsp and confirm that it's detached from the buffer it was previous + -- attached to. + exec_lua([[vim.lsp.enable('foo', false)]]) + eq( + { 0, 'foo', 1, 'bar' }, + exec_lua(function() + local foos = vim.lsp.get_clients({ bufnr = assert(_G.foo_buf) }) + local bars = vim.lsp.get_clients({ bufnr = assert(_G.bar_buf) }) + return { #foos, 'foo', #bars, bars[1].name } + end) + ) + end) + it('does not attach to buffers more than once if no root_dir', function() exec_lua(create_server_definition)