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.lua227
1 files changed, 138 insertions, 89 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 1dc1a045fd..cfd6c938f7 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -4,8 +4,8 @@ local lsp_rpc = require('vim.lsp.rpc')
local protocol = require('vim.lsp.protocol')
local util = require('vim.lsp.util')
local sync = require('vim.lsp.sync')
+local semantic_tokens = require('vim.lsp.semantic_tokens')
-local vim = vim
local api = vim.api
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds =
api.nvim_err_writeln,
@@ -26,6 +26,7 @@ local lsp = {
buf = require('vim.lsp.buf'),
diagnostic = require('vim.lsp.diagnostic'),
codelens = require('vim.lsp.codelens'),
+ semantic_tokens = semantic_tokens,
util = util,
-- Allow raw RPC access.
@@ -57,6 +58,8 @@ lsp._request_name_to_capability = {
['textDocument/formatting'] = { 'documentFormattingProvider' },
['textDocument/completion'] = { 'completionProvider' },
['textDocument/documentHighlight'] = { 'documentHighlightProvider' },
+ ['textDocument/semanticTokens/full'] = { 'semanticTokensProvider' },
+ ['textDocument/semanticTokens/full/delta'] = { 'semanticTokensProvider' },
}
-- TODO improve handling of scratch buffers with LSP attached.
@@ -64,7 +67,7 @@ lsp._request_name_to_capability = {
---@private
--- Concatenates and writes a list of strings to the Vim error buffer.
---
----@param {...} (List of strings) List to write to the buffer
+---@param {...} table[] List to write to the buffer
local function err_message(...)
nvim_err_writeln(table.concat(vim.tbl_flatten({ ... })))
nvim_command('redraw')
@@ -73,7 +76,7 @@ end
---@private
--- Returns the buffer number for the given {bufnr}.
---
----@param bufnr (number) Buffer number to resolve. Defaults to the current
+---@param bufnr (number|nil) Buffer number to resolve. Defaults to the current
---buffer if not given.
---@returns bufnr (number) Number of requested buffer
local function resolve_bufnr(bufnr)
@@ -241,9 +244,9 @@ end
---@private
--- Augments a validator function with support for optional (nil) values.
---
----@param fn (function(v)) The original validator function; should return a
+---@param fn (fun(v)) The original validator function; should return a
---bool.
----@returns (function(v)) The augmented function. Also returns true if {v} is
+---@returns (fun(v)) The augmented function. Also returns true if {v} is
---`nil`.
local function optional_validator(fn)
return function(v)
@@ -814,8 +817,7 @@ end
--- Attaches the current buffer to the client.
---
--- Example:
----
---- <pre>
+--- <pre>lua
--- vim.lsp.start({
--- name = 'my-server-name',
--- cmd = {'name-of-language-server-executable'},
@@ -823,25 +825,18 @@ end
--- })
--- </pre>
---
---- See |lsp.start_client| for all available options. The most important are:
----
---- `name` is an arbitrary name for the LSP client. It should be unique per
---- language server.
----
---- `cmd` the command as list - used to start the language server.
---- The command must be present in the `$PATH` environment variable or an
---- absolute path to the executable. Shell constructs like `~` are *NOT* expanded.
----
---- `root_dir` path to the project root.
---- By default this is used to decide if an existing client should be re-used.
---- The example above uses |vim.fs.find| and |vim.fs.dirname| to detect the
---- root by traversing the file system upwards starting
---- from the current directory until either a `pyproject.toml` or `setup.py`
---- file is found.
+--- See |vim.lsp.start_client()| for all available options. The most important are:
---
---- `workspace_folders` a list of { uri:string, name: string } tables.
---- The project root folders used by the language server.
---- If `nil` the property is derived from the `root_dir` for convenience.
+--- - `name` arbitrary name for the LSP client. Should be unique per language server.
+--- - `cmd` command (in list form) used to start the language server. Must be absolute, or found on
+--- `$PATH`. Shell constructs like `~` are not expanded.
+--- - `root_dir` path to the project root. By default this is used to decide if an existing client
+--- should be re-used. The example above uses |vim.fs.find()| and |vim.fs.dirname()| to detect the
+--- root by traversing the file system upwards starting from the current directory until either
+--- a `pyproject.toml` or `setup.py` file is found.
+--- - `workspace_folders` list of `{ uri:string, name: string }` tables specifying the project root
+--- folders used by the language server. If `nil` the property is derived from `root_dir` for
+--- convenience.
---
--- Language servers use this information to discover metadata like the
--- dependencies of your project and they tend to index the contents within the
@@ -849,17 +844,20 @@ end
---
---
--- To ensure a language server is only started for languages it can handle,
---- make sure to call |vim.lsp.start| within a |FileType| autocmd.
+--- make sure to call |vim.lsp.start()| within a |FileType| autocmd.
--- Either use |:au|, |nvim_create_autocmd()| or put the call in a
--- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
---
----@param config table Same configuration as documented in |lsp.start_client()|
+---@param config table Same configuration as documented in |vim.lsp.start_client()|
---@param opts nil|table 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 re-uses a client if name
--- and root_dir matches.
+--- - bufnr (number)
+--- Buffer handle to attach to if starting or re-using a
+--- client (0 for current).
---@return number|nil client_id
function lsp.start(config, opts)
opts = opts or {}
@@ -871,7 +869,10 @@ function lsp.start(config, opts)
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
- local bufnr = api.nvim_get_current_buf()
+ local bufnr = opts.bufnr
+ if bufnr == nil or bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
for _, clients in ipairs({ uninitialized_clients, lsp.get_active_clients() }) do
for _, client in pairs(clients) do
if reuse_client(client, config) then
@@ -902,12 +903,12 @@ end
---
---
---@param cmd: (table|string|fun(dispatchers: table):table) command string or
---- list treated like |jobstart|. The command must launch the language server
+--- list treated like |jobstart()|. The command must launch the language server
--- process. `cmd` can also be a function that creates an RPC client.
--- The function receives a dispatchers table and must return a table with the
--- functions `request`, `notify`, `is_closing` and `terminate`
---- See |vim.lsp.rpc.request| and |vim.lsp.rpc.notify|
---- For TCP there is a built-in rpc client factory: |vim.lsp.rpc.connect|
+--- See |vim.lsp.rpc.request()| and |vim.lsp.rpc.notify()|
+--- For TCP there is a built-in rpc client factory: |vim.lsp.rpc.connect()|
---
---@param cmd_cwd: (string, default=|getcwd()|) Directory to launch
--- the `cmd` process. Not related to `root_dir`.
@@ -963,7 +964,7 @@ end
---@param on_error Callback with parameters (code, ...), invoked
--- when the client operation throws an error. `code` is a number describing
--- the error. Other arguments may be passed depending on the error kind. See
---- |vim.lsp.rpc.client_errors| for possible errors.
+--- `vim.lsp.rpc.client_errors` for possible errors.
--- Use `vim.lsp.rpc.client_errors[code]` to get human-friendly name.
---
---@param before_init Callback with parameters (initialize_params, config)
@@ -999,8 +1000,8 @@ end
--- notifications to the server by the given number in milliseconds. No debounce
--- occurs if nil
--- - exit_timeout (number|boolean, default false): Milliseconds to wait for server to
---- exit cleanly after sending the 'shutdown' request before sending kill -15.
---- If set to false, nvim exits immediately after sending the 'shutdown' request to the server.
+--- exit cleanly after sending the "shutdown" request before sending kill -15.
+--- If set to false, nvim exits immediately after sending the "shutdown" request to the server.
---
---@param root_dir string Directory where the LSP
--- server will base its workspaceFolders, rootUri, and rootPath
@@ -1078,7 +1079,7 @@ function lsp.start_client(config)
---
---@param code (number) Error code
---@param err (...) Other arguments may be passed depending on the error kind
- ---@see |vim.lsp.rpc.client_errors| for possible errors. Use
+ ---@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()
@@ -1147,33 +1148,34 @@ function lsp.start_client(config)
local namespace = vim.lsp.diagnostic.get_namespace(client_id)
vim.diagnostic.reset(namespace, bufnr)
- end)
- client_ids[client_id] = nil
- end
- if vim.tbl_isempty(client_ids) then
- vim.schedule(function()
- unset_defaults(bufnr)
+ client_ids[client_id] = nil
+ if vim.tbl_isempty(client_ids) then
+ unset_defaults(bufnr)
+ end
end)
end
end
- local client = active_clients[client_id] and active_clients[client_id]
- or uninitialized_clients[client_id]
- active_clients[client_id] = nil
- uninitialized_clients[client_id] = nil
- -- Client can be absent if executable starts, but initialize fails
- -- init/attach won't have happened
- if client then
- changetracking.reset(client)
- end
- if code ~= 0 or (signal ~= 0 and signal ~= 15) then
- local msg =
- string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal)
- vim.schedule(function()
+ -- Schedule the deletion of the client object so that it exists in the execution of LspDetach
+ -- autocommands
+ vim.schedule(function()
+ local client = active_clients[client_id] and active_clients[client_id]
+ or uninitialized_clients[client_id]
+ active_clients[client_id] = nil
+ uninitialized_clients[client_id] = nil
+
+ -- Client can be absent if executable starts, but initialize fails
+ -- init/attach won't have happened
+ if client then
+ changetracking.reset(client)
+ end
+ if code ~= 0 or (signal ~= 0 and signal ~= 15) then
+ local msg =
+ string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal)
vim.notify(msg, vim.log.levels.WARN)
- end)
- end
+ end
+ end)
end
-- Start the RPC client.
@@ -1287,8 +1289,6 @@ function lsp.start_client(config)
client.initialized = true
uninitialized_clients[client_id] = nil
client.workspace_folders = workspace_folders
- -- TODO(mjlbach): Backwards compatibility, to be removed in 0.7
- client.workspaceFolders = client.workspace_folders
-- These are the cleaned up capabilities we use for dynamically deciding
-- when to send certain events to clients.
@@ -1366,7 +1366,7 @@ function lsp.start_client(config)
---
---@param method (string) LSP method name.
---@param params (table) LSP request params.
- ---@param handler (function, optional) Response |lsp-handler| for this method.
+ ---@param handler (function|nil) 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
@@ -1377,8 +1377,10 @@ function lsp.start_client(config)
---@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))
+ handler = assert(
+ resolve_handler(method),
+ string.format('not found: %q request handler for client %q.', method, client.name)
+ )
end
-- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
changetracking.flush(client, bufnr)
@@ -1396,7 +1398,7 @@ function lsp.start_client(config)
nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
end)
- if success then
+ if success and request_id then
client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method }
nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
end
@@ -1411,8 +1413,8 @@ function lsp.start_client(config)
---
---@param method (string) LSP method name.
---@param params (table) LSP request params.
- ---@param timeout_ms (number, optional, default=1000) Maximum time in
- ---milliseconds to wait for a result.
+ ---@param timeout_ms (number|nil) Maximum time in milliseconds to wait for
+ --- a result. Defaults to 1000
---@param bufnr (number) Buffer handle (0 for current).
---@returns { err=err, result=result }, a dictionary, where `err` and `result` come from the |lsp-handler|.
---On timeout, cancel or error, returns `(nil, err)` where `err` is a
@@ -1435,7 +1437,9 @@ function lsp.start_client(config)
end, 10)
if not wait_result then
- client.cancel_request(request_id)
+ if request_id then
+ client.cancel_request(request_id)
+ end
return nil, wait_result_reason[reason]
end
return request_result
@@ -1530,6 +1534,16 @@ function lsp.start_client(config)
-- TODO(ashkan) handle errors.
pcall(config.on_attach, client, bufnr)
end
+
+ -- schedule the initialization of semantic tokens to give the above
+ -- on_attach and LspAttach callbacks the ability to schedule wrap the
+ -- opt-out (deleting the semanticTokensProvider from capabilities)
+ vim.schedule(function()
+ if vim.tbl_get(client.server_capabilities, 'semanticTokensProvider', 'full') then
+ semantic_tokens.start(bufnr, client.id)
+ end
+ end)
+
client.attached_buffers[bufnr] = true
end
@@ -1615,9 +1629,37 @@ function lsp.buf_attach_client(bufnr, client_id)
all_buffer_active_clients[bufnr] = buffer_client_ids
local uri = vim.uri_from_bufnr(bufnr)
- local augroup = ('lsp_c_%d_b_%d_did_save'):format(client_id, bufnr)
+ local augroup = ('lsp_c_%d_b_%d_save'):format(client_id, bufnr)
+ local group = api.nvim_create_augroup(augroup, { clear = true })
+ api.nvim_create_autocmd('BufWritePre', {
+ group = group,
+ buffer = bufnr,
+ desc = 'vim.lsp: textDocument/willSave',
+ callback = function(ctx)
+ for_each_buffer_client(ctx.buf, function(client)
+ local params = {
+ textDocument = {
+ uri = uri,
+ },
+ reason = protocol.TextDocumentSaveReason.Manual,
+ }
+ if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then
+ client.notify('textDocument/willSave', params)
+ end
+ if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then
+ local result, err =
+ client.request_sync('textDocument/willSaveWaitUntil', params, 1000, ctx.buf)
+ if result and result.result then
+ util.apply_text_edits(result.result, ctx.buf, client.offset_encoding)
+ elseif err then
+ log.error(vim.inspect(err))
+ end
+ end
+ end)
+ end,
+ })
api.nvim_create_autocmd('BufWritePost', {
- group = api.nvim_create_augroup(augroup, { clear = true }),
+ group = group,
buffer = bufnr,
desc = 'vim.lsp: textDocument/didSave handler',
callback = function(ctx)
@@ -1644,6 +1686,7 @@ function lsp.buf_attach_client(bufnr, client_id)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
client.notify('textDocument/didClose', params)
end
+ client.attached_buffers[bufnr] = nil
end)
util.buf_versions[bufnr] = nil
all_buffer_active_clients[bufnr] = nil
@@ -1754,16 +1797,15 @@ end
---
--- You can also use the `stop()` function on a |vim.lsp.client| object.
--- To stop all clients:
----
---- <pre>
+--- <pre>lua
--- vim.lsp.stop_client(vim.lsp.get_active_clients())
--- </pre>
---
--- By default asks the server to shutdown, unless stop was requested
--- already for this client, then force-shutdown is attempted.
---
----@param client_id client id or |vim.lsp.client| object, or list thereof
----@param force boolean (optional) shutdown forcefully
+---@param client_id number|table id or |vim.lsp.client| object, or list thereof
+---@param force boolean|nil shutdown forcefully
function lsp.stop_client(client_id, force)
local ids = type(client_id) == 'table' and client_id or { client_id }
for _, id in ipairs(ids) do
@@ -1777,10 +1819,16 @@ function lsp.stop_client(client_id, force)
end
end
+---@class vim.lsp.get_active_clients.filter
+---@field id number|nil Match clients by id
+---@field bufnr number|nil match clients attached to the given buffer
+---@field name string|nil match clients by name
+
--- Get active clients.
---
----@param filter (table|nil) A table with key-value pairs used to filter the
---- returned clients. The available keys are:
+---@param filter vim.lsp.get_active_clients.filter|nil (table|nil) A table with
+--- key-value pairs used to filter the returned clients.
+--- The available keys are:
--- - 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
@@ -1797,7 +1845,8 @@ function lsp.get_active_clients(filter)
for client_id in pairs(t) do
local client = active_clients[client_id]
if
- (filter.id == nil or client.id == filter.id)
+ client
+ and (filter.id == nil or client.id == filter.id)
and (filter.name == nil or client.name == filter.name)
then
clients[#clients + 1] = client
@@ -1928,7 +1977,7 @@ end
---
---@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 params (table|nil) Parameters to send to the server
---@param callback (function) The callback to call when all requests are finished.
-- Unlike `buf_request`, this will collect all the responses from each server instead of handling them.
-- A map of client_id:request_result will be provided to the callback
@@ -1970,9 +2019,9 @@ end
---
---@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 timeout_ms (optional, number, default=1000) Maximum time in
---- milliseconds to wait for a result.
+---@param params (table|nil) Parameters to send to the server
+---@param timeout_ms (number|nil) Maximum time in milliseconds to wait for a
+--- result. Defaults to 1000
---
---@returns Map of client_id:request_result. On timeout, cancel or error,
--- returns `(nil, err)` where `err` is a string describing the failure
@@ -1997,9 +2046,9 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
end
--- Send a notification to a server
----@param bufnr [number] (optional): The number of the buffer
----@param method [string]: Name of the request method
----@param params [string]: Arguments to send to the server
+---@param bufnr (number|nil) The number of the buffer
+---@param method (string) Name of the request method
+---@param params (any) Arguments to send to the server
---
---@returns true if any client returns true; false otherwise
function lsp.buf_notify(bufnr, method, params)
@@ -2040,8 +2089,8 @@ end
---@see |complete-items|
---@see |CompleteDone|
---
----@param findstart 0 or 1, decides behavior
----@param base If findstart=0, text to match against
+---@param findstart number 0 or 1, decides behavior
+---@param base number findstart=0, text to match against
---
---@returns (number) Decided by {findstart}:
--- - findstart=0: column where the completion starts, or -2 or -3
@@ -2170,8 +2219,8 @@ end
--- Otherwise, uses "workspace/symbol". If no results are returned from
--- any LSP servers, falls back to using built-in tags.
---
----@param pattern Pattern used to find a workspace symbol
----@param flags See |tag-function|
+---@param pattern string Pattern used to find a workspace symbol
+---@param flags string See |tag-function|
---
---@returns A list of matching tags
function lsp.tagfunc(...)
@@ -2180,7 +2229,7 @@ end
---Checks whether a client is stopped.
---
----@param client_id (Number)
+---@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
@@ -2189,7 +2238,7 @@ end
--- Gets a map of client_id:client pairs for the given buffer, where each value
--- is a |vim.lsp.client| object.
---
----@param bufnr (optional, number): Buffer handle, or 0 for current
+---@param bufnr (number|nil): Buffer handle, or 0 for current
---@returns (table) Table of (client_id, client) pairs
---@deprecated Use |vim.lsp.get_active_clients()| instead.
function lsp.buf_get_clients(bufnr)
@@ -2218,7 +2267,7 @@ lsp.log_levels = log.levels
---
---@see |vim.lsp.log_levels|
---
----@param level [number|string] the case insensitive level name or number
+---@param level (number|string) the case insensitive level name or number
function lsp.set_log_level(level)
if type(level) == 'string' or type(level) == 'number' then
log.set_level(level)
@@ -2239,7 +2288,7 @@ end
---@param fn function Function to run on each client attached to buffer
--- {bufnr}. The function takes the client, client ID, and
--- buffer number as arguments. Example:
---- <pre>
+--- <pre>lua
--- vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr)
--- print(vim.inspect(client))
--- end)