commit 9e9cdcaa182be4819db5d3323fdfb81477a69c37
parent 4e1644d4d3cb6b2ae7bd7110add8424e5217882b
Author: Tristan Knight <admin@snappeh.com>
Date: Sat, 6 Dec 2025 23:31:11 +0000
refactor(lsp): unify capability checks and registration #36781
Problem:
Our LSP type system didnt have a concept of RegistrationMethods, this is where the method to dynamically register for a capability is sent to a different method endpoint then is used to call it. Eg `textDocument/semanticTokens` rather than the specific full/range/delta methods
Solution:
Extended generator to create `vim.lsp.protocol.Methods.Registration` with these registration methods. Also extend `_request_name_to_client_capability` to cover these methods. Adjust typing to suit
Diffstat:
6 files changed, 61 insertions(+), 32 deletions(-)
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
@@ -1662,7 +1662,7 @@ Lua module: vim.lsp.client *lsp-client*
See |Client:exec_cmd()|.
• {on_attach} (`fun(self: vim.lsp.Client, bufnr: integer)`)
See |Client:on_attach()|.
- • {supports_method} (`fun(self: vim.lsp.Client, method: string, bufnr: integer?)`)
+ • {supports_method} (`fun(self: vim.lsp.Client, method: string|string, bufnr: integer?)`)
See |Client:supports_method()|.
*vim.lsp.Client.Progress*
@@ -1920,7 +1920,7 @@ Client:supports_method({method}, {bufnr}) *Client:supports_method()*
Note: Some language server capabilities can be file specific.
Parameters: ~
- • {method} (`string`)
+ • {method} (`string|string`)
• {bufnr} (`integer?`)
diff --git a/runtime/lua/vim/lsp/_capability.lua b/runtime/lua/vim/lsp/_capability.lua
@@ -21,7 +21,7 @@ local all_capabilities = {}
---@field name vim.lsp.capability.Name
---
--- Static field records the method this capability requires.
----@field method vim.lsp.protocol.Method.ClientToServer
+---@field method vim.lsp.protocol.Method.ClientToServer | vim.lsp.protocol.Method.Registration
---
--- Static field for retrieving the instance associated with a specific `bufnr`.
---
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
@@ -612,41 +612,22 @@ function Client:initialize()
end)
end
--- Server capabilities for methods that support static registration.
-local static_registration_capabilities = {
- ['textDocument/prepareCallHierarchy'] = 'callHierarchyProvider',
- ['textDocument/documentColor'] = 'colorProvider',
- ['textDocument/declaration'] = 'declarationProvider',
- ['textDocument/diagnostic'] = 'diagnosticProvider',
- ['textDocument/foldingRange'] = 'foldingRangeProvider',
- ['textDocument/implementation'] = 'implementationProvider',
- ['textDocument/inlayHint'] = 'inlayHintProvider',
- ['textDocument/inlineCompletion'] = 'inlineCompletionProvider',
- ['textDocument/inlineValue'] = 'inlineValueProvider',
- ['textDocument/linkedEditingRange'] = 'linkedEditingRangeProvider',
- ['textDocument/moniker'] = 'monikerProvider',
- ['textDocument/selectionRange'] = 'selectionRangeProvider',
- ['textDocument/semanticTokens/full'] = 'semanticTokensProvider',
- ['textDocument/semanticTokens/range'] = 'semanticTokensProvider',
- ['textDocument/typeDefinition'] = 'typeDefinitionProvider',
- ['textDocument/prepareTypeHierarchy'] = 'typeHierarchyProvider',
-}
-
--- @private
function Client:_process_static_registrations()
local static_registrations = {} ---@type lsp.Registration[]
- for method, capability in pairs(static_registration_capabilities) do
+ for method, capability in pairs(lsp.protocol._request_name_to_server_capability) do
if
- vim.tbl_get(self.server_capabilities, capability, 'id')
+ vim.tbl_get(self.server_capabilities, unpack(capability), 'id')
--- @cast method vim.lsp.protocol.Method
and self:_supports_registration(method)
then
+ local cap = vim.tbl_get(self.server_capabilities, unpack(capability))
static_registrations[#static_registrations + 1] = {
- id = self.server_capabilities[capability].id,
+ id = cap.id,
method = method,
registerOptions = {
- documentSelector = self.server_capabilities[capability].documentSelector, ---@type lsp.DocumentSelector|lsp.null
+ documentSelector = cap.documentSelector, ---@type lsp.DocumentSelector|lsp.null
},
}
end
@@ -928,7 +909,7 @@ function Client:stop(force)
end
--- Get options for a method that is registered dynamically.
---- @param method vim.lsp.protocol.Method
+--- @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))
@@ -1002,7 +983,7 @@ function Client:_get_language_id(bufnr)
return self.get_language_id(bufnr, vim.bo[bufnr].filetype)
end
---- @param method vim.lsp.protocol.Method
+--- @param method vim.lsp.protocol.Method | vim.lsp.protocol.Method.Registration
--- @param bufnr? integer
--- @return lsp.Registration?
function Client:_get_registration(method, bufnr)
@@ -1171,7 +1152,7 @@ end
--- Always returns true for unknown off-spec methods.
---
--- Note: Some language server capabilities can be file specific.
---- @param method vim.lsp.protocol.Method.ClientToServer
+--- @param method vim.lsp.protocol.Method.ClientToServer | vim.lsp.protocol.Method.Registration
--- @param bufnr? integer
function Client:supports_method(method, bufnr)
-- Deprecated form
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
@@ -1149,6 +1149,12 @@ protocol.Methods = {
workspace_workspaceFolders = 'workspace/workspaceFolders',
}
+-- Generated by gen_lsp.lua, keep at end of file.
+--- LSP registration methods
+---@alias vim.lsp.protocol.Method.Registration
+--- | 'notebookDocument/sync'
+--- | 'textDocument/semanticTokens'
+
-- stylua: ignore start
-- Generated by gen_lsp.lua, keep at end of file.
--- Maps method names to the required client capability
@@ -1290,6 +1296,7 @@ protocol._request_name_to_server_capability = {
['workspace/willDeleteFiles'] = { 'workspace', 'fileOperations', 'willDelete' },
['workspace/willRenameFiles'] = { 'workspace', 'fileOperations', 'willRename' },
['workspace/workspaceFolders'] = { 'workspace', 'workspaceFolders' },
+ ['textDocument/semanticTokens'] = { 'semanticTokensProvider' },
}
-- stylua: ignore end
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -47,8 +47,7 @@ local FULL = 'FULL'
---@field client_state table<integer, STClientState>
local STHighlighter = {
name = 'semantic_tokens',
- -- TODO: how to handle this (tris203)
- method = 'textDocument/semanticTokens/full',
+ method = 'textDocument/semanticTokens',
active = {},
}
STHighlighter.__index = STHighlighter
diff --git a/src/gen/gen_lsp.lua b/src/gen/gen_lsp.lua
@@ -200,6 +200,24 @@ local function write_to_vim_protocol(protocol)
output[#output + 1] = '}'
end
+ do -- registrationMethods
+ local found = {} --- @type table<string, boolean>
+ vim.list_extend(output, {
+ '',
+ '-- Generated by gen_lsp.lua, keep at end of file.',
+ '--- LSP registration methods',
+ '---@alias vim.lsp.protocol.Method.Registration',
+ })
+ for _, item in ipairs(all) do
+ if item.registrationMethod and not found[item.registrationMethod] then
+ vim.list_extend(output, {
+ ("--- | '%s'"):format(item.registrationMethod or item.method),
+ })
+ found[item.registrationMethod or item.method] = true
+ end
+ end
+ end
+
do -- capabilities
vim.list_extend(output, {
'',
@@ -238,6 +256,30 @@ local function write_to_vim_protocol(protocol)
end
end
+ ---@type table<string, string[]>
+ local registration_capability = {}
+ for _, item in ipairs(all) do
+ if item.serverCapability then
+ if item.registrationMethod and item.registrationMethod ~= item.method then
+ local registrationMethod = item.registrationMethod
+ assert(registrationMethod, 'registrationMethod is nil')
+ if not registration_capability[item.registrationMethod] then
+ registration_capability[registrationMethod] = {}
+ end
+ table.insert(registration_capability[registrationMethod], item.serverCapability)
+ end
+ end
+ end
+
+ for registrationMethod, capabilities in pairs(registration_capability) do
+ output[#output + 1] = (" ['%s'] = { '%s' },"):format(
+ registrationMethod,
+ vim.iter(capabilities):fold(capabilities[1], function(acc, v)
+ return #v < #acc and v or acc
+ end)
+ )
+ end
+
output[#output + 1] = '}'
output[#output + 1] = '-- stylua: ignore end'
end