From 073035a030f5ef8ae9b9ca51552f887944c52eaa Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Sat, 20 May 2023 00:45:39 -0500 Subject: [PATCH] fix(lsp): don't register didChangeWatchedFiles when capability not set (#23689) Some LSP servers (tailwindcss, rome) are known to request registration for `workspace/didChangeWatchedFiles` even when the corresponding client capability does not advertise support. This change adds an extra check in the `client/registerCapability` handler not to start a watch unless the client capability is set appropriately. --- runtime/lua/vim/lsp/_watchfiles.lua | 7 ++- test/functional/plugin/lsp_spec.lua | 84 +++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua index 3a78724d79..cf2c57db1f 100644 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -193,7 +193,12 @@ local to_lsp_change_type = { function M.register(reg, ctx) local client_id = ctx.client_id local client = vim.lsp.get_client_by_id(client_id) - if not client.workspace_folders then + if + -- Ill-behaved servers may not honor the client capability and try to register + -- anyway, so ignore requests when the user has opted out of the feature. + not client.config.capabilities.workspace.didChangeWatchedFiles.dynamicRegistration + or not client.workspace_folders + then return end local watch_regs = {} diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index fc7b2dafb8..506bc1333b 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3778,6 +3778,13 @@ describe('LSP', function() name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, + }, + }, }) local expected_messages = 2 -- initialize, initialized @@ -3865,6 +3872,13 @@ describe('LSP', function() name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, + }, + }, }) local expected_messages = 2 -- initialize, initialized @@ -3982,6 +3996,13 @@ describe('LSP', function() name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, + }, + }, }) local expected_messages = 2 -- initialize, initialized @@ -4116,6 +4137,13 @@ describe('LSP', function() name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, + }, + }, }) local expected_messages = 2 -- initialize, initialized @@ -4186,5 +4214,61 @@ describe('LSP', function() }, }, result[3].params) end) + + it("ignores registrations by servers when the client doesn't advertise support", function() + exec_lua(create_server_definition) + local result = exec_lua([[ + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = 'some_dir', + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = false, + }, + }, + }, + }) + + local watching = false + require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + -- Since the registration is ignored, this should not execute and `watching` should stay false + watching = true + return function() end + end + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/*', + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + -- Ensure no errors occur when unregistering something that was never really registered. + vim.lsp.handlers['client/unregisterCapability'](nil, { + unregisterations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + }, + }, + }, { client_id = client_id }) + + return watching + ]]) + + eq(false, result) + end) end) end)