mirror of
https://github.com/neovim/neovim.git
synced 2026-03-29 20:02:03 +00:00
refactor(lsp): centralize provider capability resolution #37221
- Refactor LSP client to use unified provider-based capability lookup for diagnostics and other features. - Introduce `_provider_value_get` to abstract capability retrieval, supporting both static and dynamic registrations. - Update diagnostic handling and protocol mappings to leverage provider-centric logic.
This commit is contained in:
@@ -615,19 +615,17 @@ end
|
|||||||
function Client:_process_static_registrations()
|
function Client:_process_static_registrations()
|
||||||
local static_registrations = {} ---@type lsp.Registration[]
|
local static_registrations = {} ---@type lsp.Registration[]
|
||||||
|
|
||||||
for method, capability in pairs(lsp.protocol._request_name_to_server_capability) do
|
for method in pairs(lsp.protocol._method_supports_static_registration) do
|
||||||
|
local capability = lsp.protocol._request_name_to_server_capability[method]
|
||||||
if
|
if
|
||||||
vim.tbl_get(self.server_capabilities, unpack(capability), 'id')
|
vim.tbl_get(self.server_capabilities, capability[1], 'id')
|
||||||
--- @cast method vim.lsp.protocol.Method
|
|
||||||
and self:_supports_registration(method)
|
and self:_supports_registration(method)
|
||||||
then
|
then
|
||||||
local cap = vim.tbl_get(self.server_capabilities, unpack(capability))
|
local cap = vim.tbl_get(self.server_capabilities, capability[1])
|
||||||
static_registrations[#static_registrations + 1] = {
|
static_registrations[#static_registrations + 1] = {
|
||||||
id = cap.id,
|
id = cap.id,
|
||||||
method = method,
|
method = method,
|
||||||
registerOptions = {
|
registerOptions = cap or {},
|
||||||
documentSelector = cap.documentSelector, ---@type lsp.DocumentSelector|lsp.null
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -936,9 +934,12 @@ end
|
|||||||
--- Get options for a method that is registered dynamically.
|
--- Get options for a method that is registered dynamically.
|
||||||
--- @param method vim.lsp.protocol.Method | vim.lsp.protocol.Method.Registration
|
--- @param method vim.lsp.protocol.Method | vim.lsp.protocol.Method.Registration
|
||||||
function Client:_supports_registration(method)
|
function Client:_supports_registration(method)
|
||||||
local capability_path = lsp.protocol._request_name_to_client_capability[method] or {}
|
if lsp.protocol._methods_with_no_registration_options[method] then
|
||||||
-- dynamicRegistration is at the second level, even in deeply nested capabilities
|
return true
|
||||||
local capability = vim.tbl_get(self.capabilities, capability_path[1], capability_path[2])
|
end
|
||||||
|
local provider = self:_registration_provider(method)
|
||||||
|
local capability_path = lsp.protocol._provider_to_client_registration[provider]
|
||||||
|
local capability = vim.tbl_get(self.capabilities, unpack(capability_path))
|
||||||
return type(capability) == 'table' and capability.dynamicRegistration
|
return type(capability) == 'table' and capability.dynamicRegistration
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -946,7 +947,7 @@ end
|
|||||||
--- @param method vim.lsp.protocol.Method | vim.lsp.protocol.Method.Registration
|
--- @param method vim.lsp.protocol.Method | vim.lsp.protocol.Method.Registration
|
||||||
function Client:_registration_provider(method)
|
function Client:_registration_provider(method)
|
||||||
local capability_path = lsp.protocol._request_name_to_server_capability[method]
|
local capability_path = lsp.protocol._request_name_to_server_capability[method]
|
||||||
return capability_path and capability_path[1] or method
|
return capability_path and capability_path[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @private
|
--- @private
|
||||||
@@ -1205,7 +1206,7 @@ function Client:supports_method(method, bufnr)
|
|||||||
|
|
||||||
local provider = self:_registration_provider(method)
|
local provider = self:_registration_provider(method)
|
||||||
local regs = self:_get_registrations(provider, bufnr)
|
local regs = self:_get_registrations(provider, bufnr)
|
||||||
if lsp.protocol._request_name_allows_registration[method] and not regs then
|
if lsp.protocol._method_supports_dynamic_registration[method] and not regs then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
if regs then
|
if regs then
|
||||||
@@ -1214,6 +1215,9 @@ function Client:supports_method(method, bufnr)
|
|||||||
if vim.tbl_get(reg, 'registerOptions', unpack(required_capability, 2)) then
|
if vim.tbl_get(reg, 'registerOptions', unpack(required_capability, 2)) then
|
||||||
return self:_supports_registration(reg.method)
|
return self:_supports_registration(reg.method)
|
||||||
end
|
end
|
||||||
|
if lsp.protocol._methods_with_no_registration_options[method] then
|
||||||
|
return true
|
||||||
|
end
|
||||||
else
|
else
|
||||||
return self:_supports_registration(reg.method)
|
return self:_supports_registration(reg.method)
|
||||||
end
|
end
|
||||||
@@ -1226,6 +1230,40 @@ function Client:supports_method(method, bufnr)
|
|||||||
return required_capability == nil
|
return required_capability == nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Retrieves all capability values for a given LSP method, handling both static and dynamic registrations.
|
||||||
|
--- This function abstracts over differences between capabilities declared in `server_capabilities`
|
||||||
|
--- and those registered dynamically at runtime, returning all matching capability values.
|
||||||
|
--- It also handles cases where the registration method differs from the calling method by abstracting to the Provider.
|
||||||
|
--- For example, `workspace/diagnostic` uses capabilities registered under `textDocument/diagnostic`.
|
||||||
|
--- This is useful for features like diagnostics and formatting, where servers may register multiple providers
|
||||||
|
--- with different options (such as specific filetypes or document selectors).
|
||||||
|
--- @param method vim.lsp.protocol.Method.ClientToServer | vim.lsp.protocol.Method.Registration LSP method name
|
||||||
|
--- @param ... any Additional keys to index into the capability
|
||||||
|
--- @return lsp.LSPAny[] # The capability value if it exists, empty table if not found
|
||||||
|
function Client:_provider_value_get(method, ...)
|
||||||
|
local matched_regs = {} --- @type any[]
|
||||||
|
local provider = self:_registration_provider(method)
|
||||||
|
local dynamic_regs = self:_get_registrations(provider)
|
||||||
|
if not provider then
|
||||||
|
return matched_regs
|
||||||
|
elseif not dynamic_regs then
|
||||||
|
-- First check static capabilities
|
||||||
|
local static_reg = vim.tbl_get(self.server_capabilities, provider)
|
||||||
|
if static_reg then
|
||||||
|
matched_regs[1] = vim.tbl_get(static_reg, ...) or vim.NIL
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local required_capability = lsp.protocol._request_name_to_server_capability[method]
|
||||||
|
for _, reg in ipairs(dynamic_regs) do
|
||||||
|
if vim.tbl_get(reg, 'registerOptions', unpack(required_capability, 2)) then
|
||||||
|
matched_regs[#matched_regs + 1] = vim.tbl_get(reg, 'registerOptions', ...) or vim.NIL
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return matched_regs
|
||||||
|
end
|
||||||
|
|
||||||
--- @private
|
--- @private
|
||||||
--- Handles a notification sent by an LSP server by invoking the
|
--- Handles a notification sent by an LSP server by invoking the
|
||||||
--- corresponding handler.
|
--- corresponding handler.
|
||||||
|
|||||||
@@ -193,14 +193,8 @@ function M.get_namespace(client_id, is_pull)
|
|||||||
|
|
||||||
local client = lsp.get_client_by_id(client_id)
|
local client = lsp.get_client_by_id(client_id)
|
||||||
if is_pull then
|
if is_pull then
|
||||||
local server_id =
|
local key = ('%d'):format(client_id)
|
||||||
vim.tbl_get((client or {}).server_capabilities or {}, 'diagnosticProvider', 'identifier')
|
local name = ('nvim.lsp.%s.%d'):format(client and client.name or 'unknown', client_id)
|
||||||
local key = ('%d:%s'):format(client_id, server_id or 'nil')
|
|
||||||
local name = ('nvim.lsp.%s.%d.%s'):format(
|
|
||||||
client and client.name or 'unknown',
|
|
||||||
client_id,
|
|
||||||
server_id or 'nil'
|
|
||||||
)
|
|
||||||
local ns = client_pull_namespaces[key]
|
local ns = client_pull_namespaces[key]
|
||||||
if not ns then
|
if not ns then
|
||||||
ns = api.nvim_create_namespace(name)
|
ns = api.nvim_create_namespace(name)
|
||||||
@@ -394,10 +388,7 @@ function M.on_refresh(err, _, ctx)
|
|||||||
if client == nil then
|
if client == nil then
|
||||||
return vim.NIL
|
return vim.NIL
|
||||||
end
|
end
|
||||||
if
|
if client:supports_method('workspace/diagnostic') then
|
||||||
client.server_capabilities.diagnosticProvider
|
|
||||||
and client.server_capabilities.diagnosticProvider.workspaceDiagnostics
|
|
||||||
then
|
|
||||||
M._workspace_diagnostics({ client_id = ctx.client_id })
|
M._workspace_diagnostics({ client_id = ctx.client_id })
|
||||||
else
|
else
|
||||||
for bufnr in pairs(client.attached_buffers or {}) do
|
for bufnr in pairs(client.attached_buffers or {}) do
|
||||||
@@ -532,13 +523,16 @@ function M._workspace_diagnostics(opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
for _, client in ipairs(clients) do
|
for _, client in ipairs(clients) do
|
||||||
--- @type lsp.WorkspaceDiagnosticParams
|
local identifiers = client:_provider_value_get('workspace/diagnostic', 'identifier')
|
||||||
local params = {
|
for _, id in ipairs(identifiers) do
|
||||||
identifier = vim.tbl_get(client, 'server_capabilities', 'diagnosticProvider', 'identifier'),
|
--- @type lsp.WorkspaceDiagnosticParams
|
||||||
previousResultIds = previous_result_ids(client.id),
|
local params = {
|
||||||
}
|
identifier = type(id) == 'string' and id or nil,
|
||||||
|
previousResultIds = previous_result_ids(client.id),
|
||||||
|
}
|
||||||
|
|
||||||
client:request('workspace/diagnostic', params, handler)
|
client:request('workspace/diagnostic', params, handler)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -613,6 +613,15 @@ function protocol.make_client_capabilities()
|
|||||||
diagnostics = {
|
diagnostics = {
|
||||||
refreshSupport = true,
|
refreshSupport = true,
|
||||||
},
|
},
|
||||||
|
fileOperations = {
|
||||||
|
dynamicRegistration = false,
|
||||||
|
didCreate = false,
|
||||||
|
willCreate = false,
|
||||||
|
didRename = false,
|
||||||
|
willRename = false,
|
||||||
|
didDelete = false,
|
||||||
|
willDelete = false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
experimental = nil,
|
experimental = nil,
|
||||||
window = {
|
window = {
|
||||||
@@ -1160,79 +1169,49 @@ protocol.Methods = {
|
|||||||
-- stylua: ignore start
|
-- stylua: ignore start
|
||||||
-- Generated by gen_lsp.lua, keep at end of file.
|
-- Generated by gen_lsp.lua, keep at end of file.
|
||||||
--- Maps method names to the required client capability
|
--- Maps method names to the required client capability
|
||||||
protocol._request_name_to_client_capability = {
|
---TODO: also has workspace/* items because spec lacks a top-level "workspaceProvider"
|
||||||
['codeAction/resolve'] = { 'textDocument', 'codeAction', 'resolveSupport' },
|
protocol._provider_to_client_registration = {
|
||||||
['codeLens/resolve'] = { 'textDocument', 'codeLens', 'resolveSupport' },
|
['callHierarchyProvider'] = { 'textDocument', 'callHierarchy' },
|
||||||
['completionItem/resolve'] = { 'textDocument', 'completion', 'completionItem', 'resolveSupport' },
|
['codeActionProvider'] = { 'textDocument', 'codeAction' },
|
||||||
['documentLink/resolve'] = { 'textDocument', 'documentLink' },
|
['codeLensProvider'] = { 'textDocument', 'codeLens' },
|
||||||
['inlayHint/resolve'] = { 'textDocument', 'inlayHint', 'resolveSupport' },
|
['colorProvider'] = { 'textDocument', 'colorProvider' },
|
||||||
['textDocument/codeAction'] = { 'textDocument', 'codeAction' },
|
['completionProvider'] = { 'textDocument', 'completion' },
|
||||||
['textDocument/codeLens'] = { 'textDocument', 'codeLens' },
|
['declarationProvider'] = { 'textDocument', 'declaration' },
|
||||||
['textDocument/colorPresentation'] = { 'textDocument', 'colorProvider' },
|
['definitionProvider'] = { 'textDocument', 'definition' },
|
||||||
['textDocument/completion'] = { 'textDocument', 'completion' },
|
['diagnosticProvider'] = { 'textDocument', 'diagnostic' },
|
||||||
['textDocument/declaration'] = { 'textDocument', 'declaration' },
|
['documentFormattingProvider'] = { 'textDocument', 'formatting' },
|
||||||
['textDocument/definition'] = { 'textDocument', 'definition' },
|
['documentHighlightProvider'] = { 'textDocument', 'documentHighlight' },
|
||||||
['textDocument/diagnostic'] = { 'textDocument', 'diagnostic' },
|
['documentLinkProvider'] = { 'textDocument', 'documentLink' },
|
||||||
['textDocument/didChange'] = { 'textDocument', 'synchronization' },
|
['documentOnTypeFormattingProvider'] = { 'textDocument', 'onTypeFormatting' },
|
||||||
['textDocument/didClose'] = { 'textDocument', 'synchronization' },
|
['documentRangeFormattingProvider'] = { 'textDocument', 'rangeFormatting' },
|
||||||
['textDocument/didOpen'] = { 'textDocument', 'synchronization' },
|
['documentSymbolProvider'] = { 'textDocument', 'documentSymbol' },
|
||||||
['textDocument/didSave'] = { 'textDocument', 'synchronization', 'didSave' },
|
['executeCommandProvider'] = { 'workspace', 'executeCommand' },
|
||||||
['textDocument/documentColor'] = { 'textDocument', 'colorProvider' },
|
['foldingRangeProvider'] = { 'textDocument', 'foldingRange' },
|
||||||
['textDocument/documentHighlight'] = { 'textDocument', 'documentHighlight' },
|
['hoverProvider'] = { 'textDocument', 'hover' },
|
||||||
['textDocument/documentLink'] = { 'textDocument', 'documentLink' },
|
['implementationProvider'] = { 'textDocument', 'implementation' },
|
||||||
['textDocument/documentSymbol'] = { 'textDocument', 'documentSymbol' },
|
['inlayHintProvider'] = { 'textDocument', 'inlayHint' },
|
||||||
['textDocument/foldingRange'] = { 'textDocument', 'foldingRange' },
|
['inlineCompletionProvider'] = { 'textDocument', 'inlineCompletion' },
|
||||||
['textDocument/formatting'] = { 'textDocument', 'formatting' },
|
['inlineValueProvider'] = { 'textDocument', 'inlineValue' },
|
||||||
['textDocument/hover'] = { 'textDocument', 'hover' },
|
['linkedEditingRangeProvider'] = { 'textDocument', 'linkedEditingRange' },
|
||||||
['textDocument/implementation'] = { 'textDocument', 'implementation' },
|
['monikerProvider'] = { 'textDocument', 'moniker' },
|
||||||
['textDocument/inlayHint'] = { 'textDocument', 'inlayHint' },
|
['referencesProvider'] = { 'textDocument', 'references' },
|
||||||
['textDocument/inlineCompletion'] = { 'textDocument', 'inlineCompletion' },
|
['renameProvider'] = { 'textDocument', 'rename' },
|
||||||
['textDocument/inlineValue'] = { 'textDocument', 'inlineValue' },
|
['selectionRangeProvider'] = { 'textDocument', 'selectionRange' },
|
||||||
['textDocument/linkedEditingRange'] = { 'textDocument', 'linkedEditingRange' },
|
['semanticTokensProvider'] = { 'textDocument', 'semanticTokens' },
|
||||||
['textDocument/moniker'] = { 'textDocument', 'moniker' },
|
['signatureHelpProvider'] = { 'textDocument', 'signatureHelp' },
|
||||||
['textDocument/onTypeFormatting'] = { 'textDocument', 'onTypeFormatting' },
|
['textDocumentSync'] = { 'textDocument', 'synchronization' },
|
||||||
['textDocument/prepareCallHierarchy'] = { 'textDocument', 'callHierarchy' },
|
['typeDefinitionProvider'] = { 'textDocument', 'typeDefinition' },
|
||||||
['textDocument/prepareRename'] = { 'textDocument', 'rename', 'prepareSupport' },
|
['typeHierarchyProvider'] = { 'textDocument', 'typeHierarchy' },
|
||||||
['textDocument/prepareTypeHierarchy'] = { 'textDocument', 'typeHierarchy' },
|
|
||||||
['textDocument/publishDiagnostics'] = { 'textDocument', 'publishDiagnostics' },
|
|
||||||
['textDocument/rangeFormatting'] = { 'textDocument', 'rangeFormatting' },
|
|
||||||
['textDocument/rangesFormatting'] = { 'textDocument', 'rangeFormatting', 'rangesSupport' },
|
|
||||||
['textDocument/references'] = { 'textDocument', 'references' },
|
|
||||||
['textDocument/rename'] = { 'textDocument', 'rename' },
|
|
||||||
['textDocument/selectionRange'] = { 'textDocument', 'selectionRange' },
|
|
||||||
['textDocument/semanticTokens/full'] = { 'textDocument', 'semanticTokens' },
|
|
||||||
['textDocument/semanticTokens/full/delta'] = { 'textDocument', 'semanticTokens', 'requests', 'full', 'delta' },
|
|
||||||
['textDocument/semanticTokens/range'] = { 'textDocument', 'semanticTokens', 'requests', 'range' },
|
|
||||||
['textDocument/signatureHelp'] = { 'textDocument', 'signatureHelp' },
|
|
||||||
['textDocument/typeDefinition'] = { 'textDocument', 'typeDefinition' },
|
|
||||||
['textDocument/willSave'] = { 'textDocument', 'synchronization', 'willSave' },
|
|
||||||
['textDocument/willSaveWaitUntil'] = { 'textDocument', 'synchronization', 'willSaveWaitUntil' },
|
|
||||||
['window/showDocument'] = { 'window', 'showDocument', 'support' },
|
|
||||||
['window/showMessage'] = { 'window', 'showMessage' },
|
|
||||||
['window/showMessageRequest'] = { 'window', 'showMessage' },
|
|
||||||
['window/workDoneProgress/create'] = { 'window', 'workDoneProgress' },
|
|
||||||
['workspaceSymbol/resolve'] = { 'workspace', 'symbol', 'resolveSupport' },
|
|
||||||
['workspace/applyEdit'] = { 'workspace', 'applyEdit' },
|
|
||||||
['workspace/codeLens/refresh'] = { 'workspace', 'codeLens' },
|
|
||||||
['workspace/configuration'] = { 'workspace', 'configuration' },
|
|
||||||
['workspace/diagnostic'] = { 'workspace', 'diagnostics' },
|
|
||||||
['workspace/diagnostic/refresh'] = { 'workspace', 'diagnostics', 'refreshSupport' },
|
|
||||||
['workspace/didChangeConfiguration'] = { 'workspace', 'didChangeConfiguration' },
|
['workspace/didChangeConfiguration'] = { 'workspace', 'didChangeConfiguration' },
|
||||||
['workspace/didChangeWatchedFiles'] = { 'workspace', 'didChangeWatchedFiles' },
|
['workspace/didChangeWatchedFiles'] = { 'workspace', 'didChangeWatchedFiles' },
|
||||||
['workspace/didCreateFiles'] = { 'workspace', 'fileOperations', 'didCreate' },
|
['workspace/didCreateFiles'] = { 'workspace', 'fileOperations', 'didCreate' },
|
||||||
['workspace/didDeleteFiles'] = { 'workspace', 'fileOperations', 'didDelete' },
|
['workspace/didDeleteFiles'] = { 'workspace', 'fileOperations', 'didDelete' },
|
||||||
['workspace/didRenameFiles'] = { 'workspace', 'fileOperations', 'didRename' },
|
['workspace/didRenameFiles'] = { 'workspace', 'fileOperations', 'didRename' },
|
||||||
['workspace/executeCommand'] = { 'workspace', 'executeCommand' },
|
|
||||||
['workspace/foldingRange/refresh'] = { 'workspace', 'foldingRange', 'refreshSupport' },
|
|
||||||
['workspace/inlayHint/refresh'] = { 'workspace', 'inlayHint', 'refreshSupport' },
|
|
||||||
['workspace/inlineValue/refresh'] = { 'workspace', 'inlineValue', 'refreshSupport' },
|
|
||||||
['workspace/semanticTokens/refresh'] = { 'workspace', 'semanticTokens', 'refreshSupport' },
|
|
||||||
['workspace/symbol'] = { 'workspace', 'symbol' },
|
|
||||||
['workspace/textDocumentContent'] = { 'workspace', 'textDocumentContent' },
|
['workspace/textDocumentContent'] = { 'workspace', 'textDocumentContent' },
|
||||||
['workspace/willCreateFiles'] = { 'workspace', 'fileOperations', 'willCreate' },
|
['workspace/willCreateFiles'] = { 'workspace', 'fileOperations', 'willCreate' },
|
||||||
['workspace/willDeleteFiles'] = { 'workspace', 'fileOperations', 'willDelete' },
|
['workspace/willDeleteFiles'] = { 'workspace', 'fileOperations', 'willDelete' },
|
||||||
['workspace/willRenameFiles'] = { 'workspace', 'fileOperations', 'willRename' },
|
['workspace/willRenameFiles'] = { 'workspace', 'fileOperations', 'willRename' },
|
||||||
['workspace/workspaceFolders'] = { 'workspace', 'workspaceFolders' },
|
['workspaceSymbolProvider'] = { 'workspace', 'symbol' },
|
||||||
}
|
}
|
||||||
-- stylua: ignore end
|
-- stylua: ignore end
|
||||||
|
|
||||||
@@ -1299,13 +1278,13 @@ protocol._request_name_to_server_capability = {
|
|||||||
['workspace/willRenameFiles'] = { 'workspace', 'fileOperations', 'willRename' },
|
['workspace/willRenameFiles'] = { 'workspace', 'fileOperations', 'willRename' },
|
||||||
['workspace/workspaceFolders'] = { 'workspace', 'workspaceFolders' },
|
['workspace/workspaceFolders'] = { 'workspace', 'workspaceFolders' },
|
||||||
['textDocument/semanticTokens'] = { 'semanticTokensProvider' },
|
['textDocument/semanticTokens'] = { 'semanticTokensProvider' },
|
||||||
|
['workspace/didChangeWatchedFiles'] = { 'workspace/didChangeWatchedFiles' },
|
||||||
}
|
}
|
||||||
-- stylua: ignore end
|
-- stylua: ignore end
|
||||||
|
|
||||||
-- stylua: ignore start
|
-- stylua: ignore start
|
||||||
-- Generated by gen_lsp.lua, keep at end of file.
|
-- Generated by gen_lsp.lua, keep at end of file.
|
||||||
--- Maps method names to the required client capability
|
protocol._method_supports_dynamic_registration = {
|
||||||
protocol._request_name_allows_registration = {
|
|
||||||
['notebookDocument/didChange'] = true,
|
['notebookDocument/didChange'] = true,
|
||||||
['notebookDocument/didClose'] = true,
|
['notebookDocument/didClose'] = true,
|
||||||
['notebookDocument/didOpen'] = true,
|
['notebookDocument/didOpen'] = true,
|
||||||
@@ -1351,6 +1330,7 @@ protocol._request_name_allows_registration = {
|
|||||||
['textDocument/willSaveWaitUntil'] = true,
|
['textDocument/willSaveWaitUntil'] = true,
|
||||||
['workspace/didChangeConfiguration'] = true,
|
['workspace/didChangeConfiguration'] = true,
|
||||||
['workspace/didChangeWatchedFiles'] = true,
|
['workspace/didChangeWatchedFiles'] = true,
|
||||||
|
['workspace/didChangeWorkspaceFolders'] = true,
|
||||||
['workspace/didCreateFiles'] = true,
|
['workspace/didCreateFiles'] = true,
|
||||||
['workspace/didDeleteFiles'] = true,
|
['workspace/didDeleteFiles'] = true,
|
||||||
['workspace/didRenameFiles'] = true,
|
['workspace/didRenameFiles'] = true,
|
||||||
@@ -1363,4 +1343,51 @@ protocol._request_name_allows_registration = {
|
|||||||
}
|
}
|
||||||
-- stylua: ignore end
|
-- stylua: ignore end
|
||||||
|
|
||||||
|
-- stylua: ignore start
|
||||||
|
-- Generated by gen_lsp.lua, keep at end of file.
|
||||||
|
protocol._method_supports_static_registration = {
|
||||||
|
['textDocument/codeAction'] = true,
|
||||||
|
['textDocument/codeLens'] = true,
|
||||||
|
['textDocument/colorPresentation'] = true,
|
||||||
|
['textDocument/completion'] = true,
|
||||||
|
['textDocument/declaration'] = true,
|
||||||
|
['textDocument/definition'] = true,
|
||||||
|
['textDocument/diagnostic'] = true,
|
||||||
|
['textDocument/didChange'] = true,
|
||||||
|
['textDocument/documentColor'] = true,
|
||||||
|
['textDocument/documentHighlight'] = true,
|
||||||
|
['textDocument/documentLink'] = true,
|
||||||
|
['textDocument/documentSymbol'] = true,
|
||||||
|
['textDocument/foldingRange'] = true,
|
||||||
|
['textDocument/formatting'] = true,
|
||||||
|
['textDocument/hover'] = true,
|
||||||
|
['textDocument/implementation'] = true,
|
||||||
|
['textDocument/inlayHint'] = true,
|
||||||
|
['textDocument/inlineCompletion'] = true,
|
||||||
|
['textDocument/inlineValue'] = true,
|
||||||
|
['textDocument/linkedEditingRange'] = true,
|
||||||
|
['textDocument/moniker'] = true,
|
||||||
|
['textDocument/onTypeFormatting'] = true,
|
||||||
|
['textDocument/prepareCallHierarchy'] = true,
|
||||||
|
['textDocument/prepareTypeHierarchy'] = true,
|
||||||
|
['textDocument/rangeFormatting'] = true,
|
||||||
|
['textDocument/references'] = true,
|
||||||
|
['textDocument/rename'] = true,
|
||||||
|
['textDocument/selectionRange'] = true,
|
||||||
|
['textDocument/semanticTokens/full'] = true,
|
||||||
|
['textDocument/signatureHelp'] = true,
|
||||||
|
['textDocument/typeDefinition'] = true,
|
||||||
|
['workspace/executeCommand'] = true,
|
||||||
|
['workspace/symbol'] = true,
|
||||||
|
}
|
||||||
|
-- stylua: ignore end
|
||||||
|
|
||||||
|
-- stylua: ignore start
|
||||||
|
-- Generated by gen_lsp.lua, keep at end of file.
|
||||||
|
-- These methods have no registration options but can still be registered dynamically.
|
||||||
|
protocol._methods_with_no_registration_options = {
|
||||||
|
['workspace/didChangeWorkspaceFolders'] = true ,
|
||||||
|
}
|
||||||
|
-- stylua: ignore end
|
||||||
|
|
||||||
return protocol
|
return protocol
|
||||||
|
|||||||
@@ -224,18 +224,37 @@ local function write_to_vim_protocol(protocol)
|
|||||||
'-- stylua: ignore start',
|
'-- stylua: ignore start',
|
||||||
'-- Generated by gen_lsp.lua, keep at end of file.',
|
'-- Generated by gen_lsp.lua, keep at end of file.',
|
||||||
'--- Maps method names to the required client capability',
|
'--- Maps method names to the required client capability',
|
||||||
'protocol._request_name_to_client_capability = {',
|
'---TODO: also has workspace/* items because spec lacks a top-level "workspaceProvider"',
|
||||||
|
'protocol._provider_to_client_registration = {',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
local providers = {} --- @type table<string, string>
|
||||||
for _, item in ipairs(all) do
|
for _, item in ipairs(all) do
|
||||||
if item.clientCapability then
|
local base_provider = item.serverCapability and item.serverCapability:match('^[^%.]+')
|
||||||
output[#output + 1] = (" ['%s'] = { %s },"):format(
|
if item.registrationOptions and not providers[base_provider] and item.clientCapability then
|
||||||
item.method,
|
if item.clientCapability == item.serverCapability then
|
||||||
"'" .. item.clientCapability:gsub('%.', "', '") .. "'"
|
base_provider = nil
|
||||||
)
|
end
|
||||||
|
local key = base_provider or item.method
|
||||||
|
providers[key] = item.clientCapability
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type { provider: string, path : string }[]
|
||||||
|
local found_entries = {}
|
||||||
|
for key, value in pairs(providers) do
|
||||||
|
found_entries[#found_entries + 1] = { provider = key, path = value }
|
||||||
|
end
|
||||||
|
table.sort(found_entries, function(a, b)
|
||||||
|
return a.provider < b.provider
|
||||||
|
end)
|
||||||
|
for _, entry in ipairs(found_entries) do
|
||||||
|
output[#output + 1] = (" ['%s'] = { %s },"):format(
|
||||||
|
entry.provider,
|
||||||
|
"'" .. entry.path:gsub('%.', "', '") .. "'"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
output[#output + 1] = '}'
|
output[#output + 1] = '}'
|
||||||
output[#output + 1] = '-- stylua: ignore end'
|
output[#output + 1] = '-- stylua: ignore end'
|
||||||
|
|
||||||
@@ -280,6 +299,13 @@ local function write_to_vim_protocol(protocol)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- workspace/didChangeWatchedFiles has no server capability but we need to map it for
|
||||||
|
--- registration
|
||||||
|
output[#output + 1] = (" ['%s'] = { '%s' },"):format(
|
||||||
|
'workspace/didChangeWatchedFiles',
|
||||||
|
'workspace/didChangeWatchedFiles'
|
||||||
|
)
|
||||||
|
|
||||||
output[#output + 1] = '}'
|
output[#output + 1] = '}'
|
||||||
output[#output + 1] = '-- stylua: ignore end'
|
output[#output + 1] = '-- stylua: ignore end'
|
||||||
|
|
||||||
@@ -287,18 +313,59 @@ local function write_to_vim_protocol(protocol)
|
|||||||
'',
|
'',
|
||||||
'-- stylua: ignore start',
|
'-- stylua: ignore start',
|
||||||
'-- Generated by gen_lsp.lua, keep at end of file.',
|
'-- Generated by gen_lsp.lua, keep at end of file.',
|
||||||
'--- Maps method names to the required client capability',
|
'protocol._method_supports_dynamic_registration = {',
|
||||||
'protocol._request_name_allows_registration = {',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
--- These methods have no registrationOptions but can still be registered
|
||||||
|
--- TODO: remove if resolved upstream: https://github.com/microsoft/language-server-protocol/issues/2218
|
||||||
|
local methods_with_no_registration_options = {
|
||||||
|
['workspace/didChangeWorkspaceFolders'] = true,
|
||||||
|
}
|
||||||
|
|
||||||
for _, item in ipairs(all) do
|
for _, item in ipairs(all) do
|
||||||
if item.registrationMethod or item.registrationOptions then
|
if
|
||||||
|
item.registrationMethod
|
||||||
|
or item.registrationOptions
|
||||||
|
or methods_with_no_registration_options[item.method]
|
||||||
|
then
|
||||||
output[#output + 1] = (" ['%s'] = %s,"):format(item.method, true)
|
output[#output + 1] = (" ['%s'] = %s,"):format(item.method, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
output[#output + 1] = '}'
|
output[#output + 1] = '}'
|
||||||
output[#output + 1] = '-- stylua: ignore end'
|
output[#output + 1] = '-- stylua: ignore end'
|
||||||
|
|
||||||
|
vim.list_extend(output, {
|
||||||
|
'',
|
||||||
|
'-- stylua: ignore start',
|
||||||
|
'-- Generated by gen_lsp.lua, keep at end of file.',
|
||||||
|
'protocol._method_supports_static_registration = {',
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, item in ipairs(all) do
|
||||||
|
if
|
||||||
|
item.registrationOptions
|
||||||
|
and (item.serverCapability and not item.serverCapability:find('%.'))
|
||||||
|
then
|
||||||
|
output[#output + 1] = (" ['%s'] = %s,"):format(item.method, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
output[#output + 1] = '}'
|
||||||
|
output[#output + 1] = '-- stylua: ignore end'
|
||||||
|
|
||||||
|
vim.list_extend(output, {
|
||||||
|
'',
|
||||||
|
'-- stylua: ignore start',
|
||||||
|
'-- Generated by gen_lsp.lua, keep at end of file.',
|
||||||
|
'-- These methods have no registration options but can still be registered dynamically.',
|
||||||
|
'protocol._methods_with_no_registration_options = {',
|
||||||
|
})
|
||||||
|
for key, v in pairs(methods_with_no_registration_options) do
|
||||||
|
output[#output + 1] = (" ['%s'] = %s ,"):format(key, v)
|
||||||
|
end
|
||||||
|
output[#output + 1] = '}'
|
||||||
|
output[#output + 1] = '-- stylua: ignore end'
|
||||||
end
|
end
|
||||||
|
|
||||||
output[#output + 1] = ''
|
output[#output + 1] = ''
|
||||||
|
|||||||
@@ -774,6 +774,15 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
eq(
|
||||||
|
{ vim.NIL },
|
||||||
|
exec_lua(function()
|
||||||
|
local client = vim.lsp.get_client_by_id(client_id)
|
||||||
|
assert(client)
|
||||||
|
return client:_provider_value_get('workspace/diagnostic', 'identifier')
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
|
||||||
local requests, diags = exec_lua(function()
|
local requests, diags = exec_lua(function()
|
||||||
vim.lsp.diagnostic.on_refresh(nil, nil, {
|
vim.lsp.diagnostic.on_refresh(nil, nil, {
|
||||||
method = 'workspace/diagnostic/refresh',
|
method = 'workspace/diagnostic/refresh',
|
||||||
|
|||||||
@@ -5688,13 +5688,14 @@ describe('LSP', function()
|
|||||||
}, { client_id = client_id })
|
}, { client_id = client_id })
|
||||||
|
|
||||||
local result = {}
|
local result = {}
|
||||||
local function check(method, fname)
|
local function check(method, fname, ...)
|
||||||
local bufnr = fname and vim.fn.bufadd(fname) or nil
|
local bufnr = fname and vim.fn.bufadd(fname) or nil
|
||||||
local client = assert(vim.lsp.get_client_by_id(client_id))
|
local client = assert(vim.lsp.get_client_by_id(client_id))
|
||||||
result[#result + 1] = {
|
result[#result + 1] = {
|
||||||
method = method,
|
method = method,
|
||||||
fname = fname,
|
fname = fname,
|
||||||
supported = client:supports_method(method, { bufnr = bufnr }),
|
supported = client:supports_method(method, bufnr),
|
||||||
|
cap = select('#', ...) > 0 and client:_provider_value_get(method, ...) or nil,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -5736,6 +5737,7 @@ describe('LSP', function()
|
|||||||
id = 'diag1',
|
id = 'diag1',
|
||||||
method = 'textDocument/diagnostic',
|
method = 'textDocument/diagnostic',
|
||||||
registerOptions = {
|
registerOptions = {
|
||||||
|
identifier = 'diag-ident-1',
|
||||||
-- workspaceDiagnostics field omitted
|
-- workspaceDiagnostics field omitted
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -5744,7 +5746,7 @@ describe('LSP', function()
|
|||||||
|
|
||||||
-- Checks after registering without workspaceDiagnostics support
|
-- Checks after registering without workspaceDiagnostics support
|
||||||
-- Returns false
|
-- Returns false
|
||||||
check('workspace/diagnostic')
|
check('workspace/diagnostic', nil, 'identifier')
|
||||||
|
|
||||||
vim.lsp.handlers['client/registerCapability'](nil, {
|
vim.lsp.handlers['client/registerCapability'](nil, {
|
||||||
registrations = {
|
registrations = {
|
||||||
@@ -5752,6 +5754,7 @@ describe('LSP', function()
|
|||||||
id = 'diag2',
|
id = 'diag2',
|
||||||
method = 'textDocument/diagnostic',
|
method = 'textDocument/diagnostic',
|
||||||
registerOptions = {
|
registerOptions = {
|
||||||
|
identifier = 'diag-ident-2',
|
||||||
workspaceDiagnostics = true,
|
workspaceDiagnostics = true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -5760,7 +5763,7 @@ describe('LSP', function()
|
|||||||
|
|
||||||
-- Check after second registration with support
|
-- Check after second registration with support
|
||||||
-- Returns true
|
-- Returns true
|
||||||
check('workspace/diagnostic')
|
check('workspace/diagnostic', nil, 'identifier')
|
||||||
|
|
||||||
vim.lsp.handlers['client/unregisterCapability'](nil, {
|
vim.lsp.handlers['client/unregisterCapability'](nil, {
|
||||||
unregisterations = {
|
unregisterations = {
|
||||||
@@ -5770,7 +5773,7 @@ describe('LSP', function()
|
|||||||
|
|
||||||
-- Check after unregistering
|
-- Check after unregistering
|
||||||
-- Returns false
|
-- Returns false
|
||||||
check('workspace/diagnostic')
|
check('workspace/diagnostic', nil, 'identifier')
|
||||||
|
|
||||||
check('textDocument/codeAction')
|
check('textDocument/codeAction')
|
||||||
check('codeAction/resolve')
|
check('codeAction/resolve')
|
||||||
@@ -5790,10 +5793,21 @@ describe('LSP', function()
|
|||||||
check('textDocument/codeAction')
|
check('textDocument/codeAction')
|
||||||
check('codeAction/resolve')
|
check('codeAction/resolve')
|
||||||
|
|
||||||
|
check('workspace/didChangeWorkspaceFolders')
|
||||||
|
vim.lsp.handlers['client/registerCapability'](nil, {
|
||||||
|
registrations = {
|
||||||
|
{
|
||||||
|
id = 'didChangeWorkspaceFolders-id',
|
||||||
|
method = 'workspace/didChangeWorkspaceFolders',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, { client_id = client_id })
|
||||||
|
check('workspace/didChangeWorkspaceFolders')
|
||||||
|
|
||||||
return result
|
return result
|
||||||
end)
|
end)
|
||||||
|
|
||||||
eq(17, #result)
|
eq(19, #result)
|
||||||
eq({ method = 'textDocument/formatting', supported = false }, result[1])
|
eq({ method = 'textDocument/formatting', supported = false }, result[1])
|
||||||
eq({ method = 'textDocument/formatting', supported = true, fname = tmpfile }, result[2])
|
eq({ method = 'textDocument/formatting', supported = true, fname = tmpfile }, result[2])
|
||||||
eq({ method = 'textDocument/rangeFormatting', supported = true }, result[3])
|
eq({ method = 'textDocument/rangeFormatting', supported = true }, result[3])
|
||||||
@@ -5810,13 +5824,19 @@ describe('LSP', function()
|
|||||||
result[9]
|
result[9]
|
||||||
)
|
)
|
||||||
eq({ method = 'workspace/diagnostic', supported = false }, result[10])
|
eq({ method = 'workspace/diagnostic', supported = false }, result[10])
|
||||||
eq({ method = 'workspace/diagnostic', supported = false }, result[11])
|
eq({ method = 'workspace/diagnostic', supported = false, cap = {} }, result[11])
|
||||||
eq({ method = 'workspace/diagnostic', supported = true }, result[12])
|
eq({
|
||||||
eq({ method = 'workspace/diagnostic', supported = false }, result[13])
|
method = 'workspace/diagnostic',
|
||||||
|
supported = true,
|
||||||
|
cap = { 'diag-ident-2' },
|
||||||
|
}, result[12])
|
||||||
|
eq({ method = 'workspace/diagnostic', supported = false, cap = {} }, result[13])
|
||||||
eq({ method = 'textDocument/codeAction', supported = false }, result[14])
|
eq({ method = 'textDocument/codeAction', supported = false }, result[14])
|
||||||
eq({ method = 'codeAction/resolve', supported = false }, result[15])
|
eq({ method = 'codeAction/resolve', supported = false }, result[15])
|
||||||
eq({ method = 'textDocument/codeAction', supported = true }, result[16])
|
eq({ method = 'textDocument/codeAction', supported = true }, result[16])
|
||||||
eq({ method = 'codeAction/resolve', supported = true }, result[17])
|
eq({ method = 'codeAction/resolve', supported = true }, result[17])
|
||||||
|
eq({ method = 'workspace/didChangeWorkspaceFolders', supported = false }, result[18])
|
||||||
|
eq({ method = 'workspace/didChangeWorkspaceFolders', supported = true }, result[19])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('identifies client dynamic registration capability', function()
|
it('identifies client dynamic registration capability', function()
|
||||||
@@ -5834,6 +5854,9 @@ describe('LSP', function()
|
|||||||
synchronization = {
|
synchronization = {
|
||||||
dynamicRegistration = true,
|
dynamicRegistration = true,
|
||||||
},
|
},
|
||||||
|
diagnostic = {
|
||||||
|
dynamicRegistration = true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
@@ -5851,15 +5874,19 @@ describe('LSP', function()
|
|||||||
check('textDocument/didSave')
|
check('textDocument/didSave')
|
||||||
check('textDocument/didOpen')
|
check('textDocument/didOpen')
|
||||||
check('textDocument/codeLens')
|
check('textDocument/codeLens')
|
||||||
|
check('textDocument/diagnostic')
|
||||||
|
check('workspace/diagnostic')
|
||||||
|
|
||||||
return result
|
return result
|
||||||
end)
|
end)
|
||||||
|
|
||||||
eq(4, #result)
|
eq(6, #result)
|
||||||
eq({ method = 'textDocument/formatting', supports_reg = true }, result[1])
|
eq({ method = 'textDocument/formatting', supports_reg = true }, result[1])
|
||||||
eq({ method = 'textDocument/didSave', supports_reg = true }, result[2])
|
eq({ method = 'textDocument/didSave', supports_reg = true }, result[2])
|
||||||
eq({ method = 'textDocument/didOpen', supports_reg = true }, result[3])
|
eq({ method = 'textDocument/didOpen', supports_reg = true }, result[3])
|
||||||
eq({ method = 'textDocument/codeLens', supports_reg = false }, result[4])
|
eq({ method = 'textDocument/codeLens', supports_reg = false }, result[4])
|
||||||
|
eq({ method = 'textDocument/diagnostic', supports_reg = true }, result[5])
|
||||||
|
eq({ method = 'workspace/diagnostic', supports_reg = true }, result[6])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('supports static registration', function()
|
it('supports static registration', function()
|
||||||
@@ -5869,17 +5896,67 @@ describe('LSP', function()
|
|||||||
local server = _G._create_server({
|
local server = _G._create_server({
|
||||||
capabilities = {
|
capabilities = {
|
||||||
colorProvider = { id = 'color-registration' },
|
colorProvider = { id = 'color-registration' },
|
||||||
|
diagnosticProvider = {
|
||||||
|
id = 'diag-registration',
|
||||||
|
identifier = 'diag-ident-static',
|
||||||
|
workspaceDiagnostics = true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return assert(vim.lsp.start({ name = 'dynamic-test', cmd = server.cmd }))
|
return assert(vim.lsp.start({ name = 'dynamic-test', cmd = server.cmd }))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
local function sort_method(tbl)
|
||||||
|
local result_t = vim.deepcopy(tbl)
|
||||||
|
table.sort(result_t, function(a, b)
|
||||||
|
return (a.method or '') < (b.method or '')
|
||||||
|
end)
|
||||||
|
return result_t
|
||||||
|
end
|
||||||
|
|
||||||
eq(
|
eq(
|
||||||
true,
|
{
|
||||||
|
{
|
||||||
|
id = 'color-registration',
|
||||||
|
method = 'textDocument/colorPresentation',
|
||||||
|
registerOptions = { id = 'color-registration' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id = 'color-registration',
|
||||||
|
method = 'textDocument/documentColor',
|
||||||
|
registerOptions = { id = 'color-registration' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort_method(exec_lua(function()
|
||||||
|
local client = assert(vim.lsp.get_client_by_id(client_id))
|
||||||
|
return client.dynamic_capabilities:get('colorProvider')
|
||||||
|
end))
|
||||||
|
)
|
||||||
|
|
||||||
|
eq(
|
||||||
|
{
|
||||||
|
{
|
||||||
|
id = 'diag-registration',
|
||||||
|
method = 'textDocument/diagnostic',
|
||||||
|
registerOptions = {
|
||||||
|
id = 'diag-registration',
|
||||||
|
identifier = 'diag-ident-static',
|
||||||
|
workspaceDiagnostics = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort_method(exec_lua(function()
|
||||||
|
local client = assert(vim.lsp.get_client_by_id(client_id))
|
||||||
|
return client.dynamic_capabilities:get('diagnosticProvider')
|
||||||
|
end))
|
||||||
|
)
|
||||||
|
|
||||||
|
eq(
|
||||||
|
{ 'diag-ident-static' },
|
||||||
exec_lua(function()
|
exec_lua(function()
|
||||||
local client = assert(vim.lsp.get_client_by_id(client_id))
|
local client = assert(vim.lsp.get_client_by_id(client_id))
|
||||||
return client.dynamic_capabilities:get('colorProvider') ~= nil
|
return client:_provider_value_get('textDocument/diagnostic', 'identifier')
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user