neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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:
Mruntime/doc/lsp.txt | 4++--
Mruntime/lua/vim/lsp/_capability.lua | 2+-
Mruntime/lua/vim/lsp/client.lua | 35++++++++---------------------------
Mruntime/lua/vim/lsp/protocol.lua | 7+++++++
Mruntime/lua/vim/lsp/semantic_tokens.lua | 3+--
Msrc/gen/gen_lsp.lua | 42++++++++++++++++++++++++++++++++++++++++++
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