aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/lsp.lua')
-rw-r--r--runtime/lua/vim/lsp.lua515
1 files changed, 383 insertions, 132 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 6fe1d15b7e..dacdbcfa17 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,4 +1,4 @@
-local default_callbacks = require 'vim.lsp.callbacks'
+local default_handlers = require 'vim.lsp.handlers'
local log = require 'vim.lsp.log'
local lsp_rpc = require 'vim.lsp.rpc'
local protocol = require 'vim.lsp.protocol'
@@ -13,25 +13,61 @@ local validate = vim.validate
local lsp = {
protocol = protocol;
- callbacks = default_callbacks;
+
+ -- TODO(tjdevries): Add in the warning that `callbacks` is no longer supported.
+ -- util.warn_once("vim.lsp.callbacks is deprecated. Use vim.lsp.handlers instead.")
+ handlers = default_handlers;
+ callbacks = default_handlers;
+
buf = require'vim.lsp.buf';
+ diagnostic = require'vim.lsp.diagnostic';
util = util;
+
-- Allow raw RPC access.
rpc = lsp_rpc;
+
-- Export these directly from rpc.
rpc_response_error = lsp_rpc.rpc_response_error;
- -- You probably won't need this directly, since __tostring is set for errors
- -- by the RPC.
- -- format_rpc_error = lsp_rpc.format_rpc_error;
+}
+
+-- maps request name to the required resolved_capability in the client.
+lsp._request_name_to_capability = {
+ ['textDocument/hover'] = 'hover';
+ ['textDocument/signatureHelp'] = 'signature_help';
+ ['textDocument/definition'] = 'goto_definition';
+ ['textDocument/implementation'] = 'implementation';
+ ['textDocument/declaration'] = 'declaration';
+ ['textDocument/typeDefinition'] = 'type_definition';
+ ['textDocument/documentSymbol'] = 'document_symbol';
+ ['textDocument/workspaceSymbol'] = 'workspace_symbol';
+ ['textDocument/prepareCallHierarchy'] = 'call_hierarchy';
+ ['textDocument/rename'] = 'rename';
+ ['textDocument/codeAction'] = 'code_action';
+ ['workspace/executeCommand'] = 'execute_command';
+ ['textDocument/references'] = 'find_references';
+ ['textDocument/rangeFormatting'] = 'document_range_formatting';
+ ['textDocument/formatting'] = 'document_formatting';
+ ['textDocument/completion'] = 'completion';
+ ['textDocument/documentHighlight'] = 'document_highlight';
}
-- TODO improve handling of scratch buffers with LSP attached.
+--@private
+--- Concatenates and writes a list of strings to the Vim error buffer.
+---
+--@param {...} (List of strings) List to write to the buffer
local function err_message(...)
nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
nvim_command("redraw")
end
+--@private
+--- Returns the buffer number for the given {bufnr}.
+---
+--@param bufnr (number) Buffer number to resolve. Defaults to the current
+---buffer if not given.
+--@returns bufnr (number) Number of requested buffer
local function resolve_bufnr(bufnr)
validate { bufnr = { bufnr, 'n', true } }
if bufnr == nil or bufnr == 0 then
@@ -40,6 +76,21 @@ local function resolve_bufnr(bufnr)
return bufnr
end
+--@private
+--- Called by the client when trying to call a method that's not
+--- supported in any of the servers registered for the current buffer.
+--@param method (string) name of the method
+function lsp._unsupported_method(method)
+ local msg = string.format("method %s is not supported by any of the servers registered for the current buffer", method)
+ log.warn(msg)
+ return lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound, msg)
+end
+
+--@private
+--- Checks whether a given path is a directory.
+---
+--@param filename (string) path to check
+--@returns true if {filename} exists and is a directory, false otherwise
local function is_dir(filename)
validate{filename={filename,'s'}}
local stat = uv.fs_stat(filename)
@@ -55,6 +106,10 @@ local valid_encodings = {
}
local client_index = 0
+--@private
+--- Returns a new, unused client id.
+---
+--@returns (number) client id
local function next_client_id()
client_index = client_index + 1
return client_index
@@ -64,9 +119,15 @@ local active_clients = {}
local all_buffer_active_clients = {}
local uninitialized_clients = {}
-local function for_each_buffer_client(bufnr, callback)
+--@private
+--- Invokes a function for each LSP client attached to the buffer {bufnr}.
+---
+--@param bufnr (Number) of buffer
+--@param fn (function({client}, {client_id}, {bufnr}) Function to run on
+---each client attached to that buffer.
+local function for_each_buffer_client(bufnr, fn)
validate {
- callback = { callback, 'f' };
+ fn = { fn, 'f' };
}
bufnr = resolve_bufnr(bufnr)
local client_ids = all_buffer_active_clients[bufnr]
@@ -76,7 +137,7 @@ local function for_each_buffer_client(bufnr, callback)
for client_id in pairs(client_ids) do
local client = active_clients[client_id]
if client then
- callback(client, client_id, bufnr)
+ fn(client, client_id, bufnr)
end
end
end
@@ -88,6 +149,11 @@ lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_rever
ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1;
})
+--@private
+--- Normalizes {encoding} to valid LSP encoding names.
+---
+--@param encoding (string) Encoding to normalize
+--@returns (string) normalized encoding name
local function validate_encoding(encoding)
validate {
encoding = { encoding, 's' };
@@ -96,6 +162,13 @@ local function validate_encoding(encoding)
or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding))
end
+--@internal
+--- Parses a command invocation into the command itself and its args. If there
+--- are no arguments, an empty table is returned as the second argument.
+---
+--@param input (List)
+--@returns (string) the command
+--@returns (list of strings) its arguments
function lsp._cmd_parts(input)
vim.validate{cmd={
input,
@@ -114,19 +187,36 @@ function lsp._cmd_parts(input)
return cmd, cmd_args
end
+--@private
+--- Augments a validator function with support for optional (nil) values.
+---
+--@param fn (function(v)) The original validator function; should return a
+---bool.
+--@returns (function(v)) The augmented function. Also returns true if {v} is
+---`nil`.
local function optional_validator(fn)
return function(v)
return v == nil or fn(v)
end
end
+--@private
+--- Validates a client configuration as given to |vim.lsp.start_client()|.
+---
+--@param config (table)
+--@returns (table) "Cleaned" config, containing only the command, its
+---arguments, and a valid encoding.
+---
+--@see |vim.lsp.start_client()|
local function validate_client_config(config)
validate {
config = { config, 't' };
}
validate {
root_dir = { config.root_dir, is_dir, "directory" };
+ -- TODO(remove-callbacks)
callbacks = { config.callbacks, "t", true };
+ handlers = { config.handlers, "t", true };
capabilities = { config.capabilities, "t", true };
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
cmd_env = { config.cmd_env, "t", true };
@@ -137,17 +227,32 @@ local function validate_client_config(config)
before_init = { config.before_init, "f", true };
offset_encoding = { config.offset_encoding, "s", true };
}
+
+ -- TODO(remove-callbacks)
+ if config.handlers and config.callbacks then
+ error(debug.traceback(
+ "Unable to configure LSP with both 'config.handlers' and 'config.callbacks'. Use 'config.handlers' exclusively."
+ ))
+ end
+
local cmd, cmd_args = lsp._cmd_parts(config.cmd)
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;
+ cmd = cmd;
+ cmd_args = cmd_args;
offset_encoding = offset_encoding;
}
end
+--@private
+--- Returns full text of buffer {bufnr} as a string.
+---
+--@param bufnr (number) Buffer handle, or 0 for current.
+--@returns Buffer text as string.
local function buf_get_full_text(bufnr)
local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), '\n')
if nvim_buf_get_option(bufnr, 'eol') then
@@ -156,6 +261,11 @@ local function buf_get_full_text(bufnr)
return text
end
+--@private
+--- Default handler for the 'textDocument/didOpen' LSP notification.
+---
+--@param bufnr (Number) Number of the buffer, or 0 for current
+--@param client Client object
local function text_document_did_open_handler(bufnr, client)
if not client.resolved_capabilities.text_document_open_close then
return
@@ -176,74 +286,88 @@ local function text_document_did_open_handler(bufnr, client)
util.buf_versions[bufnr] = params.textDocument.version
end
---- LSP client object.
+-- FIXME: DOC: Shouldn't need to use a dummy function
+--
+--- LSP client object. You can get an active client object via
+--- |vim.lsp.get_client_by_id()| or |vim.lsp.get_active_clients()|.
---
--- - Methods:
---
---- - request(method, params, [callback])
---- Send a request to the server. If callback is not specified, it will use
---- {client.callbacks} to try to find a callback. If one is not found there,
---- then an error will occur.
+--- - request(method, params, [handler], bufnr)
+--- Sends a request to the server.
--- This is a thin wrapper around {client.rpc.request} with some additional
--- checking.
---- Returns a boolean to indicate if the notification was successful. If it
---- is false, then it will always be false (the client has shutdown).
---- If it was successful, then it will return the request id as the second
---- result. You can use this with `notify("$/cancel", { id = request_id })`
---- to cancel the request. This helper is made automatically with
---- |vim.lsp.buf_request()|
---- Returns: status, [client_id]
+--- If {handler} is not specified, If one is not found there, then an error will occur.
+--- Returns: {status}, {[client_id]}. {status} is a boolean indicating if
+--- the notification was successful. If it is `false`, then it will always
+--- be `false` (the client has shutdown).
+--- If {status} is `true`, the function returns {request_id} as the second
+--- result. You can use this with `client.cancel_request(request_id)`
+--- to cancel the request.
---
--- - notify(method, params)
---- This is just {client.rpc.notify}()
---- Returns a boolean to indicate if the notification was successful. If it
---- is false, then it will always be false (the client has shutdown).
---- Returns: status
+--- Sends a notification to an LSP server.
+--- Returns: a boolean to indicate if the notification was successful. If
+--- it is false, then it will always be false (the client has shutdown).
---
--- - cancel_request(id)
---- This is just {client.rpc.notify}("$/cancelRequest", { id = id })
---- Returns the same as `notify()`.
+--- Cancels a request with a given request id.
+--- Returns: same as `notify()`.
---
--- - stop([force])
---- Stop a client, optionally with force.
+--- Stops a client, optionally with force.
--- By default, it will just ask the server to shutdown without force.
--- If you request to stop a client which has previously been requested to
--- shutdown, it will automatically escalate and force shutdown.
---
--- - is_stopped()
---- Returns true if the client is fully stopped.
+--- Checks whether a client is stopped.
+--- Returns: true if the client is fully stopped.
+---
+--- - on_attach(bufnr)
+--- Runs the on_attach function from the client's config if it was defined.
---
--- - Members
---- - id (number): The id allocated to the client.
+--- - {id} (number): The id allocated to the client.
---
---- - name (string): If a name is specified on creation, that will be
+--- - {name} (string): If a name is specified on creation, that will be
--- used. Otherwise it is just the client id. This is used for
--- logs and messages.
---
---- - offset_encoding (string): The encoding used for communicating
---- with the server. You can modify this in the `on_init` method
+--- - {rpc} (table): RPC client object, for low level interaction with the
+--- client. See |vim.lsp.rpc.start()|.
+---
+--- - {offset_encoding} (string): The encoding used for communicating
+--- with the server. You can modify this in the `config`'s `on_init` method
--- before text is sent to the server.
---
---- - callbacks (table): The callbacks used by the client as
---- described in |lsp-callbacks|.
+--- - {handlers} (table): The handlers used by the client as described in |lsp-handler|.
---
---- - config (table): copy of the table that was passed by the user
+--- - {config} (table): copy of the table that was passed by the user
--- to |vim.lsp.start_client()|.
---
---- - server_capabilities (table): Response from the server sent on
+--- - {server_capabilities} (table): Response from the server sent on
--- `initialize` describing the server's capabilities.
---
---- - resolved_capabilities (table): Normalized table of
+--- - {resolved_capabilities} (table): Normalized table of
--- capabilities that we have detected based on the initialize
--- response from the server in `server_capabilities`.
function lsp.client()
error()
end
+-- 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
+-- not handling method names correctly. If you change the documentation on
+-- either, please make sure to update the other as well.
+--
--- Starts and initializes a client with the given configuration.
---
--- Parameters `cmd` and `root_dir` are required.
---
+--- The following parameters describe fields in the {config} table.
+---
--@param root_dir: (required, string) Directory where the LSP server will base
--- its rootUri on initialization.
---
@@ -269,15 +393,7 @@ end
--- `{[vim.type_idx]=vim.types.dictionary}`, else it will be encoded as an
--- array.
---
---@param callbacks Map of language server method names to
---- `function(err, method, params, client_id)` handler. Invoked for:
---- - Notifications from the server, where `err` will always be `nil`.
---- - Requests initiated by the server. For these you can respond by returning
---- two values: `result, err` where err must be shaped like a RPC error,
---- i.e. `{ code, message, data? }`. Use |vim.lsp.rpc_response_error()| to
---- help with this.
---- - Default callback for client requests not explicitly specifying
---- a callback.
+--@param handlers Map of language server method names to |lsp-handler|
---
--@param init_options Values to pass in the initialization request
--- as `initializationOptions`. See `initialize` in the LSP spec.
@@ -297,7 +413,7 @@ end
--@param before_init Callback with parameters (initialize_params, config)
--- invoked before the LSP "initialize" phase, where `params` contains the
--- parameters being sent to the server and `config` is the config that was
---- passed to `start_client()`. You can use this to modify parameters before
+--- passed to |vim.lsp.start_client()|. You can use this to modify parameters before
--- they are sent.
---
--@param on_init Callback (client, initialize_result) invoked after LSP
@@ -319,9 +435,8 @@ end
--@param trace: "off" | "messages" | "verbose" | nil passed directly to the language
--- server in the initialize request. Invalid/empty values will default to "off"
---
---@returns Client id. |vim.lsp.get_client_by_id()| Note: client is only
---- available after it has been initialized, which may happen after a small
---- delay (or never if there is an error). Use `on_init` to do any actions once
+--@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be
+--- 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)
@@ -329,37 +444,62 @@ function lsp.start_client(config)
local client_id = next_client_id()
- local callbacks = config.callbacks or {}
+ -- TODO(remove-callbacks)
+ local handlers = config.handlers or config.callbacks or {}
local name = config.name or tostring(client_id)
local log_prefix = string.format("LSP[%s]", name)
- local handlers = {}
+ local dispatch = {}
- local function resolve_callback(method)
- return callbacks[method] or default_callbacks[method]
+ --@private
+ --- Returns the handler associated with an LSP method.
+ --- Returns the default handler if the user hasn't set a custom one.
+ ---
+ --@param method (string) LSP method name
+ --@returns (fn) The handler for the given method, if defined, or the default from |vim.lsp.handlers|
+ local function resolve_handler(method)
+ return handlers[method] or default_handlers[method]
end
- function handlers.notification(method, params)
+ --@private
+ --- Handles a notification sent by an LSP server by invoking the
+ --- corresponding handler.
+ ---
+ --@param method (string) LSP method name
+ --@param params (table) The parameters for that method.
+ function dispatch.notification(method, params)
local _ = log.debug() and log.debug('notification', method, params)
- local callback = resolve_callback(method)
- if callback then
+ local handler = resolve_handler(method)
+ if handler then
-- Method name is provided here for convenience.
- callback(nil, method, params, client_id)
+ handler(nil, method, params, client_id)
end
end
- function handlers.server_request(method, params)
+ --@private
+ --- Handles a request from an LSP server by invoking the corresponding handler.
+ ---
+ --@param method (string) LSP method name
+ --@param params (table) The parameters for that method
+ function dispatch.server_request(method, params)
local _ = log.debug() and log.debug('server_request', method, params)
- local callback = resolve_callback(method)
- if callback then
- local _ = log.debug() and log.debug("server_request: found callback for", method)
- return callback(nil, method, params, client_id)
+ local handler = resolve_handler(method)
+ if handler then
+ local _ = log.debug() and log.debug("server_request: found handler for", method)
+ return handler(nil, method, params, client_id)
end
- local _ = log.debug() and log.debug("server_request: no callback found for", method)
+ local _ = log.debug() and log.debug("server_request: no handler found for", method)
return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
end
- function handlers.on_error(code, err)
+ --@private
+ --- Invoked when the client operation throws an error.
+ ---
+ --@param code (number) Error code
+ --@param err (...) Other arguments may be passed depending on the error kind
+ --@see |vim.lsp.client_errors| for possible errors. Use
+ ---`vim.lsp.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 })
err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
if config.on_error then
@@ -371,7 +511,12 @@ function lsp.start_client(config)
end
end
- function handlers.on_exit(code, signal)
+ --@private
+ --- Invoked on client exit.
+ ---
+ --@param code (number) exit code of the process
+ --@param signal (number) the signal used to terminate (if any)
+ function dispatch.on_exit(code, signal)
active_clients[client_id] = nil
uninitialized_clients[client_id] = nil
local active_buffers = {}
@@ -384,7 +529,7 @@ function lsp.start_client(config)
-- Buffer level cleanup
vim.schedule(function()
for _, bufnr in ipairs(active_buffers) do
- util.buf_clear_diagnostics(bufnr)
+ lsp.diagnostic.clear(bufnr)
end
end)
if config.on_exit then
@@ -393,7 +538,7 @@ function lsp.start_client(config)
end
-- Start the RPC client.
- local rpc = lsp_rpc.start(cmd, cmd_args, handlers, {
+ local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, {
cwd = config.cmd_cwd;
env = config.cmd_env;
})
@@ -403,14 +548,17 @@ function lsp.start_client(config)
name = name;
rpc = rpc;
offset_encoding = offset_encoding;
- callbacks = callbacks;
config = config;
+
+ -- TODO(remove-callbacks)
+ callbacks = handlers;
+ handlers = handlers;
}
- -- 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;
+ --@private
local function initialize()
local valid_traces = {
off = 'off'; messages = 'messages'; verbose = 'verbose';
@@ -466,6 +614,15 @@ function lsp.start_client(config)
-- These are the cleaned up capabilities we use for dynamically deciding
-- when to send certain events to clients.
client.resolved_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
+
+ return client.resolved_capabilities[required_capability]
+ end
if config.on_init then
local status, err = pcall(config.on_init, client, result)
if not status then
@@ -488,43 +645,53 @@ function lsp.start_client(config)
end)
end
- local function unsupported_method(method)
- local msg = "server doesn't support "..method
- local _ = log.warn() and log.warn(msg)
- err_message(msg)
- return lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound, msg)
- end
-
- --- Checks capabilities before rpc.request-ing.
- function client.request(method, params, callback, bufnr)
- if not callback then
- callback = resolve_callback(method)
- or error(string.format("not found: %q request callback for client %q.", method, client.name))
- end
- local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr)
- -- TODO keep these checks or just let it go anyway?
- if (not client.resolved_capabilities.hover and method == 'textDocument/hover')
- or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp')
- or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition')
- or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation')
- or (not client.resolved_capabilities.declaration and method == 'textDocument/declaration')
- or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition')
- or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol')
- or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol')
- or (not client.resolved_capabilities.call_hierarchy and method == 'textDocument/prepareCallHierarchy')
- then
- callback(unsupported_method(method), method, nil, client_id, bufnr)
- return
+ --@private
+ --- Sends a request to the server.
+ ---
+ --- This is a thin wrapper around {client.rpc.request} with some additional
+ --- checks for capabilities and handler availability.
+ ---
+ --@param method (string) LSP method name.
+ --@param params (table) LSP request params.
+ --@param handler (function, optional) Response |lsp-handler| for this method.
+ --@param bufnr (number) Buffer handle (0 for current).
+ --@returns ({status}, [request_id]): {status} is a bool indicating
+ ---whether the request was successful. If it is `false`, then it will
+ ---always be `false` (the client has shutdown). If it was
+ ---successful, then it will return {request_id} as the
+ ---second result. You can use this with `client.cancel_request(request_id)`
+ ---to cancel the-request.
+ --@see |vim.lsp.buf_request()|
+ function client.request(method, params, handler, bufnr)
+ if not handler then
+ handler = resolve_handler(method)
+ or error(string.format("not found: %q request handler for client %q.", method, client.name))
end
+ local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr)
return rpc.request(method, params, function(err, result)
- callback(err, method, result, client_id, bufnr)
+ handler(err, method, result, client_id, bufnr)
end)
end
+ --@private
+ --- Sends a notification to an LSP server.
+ ---
+ --@param method (string) LSP method name.
+ --@param params (optional, table) LSP request params.
+ --@param bufnr (number) Buffer handle, or 0 for current.
+ --@returns {status} (bool) true if the notification was successful.
+ ---If it is false, then it will always be false
+ ---(the client has shutdown).
function client.notify(...)
return rpc.notify(...)
end
+ --@private
+ --- Cancels a request with a given request id.
+ ---
+ --@param id (number) id of request to cancel
+ --@returns true if any client returns true; false otherwise
+ --@see |vim.lsp.client.notify()|
function client.cancel_request(id)
validate{id = {id, 'n'}}
return rpc.notify("$/cancelRequest", { id = id })
@@ -533,6 +700,14 @@ function lsp.start_client(config)
-- Track this so that we can escalate automatically if we've alredy tried a
-- graceful shutdown
local tried_graceful_shutdown = false
+ --@private
+ --- Stops a client, optionally with force.
+ ---
+ ---By default, it will just ask the - server to shutdown without force. If
+ --- you request to stop a client which has previously been requested to
+ --- shutdown, it will automatically escalate and force shutdown.
+ ---
+ --@param force (bool, optional)
function client.stop(force)
local handle = rpc.handle
if handle:is_closing() then
@@ -554,10 +729,18 @@ function lsp.start_client(config)
end)
end
+ --@private
+ --- Checks whether a client is stopped.
+ ---
+ --@returns (bool) true if client is stopped or in the process of being
+ ---stopped; false otherwise
function client.is_stopped()
return rpc.handle:is_closing()
end
+ --@private
+ --- Runs the on_attach function from the client's config if it was defined.
+ --@param bufnr (number) Buffer number
function client._on_attach(bufnr)
text_document_did_open_handler(bufnr, client)
if config.on_attach then
@@ -571,6 +754,12 @@ function lsp.start_client(config)
return client_id
end
+--@private
+--- Memoizes a function. On first run, the function return value is saved and
+--- immediately returned on subsequent runs.
+---
+--@param fn (function) Function to run
+--@returns (function) Memoized function
local function once(fn)
local value
return function(...)
@@ -579,14 +768,21 @@ local function once(fn)
end
end
+--@private
+--@fn text_document_did_change_handler(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size)
+--- Notify all attached clients that a buffer has changed.
local text_document_did_change_handler
do
local encoding_index = { ["utf-8"] = 1; ["utf-16"] = 2; ["utf-32"] = 3; }
text_document_did_change_handler = function(_, bufnr, changedtick,
firstline, lastline, new_lastline, old_byte_size, old_utf32_size,
old_utf16_size)
- local _ = log.debug() and log.debug("on_lines", bufnr, changedtick, firstline,
- lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size, nvim_buf_get_lines(bufnr, firstline, new_lastline, true))
+
+ local _ = log.debug() and log.debug(
+ string.format("on_lines bufnr: %s, changedtick: %s, firstline: %s, lastline: %s, new_lastline: %s, old_byte_size: %s, old_utf32_size: %s, old_utf16_size: %s",
+ bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size),
+ nvim_buf_get_lines(bufnr, firstline, new_lastline, true)
+ )
-- Don't do anything if there are no clients attached.
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
@@ -730,14 +926,14 @@ function lsp.buf_is_attached(bufnr, client_id)
return (all_buffer_active_clients[bufnr] or {})[client_id] == true
end
---- Gets an active client by id, or nil if the id is invalid or the
---- client is not yet initialized.
----
+--- Gets a client by id, or nil if the id is invalid.
+--- The returned client may not yet be fully initialized.
+--
--@param client_id client id number
---
---@return |vim.lsp.client| object, or nil
+--@returns |vim.lsp.client| object, or nil
function lsp.get_client_by_id(client_id)
- return active_clients[client_id]
+ return active_clients[client_id] or uninitialized_clients[client_id]
end
--- Stops a client(s).
@@ -746,7 +942,7 @@ end
--- To stop all clients:
---
--- <pre>
---- vim.lsp.stop_client(lsp.get_active_clients())
+--- vim.lsp.stop_client(vim.lsp.get_active_clients())
--- </pre>
---
--- By default asks the server to shutdown, unless stop was requested
@@ -769,7 +965,7 @@ end
--- Gets all active clients.
---
---@return Table of |vim.lsp.client| objects
+--@returns Table of |vim.lsp.client| objects
function lsp.get_active_clients()
return vim.tbl_values(active_clients)
end
@@ -803,31 +999,46 @@ nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()")
--@param bufnr (number) Buffer handle, or 0 for current.
--@param method (string) LSP method name
--@param params (optional, table) Parameters to send to the server
---@param callback (optional, functionnil) Handler
--- `function(err, method, params, client_id)` for this request. Defaults
--- to the client callback in `client.callbacks`. See |lsp-callbacks|.
+--@param handler (optional, function) See |lsp-handler|
+-- If nil, follows resolution strategy defined in |lsp-handler-configuration|
--
--@returns 2-tuple:
--- - Map of client-id:request-id pairs for all successful requests.
--- - Function which can be used to cancel all the requests. You could instead
--- iterate all clients and call their `cancel_request()` methods.
-function lsp.buf_request(bufnr, method, params, callback)
+function lsp.buf_request(bufnr, method, params, handler)
validate {
bufnr = { bufnr, 'n', true };
method = { method, 's' };
- callback = { callback, 'f', true };
+ handler = { handler, 'f', true };
}
local client_request_ids = {}
- for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
- local request_success, request_id = client.request(method, params, callback, resolved_bufnr)
- -- This could only fail if the client shut down in the time since we looked
- -- it up and we did the request, which should be rare.
- if request_success then
- client_request_ids[client_id] = request_id
+ local method_supported = false
+ for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
+ if client.supports_method(method) then
+ method_supported = true
+ local request_success, request_id = client.request(method, params, handler, resolved_bufnr)
+
+ -- This could only fail if the client shut down in the time since we looked
+ -- it up and we did the request, which should be rare.
+ if request_success then
+ client_request_ids[client_id] = request_id
+ end
end
end)
+ -- if no clients support the given method, call the handler with the proper
+ -- error message.
+ if not method_supported then
+ local unsupported_err = lsp._unsupported_method(method)
+ handler = handler or lsp.handlers[method]
+ if handler then
+ handler(unsupported_err, method, bufnr)
+ end
+ return
+ end
+
local function _cancel_all_requests()
for client_id, request_id in pairs(client_request_ids) do
local client = active_clients[client_id]
@@ -856,11 +1067,11 @@ end
function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
local request_results = {}
local result_count = 0
- local function _callback(err, _method, result, client_id)
+ local function _sync_handler(err, _, result, client_id)
request_results[client_id] = { error = err, result = result }
result_count = result_count + 1
end
- local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _callback)
+ local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler)
local expected_result_count = 0
for _ in pairs(client_request_ids) do
expected_result_count = expected_result_count + 1
@@ -904,7 +1115,7 @@ end
--@param findstart 0 or 1, decides behavior
--@param base If findstart=0, text to match against
---
---@return (number) Decided by `findstart`:
+--@returns (number) Decided by `findstart`:
--- - 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)
@@ -948,6 +1159,10 @@ function lsp.omnifunc(findstart, base)
return -2
end
+---Checks whether a client is stopped.
+---
+--@param client_id (Number)
+--@returns true if client is stopped, false otherwise.
function lsp.client_is_stopped(client_id)
return active_clients[client_id] == nil
end
@@ -992,22 +1207,58 @@ function lsp.set_log_level(level)
end
--- Gets the path of the logfile used by the LSP client.
+--@returns (String) Path to logfile.
function lsp.get_log_path()
return log.get_filename()
end
--- Define the LspDiagnostics signs if they're not defined already.
-do
- local function define_default_sign(name, properties)
- if vim.tbl_isempty(vim.fn.sign_getdefined(name)) then
- vim.fn.sign_define(name, properties)
+--- Call {fn} for every client attached to {bufnr}
+function lsp.for_each_buffer_client(bufnr, fn)
+ return for_each_buffer_client(bufnr, fn)
+end
+
+--- Function to manage overriding defaults for LSP handlers.
+--@param handler (function) See |lsp-handler|
+--@param override_config (table) Table containing the keys to override behavior of the {handler}
+function lsp.with(handler, override_config)
+ return function(err, method, params, client_id, bufnr, config)
+ return handler(err, method, params, client_id, bufnr, vim.tbl_deep_extend("force", config or {}, override_config))
+ end
+end
+
+--- Helper function to use when implementing a handler.
+--- This will check that all of the keys in the user configuration
+--- 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)
+function lsp._with_extend(name, options, user_config)
+ user_config = user_config or {}
+
+ local resulting_config = {}
+ for k, v in pairs(user_config) do
+ if options[k] == nil then
+ error(debug.traceback(string.format(
+ "Invalid option for `%s`: %s. Valid options are:\n%s",
+ name,
+ k,
+ vim.inspect(vim.tbl_keys(options))
+ )))
+ end
+
+ resulting_config[k] = v
+ end
+
+ for k, v in pairs(options) do
+ if resulting_config[k] == nil then
+ resulting_config[k] = v
end
end
- define_default_sign('LspDiagnosticsErrorSign', {text='E', texthl='LspDiagnosticsErrorSign', linehl='', numhl=''})
- define_default_sign('LspDiagnosticsWarningSign', {text='W', texthl='LspDiagnosticsWarningSign', linehl='', numhl=''})
- define_default_sign('LspDiagnosticsInformationSign', {text='I', texthl='LspDiagnosticsInformationSign', linehl='', numhl=''})
- define_default_sign('LspDiagnosticsHintSign', {text='H', texthl='LspDiagnosticsHintSign', linehl='', numhl=''})
+
+ return resulting_config
end
+-- Define the LspDiagnostics signs if they're not defined already.
+require('vim.lsp.diagnostic')._define_default_signs_and_highlights()
+
return lsp
-- vim:sw=2 ts=2 et