mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
feat(lsp): initial support for dynamic capabilities (#23681)
- `client.dynamic_capabilities` is an object that tracks client register/unregister - `client.supports_method` will additionally check if a dynamic capability supports the method, taking document filters into account. But only if the client enabled `dynamicRegistration` for the capability - updated the default client capabilities to include dynamicRegistration for: - formatting - rangeFormatting - hover - codeAction - hover - rename
This commit is contained in:
parent
e41b2e34b4
commit
ddd92a70d2
@ -36,6 +36,7 @@ ADDED FEATURES *news-added*
|
|||||||
|
|
||||||
The following new APIs or features were added.
|
The following new APIs or features were added.
|
||||||
|
|
||||||
|
• Dynamic registration of LSP capabilities. An implication of this change is that checking a client's `server_capabilities` is no longer a sufficient indicator to see if a server supports a feature. Instead use `client.supports_method(<method>)`. It considers both the dynamic capabilities and static `server_capabilities`.
|
||||||
• |vim.iter()| provides a generic iterator interface for tables and Lua
|
• |vim.iter()| provides a generic iterator interface for tables and Lua
|
||||||
iterators |luaref-in|.
|
iterators |luaref-in|.
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ lsp._request_name_to_capability = {
|
|||||||
['textDocument/codeAction'] = { 'codeActionProvider' },
|
['textDocument/codeAction'] = { 'codeActionProvider' },
|
||||||
['textDocument/codeLens'] = { 'codeLensProvider' },
|
['textDocument/codeLens'] = { 'codeLensProvider' },
|
||||||
['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' },
|
['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' },
|
||||||
|
['codeAction/resolve'] = { 'codeActionProvider', 'resolveProvider' },
|
||||||
['workspace/executeCommand'] = { 'executeCommandProvider' },
|
['workspace/executeCommand'] = { 'executeCommandProvider' },
|
||||||
['workspace/symbol'] = { 'workspaceSymbolProvider' },
|
['workspace/symbol'] = { 'workspaceSymbolProvider' },
|
||||||
['textDocument/references'] = { 'referencesProvider' },
|
['textDocument/references'] = { 'referencesProvider' },
|
||||||
@ -886,6 +887,47 @@ function lsp.start(config, opts)
|
|||||||
return client_id
|
return client_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
-- Determines whether the given option can be set by `set_defaults`.
|
||||||
|
local function is_empty_or_default(bufnr, option)
|
||||||
|
if vim.bo[bufnr][option] == '' then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local info = vim.api.nvim_get_option_info2(option, { buf = bufnr })
|
||||||
|
local scriptinfo = vim.tbl_filter(function(e)
|
||||||
|
return e.sid == info.last_set_sid
|
||||||
|
end, vim.fn.getscriptinfo())
|
||||||
|
|
||||||
|
if #scriptinfo ~= 1 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return vim.startswith(scriptinfo[1].name, vim.fn.expand('$VIMRUNTIME'))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param client lsp.Client
|
||||||
|
function lsp._set_defaults(client, bufnr)
|
||||||
|
if
|
||||||
|
client.supports_method('textDocument/definition') and is_empty_or_default(bufnr, 'tagfunc')
|
||||||
|
then
|
||||||
|
vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
|
||||||
|
end
|
||||||
|
if
|
||||||
|
client.supports_method('textDocument/completion') and is_empty_or_default(bufnr, 'omnifunc')
|
||||||
|
then
|
||||||
|
vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
|
||||||
|
end
|
||||||
|
if
|
||||||
|
client.supports_method('textDocument/rangeFormatting')
|
||||||
|
and is_empty_or_default(bufnr, 'formatprg')
|
||||||
|
and is_empty_or_default(bufnr, 'formatexpr')
|
||||||
|
then
|
||||||
|
vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
|
-- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
|
||||||
-- documented twice: Here, and on the methods themselves (e.g.
|
-- documented twice: Here, and on the methods themselves (e.g.
|
||||||
-- `client.request()`). This is a workaround for the vimdoc generator script
|
-- `client.request()`). This is a workaround for the vimdoc generator script
|
||||||
@ -1090,43 +1132,6 @@ function lsp.start_client(config)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
|
||||||
-- Determines whether the given option can be set by `set_defaults`.
|
|
||||||
local function is_empty_or_default(bufnr, option)
|
|
||||||
if vim.bo[bufnr][option] == '' then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local info = vim.api.nvim_get_option_info2(option, { buf = bufnr })
|
|
||||||
local scriptinfo = vim.tbl_filter(function(e)
|
|
||||||
return e.sid == info.last_set_sid
|
|
||||||
end, vim.fn.getscriptinfo())
|
|
||||||
|
|
||||||
if #scriptinfo ~= 1 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return vim.startswith(scriptinfo[1].name, vim.fn.expand('$VIMRUNTIME'))
|
|
||||||
end
|
|
||||||
|
|
||||||
---@private
|
|
||||||
local function set_defaults(client, bufnr)
|
|
||||||
local capabilities = client.server_capabilities
|
|
||||||
if capabilities.definitionProvider and is_empty_or_default(bufnr, 'tagfunc') then
|
|
||||||
vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
|
|
||||||
end
|
|
||||||
if capabilities.completionProvider and is_empty_or_default(bufnr, 'omnifunc') then
|
|
||||||
vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
|
|
||||||
end
|
|
||||||
if
|
|
||||||
capabilities.documentRangeFormattingProvider
|
|
||||||
and is_empty_or_default(bufnr, 'formatprg')
|
|
||||||
and is_empty_or_default(bufnr, 'formatexpr')
|
|
||||||
then
|
|
||||||
vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
--- Reset defaults set by `set_defaults`.
|
--- Reset defaults set by `set_defaults`.
|
||||||
--- Must only be called if the last client attached to a buffer exits.
|
--- Must only be called if the last client attached to a buffer exits.
|
||||||
@ -1228,7 +1233,9 @@ function lsp.start_client(config)
|
|||||||
requests = {},
|
requests = {},
|
||||||
-- for $/progress report
|
-- for $/progress report
|
||||||
messages = { name = name, messages = {}, progress = {}, status = {} },
|
messages = { name = name, messages = {}, progress = {}, status = {} },
|
||||||
|
dynamic_capabilities = require('vim.lsp._dynamic').new(client_id),
|
||||||
}
|
}
|
||||||
|
client.config.capabilities = config.capabilities or protocol.make_client_capabilities()
|
||||||
|
|
||||||
-- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
|
-- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
|
||||||
uninitialized_clients[client_id] = client
|
uninitialized_clients[client_id] = client
|
||||||
@ -1291,7 +1298,7 @@ function lsp.start_client(config)
|
|||||||
-- User provided initialization options.
|
-- User provided initialization options.
|
||||||
initializationOptions = config.init_options,
|
initializationOptions = config.init_options,
|
||||||
-- The capabilities provided by the client (editor or tool)
|
-- The capabilities provided by the client (editor or tool)
|
||||||
capabilities = config.capabilities or protocol.make_client_capabilities(),
|
capabilities = config.capabilities,
|
||||||
-- The initial trace setting. If omitted trace is disabled ("off").
|
-- The initial trace setting. If omitted trace is disabled ("off").
|
||||||
-- trace = "off" | "messages" | "verbose";
|
-- trace = "off" | "messages" | "verbose";
|
||||||
trace = valid_traces[config.trace] or 'off',
|
trace = valid_traces[config.trace] or 'off',
|
||||||
@ -1300,6 +1307,26 @@ function lsp.start_client(config)
|
|||||||
-- TODO(ashkan) handle errors here.
|
-- TODO(ashkan) handle errors here.
|
||||||
pcall(config.before_init, initialize_params, config)
|
pcall(config.before_init, initialize_params, config)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- @param method string
|
||||||
|
--- @param opts? {bufnr?: number}
|
||||||
|
client.supports_method = function(method, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local required_capability = lsp._request_name_to_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(client.server_capabilities or {}, unpack(required_capability)) then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
if client.dynamic_capabilities:supports_registration(method) then
|
||||||
|
return client.dynamic_capabilities:supports(method, opts)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local _ = log.trace() and log.trace(log_prefix, 'initialize_params', initialize_params)
|
local _ = log.trace() and log.trace(log_prefix, 'initialize_params', initialize_params)
|
||||||
rpc.request('initialize', initialize_params, function(init_err, result)
|
rpc.request('initialize', initialize_params, function(init_err, result)
|
||||||
assert(not init_err, tostring(init_err))
|
assert(not init_err, tostring(init_err))
|
||||||
@ -1314,18 +1341,6 @@ function lsp.start_client(config)
|
|||||||
client.server_capabilities =
|
client.server_capabilities =
|
||||||
assert(result.capabilities, "initialize result doesn't contain capabilities")
|
assert(result.capabilities, "initialize result doesn't contain capabilities")
|
||||||
client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities)
|
client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities)
|
||||||
client.supports_method = function(method)
|
|
||||||
local required_capability = lsp._request_name_to_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(client.server_capabilities, unpack(required_capability)) then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if next(config.settings) then
|
if next(config.settings) then
|
||||||
client.notify('workspace/didChangeConfiguration', { settings = config.settings })
|
client.notify('workspace/didChangeConfiguration', { settings = config.settings })
|
||||||
@ -1522,7 +1537,7 @@ function lsp.start_client(config)
|
|||||||
function client._on_attach(bufnr)
|
function client._on_attach(bufnr)
|
||||||
text_document_did_open_handler(bufnr, client)
|
text_document_did_open_handler(bufnr, client)
|
||||||
|
|
||||||
set_defaults(client, bufnr)
|
lsp._set_defaults(client, bufnr)
|
||||||
|
|
||||||
nvim_exec_autocmds('LspAttach', {
|
nvim_exec_autocmds('LspAttach', {
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
@ -1946,7 +1961,7 @@ function lsp.buf_request(bufnr, method, params, handler)
|
|||||||
local supported_clients = {}
|
local supported_clients = {}
|
||||||
local method_supported = false
|
local method_supported = false
|
||||||
for_each_buffer_client(bufnr, function(client, client_id)
|
for_each_buffer_client(bufnr, function(client, client_id)
|
||||||
if client.supports_method(method) then
|
if client.supports_method(method, { bufnr = bufnr }) then
|
||||||
method_supported = true
|
method_supported = true
|
||||||
table.insert(supported_clients, client_id)
|
table.insert(supported_clients, client_id)
|
||||||
end
|
end
|
||||||
@ -2002,7 +2017,7 @@ function lsp.buf_request_all(bufnr, method, params, callback)
|
|||||||
|
|
||||||
local set_expected_result_count = once(function()
|
local set_expected_result_count = once(function()
|
||||||
for_each_buffer_client(bufnr, function(client)
|
for_each_buffer_client(bufnr, function(client)
|
||||||
if client.supports_method(method) then
|
if client.supports_method(method, { bufnr = bufnr }) then
|
||||||
expected_result_count = expected_result_count + 1
|
expected_result_count = expected_result_count + 1
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
109
runtime/lua/vim/lsp/_dynamic.lua
Normal file
109
runtime/lua/vim/lsp/_dynamic.lua
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
local wf = require('vim.lsp._watchfiles')
|
||||||
|
|
||||||
|
--- @class lsp.DynamicCapabilities
|
||||||
|
--- @field capabilities table<string, lsp.Registration[]>
|
||||||
|
--- @field client_id number
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- @param client_id number
|
||||||
|
function M.new(client_id)
|
||||||
|
return setmetatable({
|
||||||
|
capabilities = {},
|
||||||
|
client_id = client_id,
|
||||||
|
}, { __index = M })
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:supports_registration(method)
|
||||||
|
local client = vim.lsp.get_client_by_id(self.client_id)
|
||||||
|
if not client then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local capability = vim.tbl_get(client.config.capabilities, unpack(vim.split(method, '/')))
|
||||||
|
return type(capability) == 'table' and capability.dynamicRegistration
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param registrations lsp.Registration[]
|
||||||
|
--- @private
|
||||||
|
function M:register(registrations)
|
||||||
|
-- remove duplicates
|
||||||
|
self:unregister(registrations)
|
||||||
|
for _, reg in ipairs(registrations) do
|
||||||
|
local method = reg.method
|
||||||
|
if not self.capabilities[method] then
|
||||||
|
self.capabilities[method] = {}
|
||||||
|
end
|
||||||
|
table.insert(self.capabilities[method], reg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param unregisterations lsp.Unregistration[]
|
||||||
|
--- @private
|
||||||
|
function M:unregister(unregisterations)
|
||||||
|
for _, unreg in ipairs(unregisterations) do
|
||||||
|
local method = unreg.method
|
||||||
|
if not self.capabilities[method] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local id = unreg.id
|
||||||
|
for i, reg in ipairs(self.capabilities[method]) do
|
||||||
|
if reg.id == id then
|
||||||
|
table.remove(self.capabilities[method], i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param method string
|
||||||
|
--- @param opts? {bufnr?: number}
|
||||||
|
--- @return lsp.Registration? (table|nil) the registration if found
|
||||||
|
--- @private
|
||||||
|
function M:get(method, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf()
|
||||||
|
for _, reg in ipairs(self.capabilities[method] or {}) do
|
||||||
|
if not reg.registerOptions then
|
||||||
|
return reg
|
||||||
|
end
|
||||||
|
local documentSelector = reg.registerOptions.documentSelector
|
||||||
|
if not documentSelector then
|
||||||
|
return reg
|
||||||
|
end
|
||||||
|
if M.match(opts.bufnr, documentSelector) then
|
||||||
|
return reg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param method string
|
||||||
|
--- @param opts? {bufnr?: number}
|
||||||
|
--- @private
|
||||||
|
function M:supports(method, opts)
|
||||||
|
return self:get(method, opts) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param bufnr number
|
||||||
|
--- @param documentSelector lsp.DocumentSelector
|
||||||
|
--- @private
|
||||||
|
function M.match(bufnr, documentSelector)
|
||||||
|
local ft = vim.bo[bufnr].filetype
|
||||||
|
local uri = vim.uri_from_bufnr(bufnr)
|
||||||
|
local fname = vim.uri_to_fname(uri)
|
||||||
|
for _, filter in ipairs(documentSelector) do
|
||||||
|
local matches = true
|
||||||
|
if filter.language and ft ~= filter.language then
|
||||||
|
matches = false
|
||||||
|
end
|
||||||
|
if matches and filter.scheme and not vim.startswith(uri, filter.scheme .. ':') then
|
||||||
|
matches = false
|
||||||
|
end
|
||||||
|
if matches and filter.pattern and not wf._match(filter.pattern, fname) then
|
||||||
|
matches = false
|
||||||
|
end
|
||||||
|
if matches then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
@ -683,11 +683,7 @@ local function on_code_action_results(results, ctx, options)
|
|||||||
--
|
--
|
||||||
local client = vim.lsp.get_client_by_id(action_tuple[1])
|
local client = vim.lsp.get_client_by_id(action_tuple[1])
|
||||||
local action = action_tuple[2]
|
local action = action_tuple[2]
|
||||||
if
|
if not action.edit and client and client.supports_method('codeAction/resolve') then
|
||||||
not action.edit
|
|
||||||
and client
|
|
||||||
and vim.tbl_get(client.server_capabilities, 'codeActionProvider', 'resolveProvider')
|
|
||||||
then
|
|
||||||
client.request('codeAction/resolve', action, function(err, resolved_action)
|
client.request('codeAction/resolve', action, function(err, resolved_action)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
|
vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
|
||||||
|
@ -118,22 +118,30 @@ end
|
|||||||
|
|
||||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
|
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
|
||||||
M['client/registerCapability'] = function(_, result, ctx)
|
M['client/registerCapability'] = function(_, result, ctx)
|
||||||
local log_unsupported = false
|
local client_id = ctx.client_id
|
||||||
|
---@type lsp.Client
|
||||||
|
local client = vim.lsp.get_client_by_id(client_id)
|
||||||
|
|
||||||
|
client.dynamic_capabilities:register(result.registrations)
|
||||||
|
for bufnr, _ in ipairs(client.attached_buffers) do
|
||||||
|
vim.lsp._set_defaults(client, bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type string[]
|
||||||
|
local unsupported = {}
|
||||||
for _, reg in ipairs(result.registrations) do
|
for _, reg in ipairs(result.registrations) do
|
||||||
if reg.method == 'workspace/didChangeWatchedFiles' then
|
if reg.method == 'workspace/didChangeWatchedFiles' then
|
||||||
require('vim.lsp._watchfiles').register(reg, ctx)
|
require('vim.lsp._watchfiles').register(reg, ctx)
|
||||||
else
|
elseif not client.dynamic_capabilities:supports_registration(reg.method) then
|
||||||
log_unsupported = true
|
unsupported[#unsupported + 1] = reg.method
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if log_unsupported then
|
if #unsupported > 0 then
|
||||||
local client_id = ctx.client_id
|
|
||||||
local warning_tpl = 'The language server %s triggers a registerCapability '
|
local warning_tpl = 'The language server %s triggers a registerCapability '
|
||||||
.. 'handler despite dynamicRegistration set to false. '
|
.. 'handler for %s despite dynamicRegistration set to false. '
|
||||||
.. 'Report upstream, this warning is harmless'
|
.. 'Report upstream, this warning is harmless'
|
||||||
local client = vim.lsp.get_client_by_id(client_id)
|
|
||||||
local client_name = client and client.name or string.format('id=%d', client_id)
|
local client_name = client and client.name or string.format('id=%d', client_id)
|
||||||
local warning = string.format(warning_tpl, client_name)
|
local warning = string.format(warning_tpl, client_name, table.concat(unsupported, ', '))
|
||||||
log.warn(warning)
|
log.warn(warning)
|
||||||
end
|
end
|
||||||
return vim.NIL
|
return vim.NIL
|
||||||
@ -141,6 +149,10 @@ end
|
|||||||
|
|
||||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
|
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
|
||||||
M['client/unregisterCapability'] = function(_, result, ctx)
|
M['client/unregisterCapability'] = function(_, result, ctx)
|
||||||
|
local client_id = ctx.client_id
|
||||||
|
local client = vim.lsp.get_client_by_id(client_id)
|
||||||
|
client.dynamic_capabilities:unregister(result.unregisterations)
|
||||||
|
|
||||||
for _, unreg in ipairs(result.unregisterations) do
|
for _, unreg in ipairs(result.unregisterations) do
|
||||||
if unreg.method == 'workspace/didChangeWatchedFiles' then
|
if unreg.method == 'workspace/didChangeWatchedFiles' then
|
||||||
require('vim.lsp._watchfiles').unregister(unreg, ctx)
|
require('vim.lsp._watchfiles').unregister(unreg, ctx)
|
||||||
|
@ -697,7 +697,7 @@ function protocol.make_client_capabilities()
|
|||||||
didSave = true,
|
didSave = true,
|
||||||
},
|
},
|
||||||
codeAction = {
|
codeAction = {
|
||||||
dynamicRegistration = false,
|
dynamicRegistration = true,
|
||||||
|
|
||||||
codeActionLiteralSupport = {
|
codeActionLiteralSupport = {
|
||||||
codeActionKind = {
|
codeActionKind = {
|
||||||
@ -714,6 +714,12 @@ function protocol.make_client_capabilities()
|
|||||||
properties = { 'edit' },
|
properties = { 'edit' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
formatting = {
|
||||||
|
dynamicRegistration = true,
|
||||||
|
},
|
||||||
|
rangeFormatting = {
|
||||||
|
dynamicRegistration = true,
|
||||||
|
},
|
||||||
completion = {
|
completion = {
|
||||||
dynamicRegistration = false,
|
dynamicRegistration = false,
|
||||||
completionItem = {
|
completionItem = {
|
||||||
@ -747,6 +753,7 @@ function protocol.make_client_capabilities()
|
|||||||
},
|
},
|
||||||
definition = {
|
definition = {
|
||||||
linkSupport = true,
|
linkSupport = true,
|
||||||
|
dynamicRegistration = true,
|
||||||
},
|
},
|
||||||
implementation = {
|
implementation = {
|
||||||
linkSupport = true,
|
linkSupport = true,
|
||||||
@ -755,7 +762,7 @@ function protocol.make_client_capabilities()
|
|||||||
linkSupport = true,
|
linkSupport = true,
|
||||||
},
|
},
|
||||||
hover = {
|
hover = {
|
||||||
dynamicRegistration = false,
|
dynamicRegistration = true,
|
||||||
contentFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
|
contentFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
|
||||||
},
|
},
|
||||||
signatureHelp = {
|
signatureHelp = {
|
||||||
@ -790,7 +797,7 @@ function protocol.make_client_capabilities()
|
|||||||
hierarchicalDocumentSymbolSupport = true,
|
hierarchicalDocumentSymbolSupport = true,
|
||||||
},
|
},
|
||||||
rename = {
|
rename = {
|
||||||
dynamicRegistration = false,
|
dynamicRegistration = true,
|
||||||
prepareSupport = true,
|
prepareSupport = true,
|
||||||
},
|
},
|
||||||
publishDiagnostics = {
|
publishDiagnostics = {
|
||||||
|
@ -35,3 +35,31 @@
|
|||||||
---@field source string
|
---@field source string
|
||||||
---@field tags? lsp.DiagnosticTag[]
|
---@field tags? lsp.DiagnosticTag[]
|
||||||
---@field relatedInformation DiagnosticRelatedInformation[]
|
---@field relatedInformation DiagnosticRelatedInformation[]
|
||||||
|
|
||||||
|
--- @class lsp.DocumentFilter
|
||||||
|
--- @field language? string
|
||||||
|
--- @field scheme? string
|
||||||
|
--- @field pattern? string
|
||||||
|
|
||||||
|
--- @alias lsp.DocumentSelector lsp.DocumentFilter[]
|
||||||
|
|
||||||
|
--- @alias lsp.RegisterOptions any | lsp.StaticRegistrationOptions | lsp.TextDocumentRegistrationOptions
|
||||||
|
|
||||||
|
--- @class lsp.Registration
|
||||||
|
--- @field id string
|
||||||
|
--- @field method string
|
||||||
|
--- @field registerOptions? lsp.RegisterOptions
|
||||||
|
|
||||||
|
--- @alias lsp.RegistrationParams {registrations: lsp.Registration[]}
|
||||||
|
|
||||||
|
--- @class lsp.StaticRegistrationOptions
|
||||||
|
--- @field id? string
|
||||||
|
|
||||||
|
--- @class lsp.TextDocumentRegistrationOptions
|
||||||
|
--- @field documentSelector? lsp.DocumentSelector
|
||||||
|
|
||||||
|
--- @class lsp.Unregistration
|
||||||
|
--- @field id string
|
||||||
|
--- @field method string
|
||||||
|
|
||||||
|
--- @alias lsp.UnregistrationParams {unregisterations: lsp.Unregistration[]}
|
||||||
|
@ -3765,6 +3765,96 @@ describe('LSP', function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('#dynamic vim.lsp._dynamic', function()
|
||||||
|
it('supports dynamic registration', function()
|
||||||
|
local root_dir = helpers.tmpname()
|
||||||
|
os.remove(root_dir)
|
||||||
|
mkdir(root_dir)
|
||||||
|
local tmpfile = root_dir .. '/dynamic.foo'
|
||||||
|
local file = io.open(tmpfile, 'w')
|
||||||
|
file:close()
|
||||||
|
|
||||||
|
exec_lua(create_server_definition)
|
||||||
|
local result = exec_lua([[
|
||||||
|
local root_dir, tmpfile = ...
|
||||||
|
|
||||||
|
local server = _create_server()
|
||||||
|
local client_id = vim.lsp.start({
|
||||||
|
name = 'dynamic-test',
|
||||||
|
cmd = server.cmd,
|
||||||
|
root_dir = root_dir,
|
||||||
|
capabilities = {
|
||||||
|
textDocument = {
|
||||||
|
formatting = {
|
||||||
|
dynamicRegistration = true,
|
||||||
|
},
|
||||||
|
rangeFormatting = {
|
||||||
|
dynamicRegistration = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local expected_messages = 2 -- initialize, initialized
|
||||||
|
|
||||||
|
vim.lsp.handlers['client/registerCapability'](nil, {
|
||||||
|
registrations = {
|
||||||
|
{
|
||||||
|
id = 'formatting',
|
||||||
|
method = 'textDocument/formatting',
|
||||||
|
registerOptions = {
|
||||||
|
documentSelector = {{
|
||||||
|
pattern = root_dir .. '/*.foo',
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, { client_id = client_id })
|
||||||
|
|
||||||
|
vim.lsp.handlers['client/registerCapability'](nil, {
|
||||||
|
registrations = {
|
||||||
|
{
|
||||||
|
id = 'range-formatting',
|
||||||
|
method = 'textDocument/rangeFormatting',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, { client_id = client_id })
|
||||||
|
|
||||||
|
vim.lsp.handlers['client/registerCapability'](nil, {
|
||||||
|
registrations = {
|
||||||
|
{
|
||||||
|
id = 'completion',
|
||||||
|
method = 'textDocument/completion',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, { client_id = client_id })
|
||||||
|
|
||||||
|
local result = {}
|
||||||
|
local function check(method, fname)
|
||||||
|
local bufnr = fname and vim.fn.bufadd(fname) or nil
|
||||||
|
local client = vim.lsp.get_client_by_id(client_id)
|
||||||
|
result[#result + 1] = {method = method, fname = fname, supported = client.supports_method(method, {bufnr = bufnr})}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
check("textDocument/formatting")
|
||||||
|
check("textDocument/formatting", tmpfile)
|
||||||
|
check("textDocument/rangeFormatting")
|
||||||
|
check("textDocument/rangeFormatting", tmpfile)
|
||||||
|
check("textDocument/completion")
|
||||||
|
|
||||||
|
return result
|
||||||
|
]], root_dir, tmpfile)
|
||||||
|
|
||||||
|
eq(5, #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])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
describe('vim.lsp._watchfiles', function()
|
describe('vim.lsp._watchfiles', function()
|
||||||
it('sends notifications when files change', function()
|
it('sends notifications when files change', function()
|
||||||
local root_dir = helpers.tmpname()
|
local root_dir = helpers.tmpname()
|
||||||
|
Loading…
Reference in New Issue
Block a user