fix(lsp): reduce diagnostics and add more types (#23948)

This commit is contained in:
Lewis Russell 2023-06-07 13:39:41 +01:00 committed by GitHub
parent 0381f5af5b
commit 4ecc71f6fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 58 deletions

View File

@ -858,7 +858,7 @@ get_active_clients({filter}) *vim.lsp.get_active_clients()*
• name (string): Only return clients with the given name
Return: ~
(table) List of |vim.lsp.client| objects
lsp.Client []: List of |vim.lsp.client| objects
*vim.lsp.get_buffers_by_client_id()*
get_buffers_by_client_id({client_id})
@ -961,7 +961,7 @@ start({config}, {opts}) *vim.lsp.start()*
Parameters: ~
• {config} (table) Same configuration as documented in
|vim.lsp.start_client()|
• {opts} nil|table Optional keyword arguments:
• {opts} (nil|lsp.StartOpts) Optional keyword arguments:
• reuse_client (fun(client: client, config: table): boolean)
Predicate used to decide if a client should be re-used.
Used on all running clients. The default implementation
@ -970,7 +970,7 @@ start({config}, {opts}) *vim.lsp.start()*
re-using a client (0 for current).
Return: ~
(number|nil) client_id
(integer|nil) client_id
start_client({config}) *vim.lsp.start_client()*
Starts and initializes a client with the given configuration.
@ -978,7 +978,7 @@ start_client({config}) *vim.lsp.start_client()*
Field `cmd` in {config} is required.
Parameters: ~
• {config} (table) Configuration for the server:
• {config} ( lsp.ClientConfig ) Configuration for the server:
• cmd: (string[]|fun(dispatchers: table):table) command a
list of strings treated like |jobstart()|. The command
must launch the language server process. `cmd` can also be
@ -1108,7 +1108,7 @@ stop_client({client_id}, {force}) *vim.lsp.stop_client()*
thereof
• {force} (boolean|nil) shutdown forcefully
tagfunc({...}) *vim.lsp.tagfunc()*
tagfunc({pattern}, {flags}) *vim.lsp.tagfunc()*
Provides an interface between the built-in client and 'tagfunc'.
When used with normal mode commands (e.g. |CTRL-]|) this will invoke the
@ -2168,6 +2168,9 @@ make_client_capabilities()
Gets a new ClientCapabilities object describing the LSP client
capabilities.
Return: ~
lsp.ClientCapabilities
*vim.lsp.protocol.resolve_capabilities()*
resolve_capabilities({server_capabilities})
Creates a normalized object describing LSP server capabilities.

View File

@ -147,9 +147,9 @@ local function next_client_id()
return client_index
end
-- Tracks all clients created via lsp.start_client
local active_clients = {}
local all_buffer_active_clients = {}
local uninitialized_clients = {}
local active_clients = {} --- @type table<integer,lsp.Client>
local all_buffer_active_clients = {} --- @type table<integer,table<integer,true>>
local uninitialized_clients = {} --- @type table<integer,lsp.Client>
---@private
---@param bufnr? integer
@ -166,7 +166,7 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
end
if restrict_client_ids and #restrict_client_ids > 0 then
local filtered_client_ids = {}
local filtered_client_ids = {} --- @type table<integer,true>
for client_id in pairs(client_ids) do
if vim.list_contains(restrict_client_ids, client_id) then
filtered_client_ids[client_id] = true
@ -257,9 +257,10 @@ end
---@private
--- Validates a client configuration as given to |vim.lsp.start_client()|.
---
---@param config (table)
---@return table config Cleaned config, containing the command, its
---arguments, and a valid encoding.
---@param config (lsp.ClientConfig)
---@return (string|fun(dispatchers:table):table) Command
---@return string[] Arguments
---@return string Encoding.
local function validate_client_config(config)
validate({
config = { config, 't' },
@ -290,22 +291,19 @@ local function validate_client_config(config)
'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
)
local cmd, cmd_args
if type(config.cmd) == 'function' then
cmd = config.cmd
local cmd, cmd_args --- @type (string|fun(dispatchers:table):table), string[]
local config_cmd = config.cmd
if type(config_cmd) == 'function' then
cmd = config_cmd
else
cmd, cmd_args = lsp._cmd_parts(config.cmd)
cmd, cmd_args = lsp._cmd_parts(config_cmd)
end
local offset_encoding = valid_encodings.UTF16
if config.offset_encoding then
offset_encoding = validate_encoding(config.offset_encoding)
end
return {
cmd = cmd,
cmd_args = cmd_args,
offset_encoding = offset_encoding,
}
return cmd, cmd_args, offset_encoding
end
---@private
@ -328,10 +326,11 @@ end
--- only the first returned value will be memoized and returned. The function will only be run once,
--- even if it has side effects.
---
---@param fn (function) Function to run
---@return function fn Memoized function
---@generic T: function
---@param fn (T) Function to run
---@return T
local function once(fn)
local value
local value --- @type any
local ran = false
return function(...)
if not ran then
@ -371,7 +370,7 @@ do
--- @field lines string[] snapshot of buffer lines from last didChange
--- @field lines_tmp string[]
--- @field pending_changes table[] List of debounced changes in incremental sync mode
--- @field timer nil|uv.uv_timer_t uv_timer
--- @field timer nil|uv_timer_t uv_timer
--- @field last_flush nil|number uv.hrtime of the last flush/didChange-notification
--- @field needs_flush boolean true if buffer updates haven't been sent to clients/servers yet
--- @field refs integer how many clients are using this group
@ -610,7 +609,7 @@ do
---@private
function changetracking.send_changes(bufnr, firstline, lastline, new_lastline)
local groups = {}
local groups = {} ---@type table<string,CTGroup>
for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do
local group = get_group(client)
groups[group_key(group)] = group
@ -812,6 +811,10 @@ function lsp.client()
error()
end
--- @class lsp.StartOpts
--- @field reuse_client fun(client: lsp.Client, config: table): boolean
--- @field bufnr integer
--- Create a new LSP client and start a language server or reuses an already
--- running client if one is found matching `name` and `root_dir`.
--- Attaches the current buffer to the client.
@ -849,7 +852,7 @@ end
--- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
---
---@param config table Same configuration as documented in |vim.lsp.start_client()|
---@param opts nil|table Optional keyword arguments:
---@param opts (nil|lsp.StartOpts) Optional keyword arguments:
--- - reuse_client (fun(client: client, config: table): boolean)
--- Predicate used to decide if a client should be re-used.
--- Used on all running clients.
@ -858,14 +861,13 @@ end
--- - bufnr (number)
--- Buffer handle to attach to if starting or re-using a
--- client (0 for current).
---@return number|nil client_id
---@return integer|nil client_id
function lsp.start(config, opts)
opts = opts or {}
local reuse_client = opts.reuse_client
or function(client, conf)
return client.config.root_dir == conf.root_dir and client.name == conf.name
end
config.name = config.name
if not config.name and type(config.cmd) == 'table' then
config.name = config.cmd[1] and vim.fs.basename(config.cmd[1]) or nil
end
@ -930,6 +932,29 @@ function lsp._set_defaults(client, bufnr)
end
end
--- @class lsp.ClientConfig
--- @field cmd (string[]|fun(dispatchers: table):table)
--- @field cmd_cwd string
--- @field cmd_env (table)
--- @field detached boolean
--- @field workspace_folders (table)
--- @field capabilities lsp.ClientCapabilities
--- @field handlers table<string,function>
--- @field settings table
--- @field commands table
--- @field init_options table
--- @field name string
--- @field get_language_id fun(bufnr: integer, filetype: string): string
--- @field offset_encoding string
--- @field on_error fun(code: integer)
--- @field before_init function
--- @field on_init function
--- @field on_exit fun(code: integer, signal: integer, client_id: integer)
--- @field on_attach fun(client: lsp.Client, bufnr: integer)
--- @field trace 'off'|'messages'|'verbose'|nil
--- @field flags table
--- @field root_dir string
-- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
-- documented twice: Here, and on the methods themselves (e.g.
-- `client.request()`). This is a workaround for the vimdoc generator script
@ -940,7 +965,7 @@ end
---
--- Field `cmd` in {config} is required.
---
---@param config (table) Configuration for the server:
---@param config (lsp.ClientConfig) Configuration for the server:
--- - cmd: (string[]|fun(dispatchers: table):table) command a list of
--- strings treated like |jobstart()|. The command must launch the language server
--- process. `cmd` can also be a function that creates an RPC client.
@ -970,7 +995,7 @@ end
--- the LSP spec.
---
--- - capabilities: Map overriding the default capabilities defined by
--- |vim.lsp.protocol.make_client_capabilities()|, passed to the language
--- \|vim.lsp.protocol.make_client_capabilities()|, passed to the language
--- server on initialization. Hint: use make_client_capabilities() and modify
--- its result.
--- - Note: To send an empty dictionary use
@ -1051,9 +1076,7 @@ end
--- fully initialized. Use `on_init` to do any actions once
--- the client has been initialized.
function lsp.start_client(config)
local cleaned_config = validate_client_config(config)
local cmd, cmd_args, offset_encoding =
cleaned_config.cmd, cleaned_config.cmd_args, cleaned_config.offset_encoding
local cmd, cmd_args, offset_encoding = validate_client_config(config)
config.flags = config.flags or {}
config.settings = config.settings or {}
@ -1090,7 +1113,9 @@ function lsp.start_client(config)
---@param method (string) LSP method name
---@param params (table) The parameters for that method.
function dispatch.notification(method, params)
local _ = log.trace() and log.trace('notification', method, params)
if log.trace() then
log.trace('notification', method, params)
end
local handler = resolve_handler(method)
if handler then
-- Method name is provided here for convenience.
@ -1104,13 +1129,19 @@ function lsp.start_client(config)
---@param method (string) LSP method name
---@param params (table) The parameters for that method
function dispatch.server_request(method, params)
local _ = log.trace() and log.trace('server_request', method, params)
if log.trace() then
log.trace('server_request', method, params)
end
local handler = resolve_handler(method)
if handler then
local _ = log.trace() and log.trace('server_request: found handler for', method)
if log.trace() then
log.trace('server_request: found handler for', method)
end
return handler(nil, params, { method = method, client_id = client_id })
end
local _ = log.warn() and log.warn('server_request: no handler found for', method)
if log.warn() then
log.warn('server_request: no handler found for', method)
end
return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
end
@ -1122,8 +1153,9 @@ function lsp.start_client(config)
---@see `vim.lsp.rpc.client_errors` for possible errors. Use
---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
function dispatch.on_error(code, err)
local _ = log.error()
and log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err })
if log.error() then
log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err })
end
err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
if config.on_error then
local status, usererr = pcall(config.on_error, code, err)
@ -1232,11 +1264,13 @@ function lsp.start_client(config)
handlers = handlers,
commands = config.commands or {},
--- @type table<integer,{ type: string, bufnr: integer, method: string}>
requests = {},
-- for $/progress report
messages = { name = name, messages = {}, progress = {}, status = {} },
dynamic_capabilities = require('vim.lsp._dynamic').new(client_id),
}
--- @type lsp.ClientCapabilities
client.config.capabilities = config.capabilities or protocol.make_client_capabilities()
-- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
@ -1251,9 +1285,9 @@ function lsp.start_client(config)
}
local version = vim.version()
local workspace_folders
local root_uri
local root_path
local workspace_folders --- @type table[]?
local root_uri --- @type string?
local root_path --- @type string?
if config.workspace_folders or config.root_dir then
if config.root_dir and not config.workspace_folders then
workspace_folders = {
@ -1278,7 +1312,7 @@ function lsp.start_client(config)
-- the process has not been started by another process. If the parent
-- process is not alive then the server should exit (see exit notification)
-- its process.
processId = uv.getpid(),
processId = uv.os_getpid(),
-- Information about the client
-- since 3.15.0
clientInfo = {
@ -1405,8 +1439,9 @@ function lsp.start_client(config)
-- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
changetracking.flush(client, bufnr)
bufnr = resolve_bufnr(bufnr)
local _ = log.debug()
and log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr)
if log.debug() then
log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr)
end
local success, request_id = rpc.request(method, params, function(err, result)
handler(
err,
@ -1879,13 +1914,13 @@ end
--- - id (number): Only return clients with the given id
--- - bufnr (number): Only return clients attached to this buffer
--- - name (string): Only return clients with the given name
---@returns (table) List of |vim.lsp.client| objects
---@return lsp.Client[]: List of |vim.lsp.client| objects
function lsp.get_active_clients(filter)
validate({ filter = { filter, 't', true } })
filter = filter or {}
local clients = {}
local clients = {} --- @type lsp.Client[]
local t = filter.bufnr and (all_buffer_active_clients[resolve_bufnr(filter.bufnr)] or {})
or active_clients
@ -2143,20 +2178,20 @@ end
--- - findstart=0: column where the completion starts, or -2 or -3
--- - findstart=1: list of matches (actually just calls |complete()|)
function lsp.omnifunc(findstart, base)
local _ = log.debug() and log.debug('omnifunc.findstart', { findstart = findstart, base = base })
if log.debug() then
log.debug('omnifunc.findstart', { findstart = findstart, base = base })
end
local bufnr = resolve_bufnr()
local has_buffer_clients = not tbl_isempty(all_buffer_active_clients[bufnr] or {})
if not has_buffer_clients then
if findstart == 1 then
return -1
else
return {}
end
return findstart == 1 and -1 or {}
end
-- Then, perform standard completion request
local _ = log.info() and log.info('base ', base)
if log.info() then
log.info('base ', base)
end
local pos = api.nvim_win_get_cursor(0)
local line = api.nvim_get_current_line()
@ -2270,8 +2305,8 @@ end
---@param flags string See |tag-function|
---
---@return table[] tags A list of matching tags
function lsp.tagfunc(...)
return require('vim.lsp.tagfunc')(...)
function lsp.tagfunc(pattern, flags)
return require('vim.lsp.tagfunc')(pattern, flags)
end
---Checks whether a client is stopped.
@ -2359,10 +2394,13 @@ end
--- are valid keys and make sense to include for this handler.
---
--- Will error on invalid keys (i.e. keys that do not exist in the options)
--- @param name string
--- @param options table<string,any>
--- @param user_config table<string,any>
function lsp._with_extend(name, options, user_config)
user_config = user_config or {}
local resulting_config = {}
local resulting_config = {} --- @type table<string,any>
for k, v in pairs(user_config) do
if options[k] == nil then
error(

View File

@ -632,6 +632,7 @@ export interface WorkspaceClientCapabilities {
--- Gets a new ClientCapabilities object describing the LSP client
--- capabilities.
--- @return lsp.ClientCapabilities
function protocol.make_client_capabilities()
return {
general = {