commit 8165427b4d92e55be5644bdf9270eb49b99d6dcc
parent 46220afef806e571b408973c51ba72acf009e078
Author: Tristan Knight <admin@snappeh.com>
Date: Mon, 15 Dec 2025 18:23:57 +0000
fix(lsp): correct capability checks for dynamic registration (#36932)
Refactor capability checks in Client:_supports_registration and
Client:supports_method to properly handle dynamicRegistration and unknown
methods. Now, dynamic capabilities are checked before assuming support for
unknown methods, ensuring more accurate LSP feature detection.
Diffstat:
2 files changed, 88 insertions(+), 8 deletions(-)
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
@@ -912,7 +912,8 @@ end
--- @param method vim.lsp.protocol.Method | vim.lsp.protocol.Method.Registration
function Client:_supports_registration(method)
local capability_path = lsp.protocol._request_name_to_client_capability[method] or {}
- local capability = vim.tbl_get(self.capabilities, unpack(capability_path))
+ -- dynamicRegistration is at the second level, even in deeply nested capabilities
+ local capability = vim.tbl_get(self.capabilities, capability_path[1], capability_path[2])
return type(capability) == 'table' and capability.dynamicRegistration
end
@@ -1161,11 +1162,7 @@ function Client:supports_method(method, bufnr)
bufnr = bufnr.bufnr
end
local required_capability = lsp.protocol._request_name_to_server_capability[method]
- -- if we don't know about the method, assume that the client supports it.
- if not required_capability then
- return true
- end
- if vim.tbl_get(self.server_capabilities, unpack(required_capability)) then
+ if required_capability and vim.tbl_get(self.server_capabilities, unpack(required_capability)) then
return true
end
@@ -1180,7 +1177,9 @@ function Client:supports_method(method, bufnr)
return self:_get_registration(method, bufnr) ~= nil
end
end
- return false
+ -- if we don't know about the method, assume that the client supports it.
+ -- This needs to be at the end, so that dynamic_capabilities are checked first
+ return required_capability == nil
end
--- @private
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
@@ -5841,6 +5841,11 @@ describe('LSP', function()
dynamicRegistration = true,
},
},
+ workspace = {
+ didChangeWatchedFiles = {
+ dynamicRegistration = true,
+ },
+ },
},
}))
@@ -5902,15 +5907,91 @@ describe('LSP', function()
check('textDocument/rangeFormatting', tmpfile)
check('textDocument/completion')
+ check('workspace/didChangeWatchedFiles')
+ check('workspace/didChangeWatchedFiles', tmpfile)
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'didChangeWatched',
+ method = 'workspace/didChangeWatchedFiles',
+ registerOptions = {
+ watchers = {
+ {
+ globPattern = 'something',
+ kind = 4,
+ },
+ },
+ },
+ },
+ },
+ }, { client_id = client_id })
+
+ check('workspace/didChangeWatchedFiles')
+ check('workspace/didChangeWatchedFiles', tmpfile)
+
return result
end)
- eq(5, #result)
+ eq(9, #result)
eq({ method = 'textDocument/formatting', supported = false }, result[1])
eq({ method = 'textDocument/formatting', supported = true, fname = tmpfile }, result[2])
eq({ method = 'textDocument/rangeFormatting', supported = true }, result[3])
eq({ method = 'textDocument/rangeFormatting', supported = true, fname = tmpfile }, result[4])
eq({ method = 'textDocument/completion', supported = false }, result[5])
+ eq({ method = 'workspace/didChangeWatchedFiles', supported = false }, result[6])
+ eq(
+ { method = 'workspace/didChangeWatchedFiles', supported = false, fname = tmpfile },
+ result[7]
+ )
+ eq({ method = 'workspace/didChangeWatchedFiles', supported = true }, result[8])
+ eq(
+ { method = 'workspace/didChangeWatchedFiles', supported = true, fname = tmpfile },
+ result[9]
+ )
+ end)
+
+ it('identifies client dynamic registration capability', function()
+ exec_lua(create_server_definition)
+ local result = exec_lua(function()
+ local server = _G._create_server()
+ local client_id = assert(vim.lsp.start({
+ name = 'dynamic-test',
+ cmd = server.cmd,
+ capabilities = {
+ textDocument = {
+ formatting = {
+ dynamicRegistration = true,
+ },
+ synchronization = {
+ dynamicRegistration = true,
+ },
+ },
+ },
+ }))
+
+ local result = {}
+ local function check(method)
+ local client = assert(vim.lsp.get_client_by_id(client_id))
+ result[#result + 1] = {
+ method = method,
+ supports_reg = client:_supports_registration(method),
+ }
+ end
+
+ check('textDocument/formatting')
+ check('textDocument/didSave')
+ check('textDocument/didOpen')
+ check('textDocument/codeLens')
+
+ return result
+ end)
+
+ eq(4, #result)
+ eq({ method = 'textDocument/formatting', supports_reg = true }, result[1])
+ eq({ method = 'textDocument/didSave', supports_reg = true }, result[2])
+ eq({ method = 'textDocument/didOpen', supports_reg = true }, result[3])
+ eq({ method = 'textDocument/codeLens', supports_reg = false }, result[4])
end)
it('supports static registration', function()