aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp.lua
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-30 20:35:25 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-30 20:35:25 +0000
commit1b7b916b7631ddf73c38e3a0070d64e4636cb2f3 (patch)
treecd08258054db80bb9a11b1061bb091c70b76926a /runtime/lua/vim/lsp.lua
parenteaa89c11d0f8aefbb512de769c6c82f61a8baca3 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-aucmd_textputpost.tar.gz
rneovim-aucmd_textputpost.tar.bz2
rneovim-aucmd_textputpost.zip
Merge remote-tracking branch 'upstream/master' into aucmd_textputpostaucmd_textputpost
Diffstat (limited to 'runtime/lua/vim/lsp.lua')
-rw-r--r--runtime/lua/vim/lsp.lua1063
1 files changed, 597 insertions, 466 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index c5392ac154..261a3aa5de 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,19 +1,17 @@
+---@diagnostic disable: invisible
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')
+local ms = protocol.Methods
local util = require('vim.lsp.util')
local sync = require('vim.lsp.sync')
local semantic_tokens = require('vim.lsp.semantic_tokens')
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,
- api.nvim_buf_get_lines,
- api.nvim_command,
- api.nvim_buf_get_option,
- api.nvim_exec_autocmds
-local uv = vim.loop
+local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_exec_autocmds =
+ api.nvim_err_writeln, api.nvim_buf_get_lines, api.nvim_command, api.nvim_exec_autocmds
+local uv = vim.uv
local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
local validate = vim.validate
local if_nil = vim.F.if_nil
@@ -26,6 +24,7 @@ local lsp = {
buf = require('vim.lsp.buf'),
diagnostic = require('vim.lsp.diagnostic'),
codelens = require('vim.lsp.codelens'),
+ inlay_hint = require('vim.lsp.inlay_hint'),
semantic_tokens = semantic_tokens,
util = util,
@@ -38,47 +37,50 @@ local lsp = {
-- maps request name to the required server_capability in the client.
lsp._request_name_to_capability = {
- ['textDocument/hover'] = { 'hoverProvider' },
- ['textDocument/signatureHelp'] = { 'signatureHelpProvider' },
- ['textDocument/definition'] = { 'definitionProvider' },
- ['textDocument/implementation'] = { 'implementationProvider' },
- ['textDocument/declaration'] = { 'declarationProvider' },
- ['textDocument/typeDefinition'] = { 'typeDefinitionProvider' },
- ['textDocument/documentSymbol'] = { 'documentSymbolProvider' },
- ['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' },
- ['textDocument/rename'] = { 'renameProvider' },
- ['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider' },
- ['textDocument/codeAction'] = { 'codeActionProvider' },
- ['textDocument/codeLens'] = { 'codeLensProvider' },
- ['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' },
- ['workspace/executeCommand'] = { 'executeCommandProvider' },
- ['workspace/symbol'] = { 'workspaceSymbolProvider' },
- ['textDocument/references'] = { 'referencesProvider' },
- ['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' },
- ['textDocument/formatting'] = { 'documentFormattingProvider' },
- ['textDocument/completion'] = { 'completionProvider' },
- ['textDocument/documentHighlight'] = { 'documentHighlightProvider' },
- ['textDocument/semanticTokens/full'] = { 'semanticTokensProvider' },
- ['textDocument/semanticTokens/full/delta'] = { 'semanticTokensProvider' },
+ [ms.textDocument_hover] = { 'hoverProvider' },
+ [ms.textDocument_signatureHelp] = { 'signatureHelpProvider' },
+ [ms.textDocument_definition] = { 'definitionProvider' },
+ [ms.textDocument_implementation] = { 'implementationProvider' },
+ [ms.textDocument_declaration] = { 'declarationProvider' },
+ [ms.textDocument_typeDefinition] = { 'typeDefinitionProvider' },
+ [ms.textDocument_documentSymbol] = { 'documentSymbolProvider' },
+ [ms.textDocument_prepareCallHierarchy] = { 'callHierarchyProvider' },
+ [ms.callHierarchy_incomingCalls] = { 'callHierarchyProvider' },
+ [ms.callHierarchy_outgoingCalls] = { 'callHierarchyProvider' },
+ [ms.textDocument_rename] = { 'renameProvider' },
+ [ms.textDocument_prepareRename] = { 'renameProvider', 'prepareProvider' },
+ [ms.textDocument_codeAction] = { 'codeActionProvider' },
+ [ms.textDocument_codeLens] = { 'codeLensProvider' },
+ [ms.codeLens_resolve] = { 'codeLensProvider', 'resolveProvider' },
+ [ms.codeAction_resolve] = { 'codeActionProvider', 'resolveProvider' },
+ [ms.workspace_executeCommand] = { 'executeCommandProvider' },
+ [ms.workspace_symbol] = { 'workspaceSymbolProvider' },
+ [ms.textDocument_references] = { 'referencesProvider' },
+ [ms.textDocument_rangeFormatting] = { 'documentRangeFormattingProvider' },
+ [ms.textDocument_formatting] = { 'documentFormattingProvider' },
+ [ms.textDocument_completion] = { 'completionProvider' },
+ [ms.textDocument_documentHighlight] = { 'documentHighlightProvider' },
+ [ms.textDocument_semanticTokens_full] = { 'semanticTokensProvider' },
+ [ms.textDocument_semanticTokens_full_delta] = { 'semanticTokensProvider' },
+ [ms.textDocument_inlayHint] = { 'inlayHintProvider' },
+ [ms.textDocument_diagnostic] = { 'diagnosticProvider' },
+ [ms.inlayHint_resolve] = { 'inlayHintProvider', 'resolveProvider' },
}
-- TODO improve handling of scratch buffers with LSP attached.
----@private
--- Concatenates and writes a list of strings to the Vim error buffer.
---
----@param {...} table[] List to write to the buffer
+---@param ... string 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|nil) Buffer number to resolve. Defaults to the current
----buffer if not given.
----@returns bufnr (number) Number of requested buffer
+---@param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer
+---@return integer bufnr
local function resolve_bufnr(bufnr)
validate({ bufnr = { bufnr, 'n', true } })
if bufnr == nil or bufnr == 0 then
@@ -100,11 +102,10 @@ function lsp._unsupported_method(method)
return 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
+---@return boolean # 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)
@@ -131,28 +132,27 @@ local format_line_ending = {
['mac'] = '\r',
}
----@private
---@param bufnr (number)
----@returns (string)
+---@return string
local function buf_get_line_ending(bufnr)
- return format_line_ending[nvim_buf_get_option(bufnr, 'fileformat')] or '\n'
+ return format_line_ending[vim.bo[bufnr].fileformat] or '\n'
end
local client_index = 0
----@private
--- Returns a new, unused client id.
---
----@returns (number) client id
+---@return integer client_id
local function next_client_id()
client_index = client_index + 1
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
+---@param fn fun(client: lsp.Client, client_id: integer, bufnr: integer)
local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
validate({
fn = { fn, 'f' },
@@ -165,9 +165,9 @@ 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.tbl_contains(restrict_client_ids, client_id) then
+ if vim.list_contains(restrict_client_ids, client_id) then
filtered_client_ids[client_id] = true
end
end
@@ -182,22 +182,24 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
end
end
--- Error codes to be used with `on_error` from |vim.lsp.start_client|.
--- Can be used to look up the string from a the number or the number
--- from the string.
+--- Error codes to be used with `on_error` from |vim.lsp.start_client|.
+--- Can be used to look up the string from a the number or the number
+--- from the string.
+--- @nodoc
lsp.client_errors = tbl_extend(
'error',
lsp_rpc.client_errors,
vim.tbl_add_reverse_lookup({
- ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1,
+ BEFORE_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1,
+ ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 2,
+ ON_ATTACH_ERROR = table.maxn(lsp_rpc.client_errors) + 3,
})
)
----@private
--- Normalizes {encoding} to valid LSP encoding names.
---
---@param encoding (string) Encoding to normalize
----@returns (string) normalized encoding name
+---@return string # normalized encoding name
local function validate_encoding(encoding)
validate({
encoding = { encoding, 's' },
@@ -215,9 +217,8 @@ end
--- 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
+---@param input string[]
+---@return string command, string[] args #the command and arguments
function lsp._cmd_parts(input)
validate({
cmd = {
@@ -241,12 +242,11 @@ function lsp._cmd_parts(input)
return cmd, cmd_args
end
----@private
--- Augments a validator function with support for optional (nil) values.
---
----@param fn (fun(v)) The original validator function; should return a
+---@param fn (fun(v): boolean) The original validator function; should return a
---bool.
----@returns (fun(v)) The augmented function. Also returns true if {v} is
+---@return fun(v): boolean # The augmented function. Also returns true if {v} is
---`nil`.
local function optional_validator(fn)
return function(v)
@@ -254,14 +254,12 @@ local function optional_validator(fn)
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()|
+---@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' },
@@ -292,48 +290,44 @@ 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
--- Returns full text of buffer {bufnr} as a string.
---
---@param bufnr (number) Buffer handle, or 0 for current.
----@returns Buffer text as string.
+---@return string # Buffer text as string.
local function buf_get_full_text(bufnr)
local line_ending = buf_get_line_ending(bufnr)
local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending)
- if nvim_buf_get_option(bufnr, 'eol') then
+ if vim.bo[bufnr].eol then
text = text .. line_ending
end
return text
end
----@private
--- Memoizes a function. On first run, the function return value is saved and
--- immediately returned on subsequent runs. If the function returns a multival,
--- 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
----@returns (function) 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
@@ -365,7 +359,7 @@ do
--- smallest debounce interval is used and we don't group clients by different intervals.
---
--- @class CTGroup
- --- @field sync_kind number TextDocumentSyncKind, considers config.flags.allow_incremental_sync
+ --- @field sync_kind integer TextDocumentSyncKind, considers config.flags.allow_incremental_sync
--- @field offset_encoding "utf-8"|"utf-16"|"utf-32"
---
--- @class CTBufferState
@@ -373,17 +367,16 @@ 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|userdata uv_timer
+ --- @field timer nil|uv.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 number how many clients are using this group
+ --- @field refs integer how many clients are using this group
---
--- @class CTGroupState
- --- @field buffers table<number, CTBufferState>
- --- @field debounce number debounce duration in ms
- --- @field clients table<number, table> clients using this state. {client_id, client}
+ --- @field buffers table<integer, CTBufferState>
+ --- @field debounce integer debounce duration in ms
+ --- @field clients table<integer, table> clients using this state. {client_id, client}
- ---@private
---@param group CTGroup
---@return string
local function group_key(group)
@@ -404,12 +397,10 @@ do
end,
})
- ---@private
---@return CTGroup
local function get_group(client)
local allow_inc_sync = if_nil(client.config.flags.allow_incremental_sync, true)
- local change_capability =
- vim.tbl_get(client.server_capabilities or {}, 'textDocumentSync', 'change')
+ local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
local sync_kind = change_capability or protocol.TextDocumentSyncKind.None
if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then
sync_kind = protocol.TextDocumentSyncKind.Full
@@ -420,7 +411,6 @@ do
}
end
- ---@private
---@param state CTBufferState
local function incremental_changes(state, encoding, bufnr, firstline, lastline, new_lastline)
local prev_lines = state.lines
@@ -544,15 +534,13 @@ do
end
end
- ---@private
- --
-- Adjust debounce time by taking time of last didChange notification into
-- consideration. If the last didChange happened more than `debounce` time ago,
-- debounce can be skipped and otherwise maybe reduced.
--
-- This turns the debounce into a kind of client rate limiting
--
- ---@param debounce number
+ ---@param debounce integer
---@param buf_state CTBufferState
---@return number
local function next_debounce(debounce, buf_state)
@@ -568,9 +556,8 @@ do
return math.max(debounce - ms_since_last_flush, 0)
end
- ---@private
- ---@param bufnr number
- ---@param sync_kind number protocol.TextDocumentSyncKind
+ ---@param bufnr integer
+ ---@param sync_kind integer protocol.TextDocumentSyncKind
---@param state CTGroupState
---@param buf_state CTBufferState
local function send_changes(bufnr, sync_kind, state, buf_state)
@@ -599,7 +586,7 @@ do
local uri = vim.uri_from_bufnr(bufnr)
for _, client in pairs(state.clients) do
if not client.is_stopped() and lsp.buf_is_attached(bufnr, client.id) then
- client.notify('textDocument/didChange', {
+ client.notify(ms.textDocument_didChange, {
textDocument = {
uri = uri,
version = util.buf_versions[bufnr],
@@ -612,8 +599,8 @@ do
---@private
function changetracking.send_changes(bufnr, firstline, lastline, new_lastline)
- local groups = {}
- for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do
+ local groups = {} ---@type table<string,CTGroup>
+ for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
local group = get_group(client)
groups[group_key(group)] = group
end
@@ -648,7 +635,7 @@ do
if debounce == 0 then
send_changes(bufnr, group.sync_kind, state, buf_state)
else
- local timer = uv.new_timer()
+ local timer = assert(uv.new_timer(), 'Must be able to create timer')
buf_state.timer = timer
timer:start(
debounce,
@@ -695,10 +682,9 @@ do
end
end
----@private
--- Default handler for the 'textDocument/didOpen' LSP notification.
---
----@param bufnr number Number of the buffer, or 0 for current
+---@param bufnr integer Number of the buffer, or 0 for current
---@param client table Client object
local function text_document_did_open_handler(bufnr, client)
changetracking.init(client, bufnr)
@@ -708,7 +694,7 @@ local function text_document_did_open_handler(bufnr, client)
if not api.nvim_buf_is_loaded(bufnr) then
return
end
- local filetype = nvim_buf_get_option(bufnr, 'filetype')
+ local filetype = vim.bo[bufnr].filetype
local params = {
textDocument = {
@@ -718,7 +704,7 @@ local function text_document_did_open_handler(bufnr, client)
text = buf_get_full_text(bufnr),
},
}
- client.notify('textDocument/didOpen', params)
+ client.notify(ms.textDocument_didOpen, params)
util.buf_versions[bufnr] = params.textDocument.version
-- Next chance we get, we should re-do the diagnostics
@@ -735,7 +721,7 @@ end
-- 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()|.
+--- |vim.lsp.get_client_by_id()| or |vim.lsp.get_clients()|.
---
--- - Methods:
---
@@ -801,29 +787,39 @@ end
--- to the server. Entries are key-value pairs with the key
--- being the request ID while the value is a table with `type`,
--- `bufnr`, and `method` key-value pairs. `type` is either "pending"
---- for an active request, or "cancel" for a cancel request.
+--- for an active request, or "cancel" for a cancel request. It will
+--- be "complete" ephemerally while executing |LspRequest| autocmds
+--- when replies are received from the server.
---
--- - {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
--- `initialize` describing the server's capabilities.
+---
+--- - {progress} A ring buffer (|vim.ringbuf()|) containing progress messages
+--- sent by the server.
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.
---
--- Example:
---- <pre>lua
+---
+--- ```lua
--- vim.lsp.start({
--- name = 'my-server-name',
--- cmd = {'name-of-language-server-executable'},
--- root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
--- })
---- </pre>
+--- ```
---
--- See |vim.lsp.start_client()| for all available options. The most important are:
---
@@ -849,7 +845,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 +854,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
@@ -873,7 +868,7 @@ function lsp.start(config, opts)
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 _, clients in ipairs({ uninitialized_clients, lsp.get_clients() }) do
for _, client in pairs(clients) do
if reuse_client(client, config) then
lsp.buf_attach_client(bufnr, client.id)
@@ -889,6 +884,109 @@ function lsp.start(config, opts)
return client_id
end
+--- Consumes the latest progress messages from all clients and formats them as a string.
+--- Empty if there are no clients or if no new messages
+---
+---@return string
+function lsp.status()
+ local percentage = nil
+ local messages = {}
+ for _, client in ipairs(vim.lsp.get_clients()) do
+ for progress in client.progress do
+ local value = progress.value
+ if type(value) == 'table' and value.kind then
+ local message = value.message and (value.title .. ': ' .. value.message) or value.title
+ messages[#messages + 1] = message
+ if value.percentage then
+ percentage = math.max(percentage or 0, value.percentage)
+ end
+ end
+ -- else: Doesn't look like work done progress and can be in any format
+ -- Just ignore it as there is no sensible way to display it
+ end
+ end
+ local message = table.concat(messages, ', ')
+ if percentage then
+ return string.format('%3d%%: %s', percentage, message)
+ end
+ return message
+end
+
+-- 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(ms.textDocument_definition) and is_empty_or_default(bufnr, 'tagfunc')
+ then
+ vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
+ end
+ if
+ client.supports_method(ms.textDocument_completion) and is_empty_or_default(bufnr, 'omnifunc')
+ then
+ vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
+ end
+ if
+ client.supports_method(ms.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
+ api.nvim_buf_call(bufnr, function()
+ if
+ client.supports_method(ms.textDocument_hover)
+ and is_empty_or_default(bufnr, 'keywordprg')
+ and vim.fn.maparg('K', 'n', false, false) == ''
+ then
+ vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr })
+ end
+ end)
+ if client.supports_method(ms.textDocument_diagnostic) then
+ lsp.diagnostic._enable(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
@@ -899,9 +997,9 @@ end
---
--- Field `cmd` in {config} is required.
---
----@param config (table) Configuration for the server:
---- - cmd: (table|string|fun(dispatchers: table):table) command string or
---- list treated like |jobstart()|. The command must launch the language 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.
--- The function receives a dispatchers table and must return a table with the
--- functions `request`, `notify`, `is_closing` and `terminate`
@@ -912,11 +1010,11 @@ end
--- the `cmd` process. Not related to `root_dir`.
---
--- - cmd_env: (table) Environment flags to pass to the LSP on
---- spawn. Can be specified using keys like a map or as a list with `k=v`
---- pairs or both. Non-string values are coerced to string.
+--- spawn. Must be specified using a table.
+--- Non-string values are coerced to string.
--- Example:
--- <pre>
---- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; }
+--- { PORT = 8080; HOST = "0.0.0.0"; }
--- </pre>
---
--- - detached: (boolean, default true) Daemonize the server process so that it runs in a
@@ -929,11 +1027,10 @@ 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
---- `{[vim.type_idx]=vim.types.dictionary}`, else it will be encoded as an
+--- - Note: To send an empty dictionary use |vim.empty_dict()|, else it will be encoded as an
--- array.
---
--- - handlers: Map of language server method names to |lsp-handler|
@@ -977,7 +1074,7 @@ end
--- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was
--- sent to it. You can only modify the `client.offset_encoding` here before
--- any notifications are sent. Most language servers expect to be sent client specified settings after
---- initialization. Neovim does not make this assumption. A
+--- initialization. Nvim does not make this assumption. A
--- `workspace/didChangeConfiguration` notification should be sent
--- to the server during on_init.
---
@@ -1006,13 +1103,11 @@ end
--- server will base its workspaceFolders, rootUri, and rootPath
--- on initialization.
---
----@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be
+---@return integer|nil 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)
- 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 {}
@@ -1032,12 +1127,11 @@ function lsp.start_client(config)
local dispatch = {}
- ---@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|
+ ---@return lsp-handler|nil 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
@@ -1049,7 +1143,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.
@@ -1063,27 +1159,41 @@ 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
+ --- Logs the given error to the LSP log and to the error buffer.
+ --- @param code integer Error code
+ --- @param err any Error arguments
+ local function write_error(code, 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))
+ end
+
---@private
--- Invoked when the client operation throws an error.
---
- ---@param code (number) Error code
+ ---@param code (integer) 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()
- 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))
+ write_error(code, err)
if config.on_error then
local status, usererr = pcall(config.on_error, code, err)
if not status then
@@ -1093,28 +1203,9 @@ function lsp.start_client(config)
end
end
- ---@private
- local function set_defaults(client, bufnr)
- local capabilities = client.server_capabilities
- if capabilities.definitionProvider and vim.bo[bufnr].tagfunc == '' then
- vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
- end
- if capabilities.completionProvider and vim.bo[bufnr].omnifunc == '' then
- vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
- end
- if
- capabilities.documentRangeFormattingProvider
- and vim.bo[bufnr].formatprg == ''
- and vim.bo[bufnr].formatexpr == ''
- then
- vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()'
- end
- end
-
- ---@private
--- Reset defaults set by `set_defaults`.
--- Must only be called if the last client attached to a buffer exits.
- local function unset_defaults(bufnr)
+ local function reset_defaults(bufnr)
if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
vim.bo[bufnr].tagfunc = nil
end
@@ -1124,33 +1215,44 @@ function lsp.start_client(config)
if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then
vim.bo[bufnr].formatexpr = nil
end
+ api.nvim_buf_call(bufnr, function()
+ local keymap = vim.fn.maparg('K', 'n', false, true)
+ if keymap and keymap.callback == vim.lsp.buf.hover then
+ vim.keymap.del('n', 'K', { buffer = bufnr })
+ end
+ end)
end
---@private
--- Invoked on client exit.
---
- ---@param code (number) exit code of the process
- ---@param signal (number) the signal used to terminate (if any)
+ ---@param code (integer) exit code of the process
+ ---@param signal (integer) the signal used to terminate (if any)
function dispatch.on_exit(code, signal)
if config.on_exit then
pcall(config.on_exit, code, signal, client_id)
end
+ local client = active_clients[client_id] and active_clients[client_id]
+ or uninitialized_clients[client_id]
+
for bufnr, client_ids in pairs(all_buffer_active_clients) do
if client_ids[client_id] then
vim.schedule(function()
- nvim_exec_autocmds('LspDetach', {
- buffer = bufnr,
- modeline = false,
- data = { client_id = client_id },
- })
+ if client and client.attached_buffers[bufnr] then
+ nvim_exec_autocmds('LspDetach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id },
+ })
+ end
local namespace = vim.lsp.diagnostic.get_namespace(client_id)
vim.diagnostic.reset(namespace, bufnr)
client_ids[client_id] = nil
if vim.tbl_isempty(client_ids) then
- unset_defaults(bufnr)
+ reset_defaults(bufnr)
end
end)
end
@@ -1159,8 +1261,6 @@ function lsp.start_client(config)
-- 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
@@ -1170,8 +1270,13 @@ function lsp.start_client(config)
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)
+ local msg = string.format(
+ 'Client %s quit with exit code %s and signal %s. Check log for errors: %s',
+ name,
+ code,
+ signal,
+ lsp.get_log_path()
+ )
vim.notify(msg, vim.log.levels.WARN)
end
end)
@@ -1194,6 +1299,7 @@ function lsp.start_client(config)
return
end
+ ---@class lsp.Client
local client = {
id = client_id,
name = name,
@@ -1205,26 +1311,44 @@ function lsp.start_client(config)
handlers = handlers,
commands = config.commands or {},
+ --- @type table<integer,{ type: string, bufnr: integer, method: string}>
requests = {},
- -- for $/progress report
+
+ --- Contains $/progress report messages.
+ --- They have the format {token: integer|string, value: any}
+ --- For "work done progress", value will be one of:
+ --- - lsp.WorkDoneProgressBegin,
+ --- - lsp.WorkDoneProgressReport (extended with title from Begin)
+ --- - lsp.WorkDoneProgressEnd (extended with title from Begin)
+ progress = vim.ringbuf(50),
+
+ --- @type lsp.ServerCapabilities
+ server_capabilities = {},
+
+ ---@deprecated use client.progress instead
messages = { name = name, messages = {}, progress = {}, status = {} },
+ dynamic_capabilities = require('vim.lsp._dynamic').new(client_id),
}
+ ---@type table<string|integer, string> title of unfinished progress sequences by token
+ client.progress.pending = {}
+
+ --- @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.
uninitialized_clients[client_id] = client
- ---@private
local function initialize()
local valid_traces = {
off = 'off',
messages = 'messages',
verbose = 'verbose',
}
- 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 = {
@@ -1249,12 +1373,12 @@ 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 = {
name = 'Neovim',
- version = string.format('%s.%s.%s', version.major, version.minor, version.patch),
+ version = tostring(vim.version()),
},
-- The rootPath of the workspace. Is null if no folder is open.
--
@@ -1271,15 +1395,37 @@ function lsp.start_client(config)
-- User provided initialization options.
initializationOptions = config.init_options,
-- 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").
-- trace = "off" | "messages" | "verbose";
trace = valid_traces[config.trace] or 'off',
}
if config.before_init then
- -- TODO(ashkan) handle errors here.
- pcall(config.before_init, initialize_params, config)
+ local status, err = pcall(config.before_init, initialize_params, config)
+ if not status then
+ write_error(lsp.client_errors.BEFORE_INIT_CALLBACK_ERROR, err)
+ 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, 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)
rpc.request('initialize', initialize_params, function(init_err, result)
assert(not init_err, tostring(init_err))
@@ -1295,46 +1441,18 @@ function lsp.start_client(config)
assert(result.capabilities, "initialize result doesn't contain capabilities")
client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities)
- -- Deprecation wrapper: this will be removed in 0.8
- local mt = {}
- mt.__index = function(table, key)
- if key == 'resolved_capabilities' then
- vim.notify_once(
- '[LSP] Accessing client.resolved_capabilities is deprecated, '
- .. 'update your plugins or configuration to access client.server_capabilities instead.'
- .. 'The new key/value pairs in server_capabilities directly match those '
- .. 'defined in the language server protocol',
- vim.log.levels.WARN
- )
- rawset(table, key, protocol._resolve_capabilities_compat(client.server_capabilities))
- return rawget(table, key)
- else
- return rawget(table, key)
- end
- end
- setmetatable(client, mt)
-
- 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
+ if client.server_capabilities.positionEncoding then
+ client.offset_encoding = client.server_capabilities.positionEncoding
end
if next(config.settings) then
- client.notify('workspace/didChangeConfiguration', { settings = config.settings })
+ client.notify(ms.workspace_didChangeConfiguration, { settings = config.settings })
end
if config.on_init then
local status, err = pcall(config.on_init, client, result)
if not status then
- pcall(handlers.on_error, lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
+ write_error(lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
end
end
local _ = log.info()
@@ -1357,23 +1475,23 @@ function lsp.start_client(config)
end)
end
- ---@private
+ ---@nodoc
--- 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|nil) Response |lsp-handler| for this method.
- ---@param bufnr (number) Buffer handle (0 for current).
- ---@returns ({status}, [request_id]): {status} is a bool indicating
+ ---@param method string LSP method name.
+ ---@param params table|nil LSP request params.
+ ---@param handler lsp-handler|nil Response |lsp-handler| for this method.
+ ---@param bufnr integer Buffer handle (0 for current).
+ ---@return boolean status, integer|nil 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()|
+ ---@see |vim.lsp.buf_request_all()|
function client.request(method, params, handler, bufnr)
if not handler then
handler = assert(
@@ -1383,23 +1501,39 @@ function lsp.start_client(config)
end
-- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
changetracking.flush(client, bufnr)
+ local version = util.buf_versions[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,
- result,
- { method = method, client_id = client_id, bufnr = bufnr, params = params }
- )
+ local context = {
+ method = method,
+ client_id = client_id,
+ bufnr = bufnr,
+ params = params,
+ version = version,
+ }
+ handler(err, result, context)
end, function(request_id)
+ local request = client.requests[request_id]
+ request.type = 'complete'
+ nvim_exec_autocmds('LspRequest', {
+ buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
+ modeline = false,
+ data = { client_id = client_id, request_id = request_id, request = request },
+ })
client.requests[request_id] = nil
- nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
end)
if success and request_id then
- client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method }
- nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
+ local request = { type = 'pending', bufnr = bufnr, method = method }
+ client.requests[request_id] = request
+ nvim_exec_autocmds('LspRequest', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id, request_id = request_id, request = request },
+ })
end
return success, request_id
@@ -1412,13 +1546,14 @@ function lsp.start_client(config)
---
---@param method (string) LSP method name.
---@param params (table) LSP request params.
- ---@param timeout_ms (number|nil) Maximum time in milliseconds to wait for
+ ---@param timeout_ms (integer|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
- ---string describing the failure reason. If the request was unsuccessful
- ---returns `nil`.
+ ---@param bufnr (integer) Buffer handle (0 for current).
+ ---@return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dictionary, where
+ --- `err` and `result` come from the |lsp-handler|.
+ --- On timeout, cancel or error, returns `(nil, err)` where `err` is a
+ --- string describing the failure reason. If the request was unsuccessful
+ --- returns `nil`.
---@see |vim.lsp.buf_request_sync()|
function client.request_sync(method, params, timeout_ms, bufnr)
local request_result = nil
@@ -1444,41 +1579,62 @@ function lsp.start_client(config)
return request_result
end
- ---@private
+ ---@nodoc
--- Sends a notification to an LSP server.
---
---@param method string LSP method name.
---@param params table|nil LSP request params.
- ---@returns {status} (bool) true if the notification was successful.
+ ---@return boolean status true if the notification was successful.
---If it is false, then it will always be false
---(the client has shutdown).
function client.notify(method, params)
- if method ~= 'textDocument/didChange' then
+ if method ~= ms.textDocument_didChange then
changetracking.flush(client)
end
- return rpc.notify(method, params)
+
+ local client_active = rpc.notify(method, params)
+
+ if client_active then
+ vim.schedule(function()
+ nvim_exec_autocmds('LspNotify', {
+ modeline = false,
+ data = {
+ client_id = client.id,
+ method = method,
+ params = params,
+ },
+ })
+ end)
+ end
+
+ return client_active
end
- ---@private
+ ---@nodoc
--- 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
+ ---@param id (integer) id of request to cancel
+ ---@return boolean status true if notification was successful. false otherwise
---@see |vim.lsp.client.notify()|
function client.cancel_request(id)
validate({ id = { id, 'n' } })
local request = client.requests[id]
if request and request.type == 'pending' then
request.type = 'cancel'
- nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
+ nvim_exec_autocmds('LspRequest', {
+ buffer = request.bufnr,
+ modeline = false,
+ data = { client_id = client_id, request_id = id, request = request },
+ })
end
- return rpc.notify('$/cancelRequest', { id = id })
+ return rpc.notify(ms.dollar_cancelRequest, { id = id })
end
-- Track this so that we can escalate automatically if we've already tried a
-- graceful shutdown
local graceful_shutdown_failed = false
- ---@private
+
+ ---@nodoc
--- Stops a client, optionally with force.
---
---By default, it will just ask the - server to shutdown without force. If
@@ -1495,9 +1651,9 @@ function lsp.start_client(config)
return
end
-- Sending a signal after a process has exited is acceptable.
- rpc.request('shutdown', nil, function(err, _)
+ rpc.request(ms.shutdown, nil, function(err, _)
if err == nil then
- rpc.notify('exit')
+ rpc.notify(ms.exit)
else
-- If there was an error in the shutdown request, then term to be safe.
rpc.terminate()
@@ -1509,19 +1665,59 @@ function lsp.start_client(config)
---@private
--- Checks whether a client is stopped.
---
- ---@returns (bool) true if client is stopped or in the process of being
+ ---@return boolean # true if client is stopped or in the process of being
---stopped; false otherwise
function client.is_stopped()
return rpc.is_closing()
end
---@private
+ --- Execute a lsp command, either via client command function (if available)
+ --- or via workspace/executeCommand (if supported by the server)
+ ---
+ ---@param command lsp.Command
+ ---@param context? {bufnr: integer}
+ ---@param handler? lsp-handler only called if a server command
+ function client._exec_cmd(command, context, handler)
+ context = vim.deepcopy(context or {})
+ context.bufnr = context.bufnr or api.nvim_get_current_buf()
+ context.client_id = client.id
+ local cmdname = command.command
+ local fn = client.commands[cmdname] or lsp.commands[cmdname]
+ if fn then
+ fn(command, context)
+ return
+ end
+
+ local command_provider = client.server_capabilities.executeCommandProvider
+ local commands = type(command_provider) == 'table' and command_provider.commands or {}
+ if not vim.list_contains(commands, cmdname) then
+ vim.notify_once(
+ string.format(
+ 'Language server `%s` does not support command `%s`. This command may require a client extension.',
+ client.name,
+ cmdname
+ ),
+ vim.log.levels.WARN
+ )
+ return
+ end
+ -- Not using command directly to exclude extra properties,
+ -- see https://github.com/python-lsp/python-lsp-server/issues/146
+ local params = {
+ command = command.command,
+ arguments = command.arguments,
+ }
+ client.request(ms.workspace_executeCommand, params, handler, context.bufnr)
+ end
+
+ ---@private
--- Runs the on_attach function from the client's config if it was defined.
- ---@param bufnr (number) Buffer number
+ ---@param bufnr integer Buffer number
function client._on_attach(bufnr)
text_document_did_open_handler(bufnr, client)
- set_defaults(client, bufnr)
+ lsp._set_defaults(client, bufnr)
nvim_exec_autocmds('LspAttach', {
buffer = bufnr,
@@ -1530,8 +1726,10 @@ function lsp.start_client(config)
})
if config.on_attach then
- -- TODO(ashkan) handle errors.
- pcall(config.on_attach, client, bufnr)
+ local status, err = pcall(config.on_attach, client, bufnr)
+ if not status then
+ write_error(lsp.client_errors.ON_ATTACH_ERROR, err)
+ end
end
-- schedule the initialization of semantic tokens to give the above
@@ -1573,17 +1771,21 @@ do
end
end
----@private
---Buffer lifecycle handler for textDocument/didSave
local function text_document_did_save_handler(bufnr)
bufnr = resolve_bufnr(bufnr)
local uri = vim.uri_from_bufnr(bufnr)
local text = once(buf_get_full_text)
- for_each_buffer_client(bufnr, function(client)
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
local name = api.nvim_buf_get_name(bufnr)
local old_name = changetracking._get_and_set_name(client, bufnr, name)
if old_name and name ~= old_name then
- client.notify('textDocument/didOpen', {
+ client.notify(ms.textDocument_didClose, {
+ textDocument = {
+ uri = vim.uri_from_fname(old_name),
+ },
+ })
+ client.notify(ms.textDocument_didOpen, {
textDocument = {
version = 0,
uri = uri,
@@ -1599,14 +1801,14 @@ local function text_document_did_save_handler(bufnr)
if type(save_capability) == 'table' and save_capability.includeText then
included_text = text(bufnr)
end
- client.notify('textDocument/didSave', {
+ client.notify(ms.textDocument_didSave, {
textDocument = {
uri = uri,
},
text = included_text,
})
end
- end)
+ end
end
--- Implements the `textDocument/did…` notifications required to track a buffer
@@ -1614,8 +1816,9 @@ end
---
--- Without calling this, the server won't be notified of changes to a buffer.
---
----@param bufnr (number) Buffer handle, or 0 for current
----@param client_id (number) Client id
+---@param bufnr (integer) Buffer handle, or 0 for current
+---@param client_id (integer) Client id
+---@return boolean success `true` if client was attached successfully; `false` otherwise
function lsp.buf_attach_client(bufnr, client_id)
validate({
bufnr = { bufnr, 'n', true },
@@ -1641,7 +1844,7 @@ function lsp.buf_attach_client(bufnr, client_id)
buffer = bufnr,
desc = 'vim.lsp: textDocument/willSave',
callback = function(ctx)
- for_each_buffer_client(ctx.buf, function(client)
+ for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do
local params = {
textDocument = {
uri = uri,
@@ -1649,18 +1852,18 @@ function lsp.buf_attach_client(bufnr, client_id)
reason = protocol.TextDocumentSaveReason.Manual,
}
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then
- client.notify('textDocument/willSave', params)
+ client.notify(ms.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)
+ client.request_sync(ms.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
end,
})
api.nvim_create_autocmd('BufWritePost', {
@@ -1676,23 +1879,23 @@ function lsp.buf_attach_client(bufnr, client_id)
on_lines = text_document_did_change_handler,
on_reload = function()
local params = { textDocument = { uri = uri } }
- for_each_buffer_client(bufnr, function(client, _)
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
changetracking.reset_buf(client, bufnr)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
- client.notify('textDocument/didClose', params)
+ client.notify(ms.textDocument_didClose, params)
end
text_document_did_open_handler(bufnr, client)
- end)
+ end
end,
on_detach = function()
local params = { textDocument = { uri = uri } }
- for_each_buffer_client(bufnr, function(client, _)
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
changetracking.reset_buf(client, bufnr)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
- client.notify('textDocument/didClose', params)
+ client.notify(ms.textDocument_didClose, params)
end
client.attached_buffers[bufnr] = nil
- end)
+ end
util.buf_versions[bufnr] = nil
all_buffer_active_clients[bufnr] = nil
end,
@@ -1704,7 +1907,7 @@ function lsp.buf_attach_client(bufnr, client_id)
end
if buffer_client_ids[client_id] then
- return
+ return true
end
-- This is our first time attaching this client to this buffer.
buffer_client_ids[client_id] = true
@@ -1722,8 +1925,8 @@ end
--- Note: While the server is notified that the text document (buffer)
--- was closed, it is still able to send notifications should it ignore this notification.
---
----@param bufnr number Buffer handle, or 0 for current
----@param client_id number Client id
+---@param bufnr integer Buffer handle, or 0 for current
+---@param client_id integer Client id
function lsp.buf_detach_client(bufnr, client_id)
validate({
bufnr = { bufnr, 'n', true },
@@ -1736,8 +1939,8 @@ function lsp.buf_detach_client(bufnr, client_id)
vim.notify(
string.format(
'Buffer (id: %d) is not attached to client (id: %d). Cannot detach.',
- client_id,
- bufnr
+ bufnr,
+ client_id
)
)
return
@@ -1754,7 +1957,7 @@ function lsp.buf_detach_client(bufnr, client_id)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
local uri = vim.uri_from_bufnr(bufnr)
local params = { textDocument = { uri = uri } }
- client.notify('textDocument/didClose', params)
+ client.notify(ms.textDocument_didClose, params)
end
client.attached_buffers[bufnr] = nil
@@ -1765,16 +1968,14 @@ function lsp.buf_detach_client(bufnr, client_id)
all_buffer_active_clients[bufnr] = nil
end
- local namespace = vim.lsp.diagnostic.get_namespace(client_id)
+ local namespace = lsp.diagnostic.get_namespace(client_id)
vim.diagnostic.reset(namespace, bufnr)
-
- vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id))
end
--- Checks if a buffer is attached for a particular client.
---
----@param bufnr (number) Buffer handle, or 0 for current
----@param client_id (number) the client id
+---@param bufnr (integer) Buffer handle, or 0 for current
+---@param client_id (integer) the client id
function lsp.buf_is_attached(bufnr, client_id)
return (all_buffer_active_clients[resolve_bufnr(bufnr)] or {})[client_id] == true
end
@@ -1782,17 +1983,17 @@ end
--- Gets a client by id, or nil if the id is invalid.
--- The returned client may not yet be fully initialized.
---
----@param client_id number client id
+---@param client_id integer client id
---
----@returns |vim.lsp.client| object, or nil
+---@return (nil|lsp.Client) client rpc object
function lsp.get_client_by_id(client_id)
return active_clients[client_id] or uninitialized_clients[client_id]
end
--- Returns list of buffers attached to client_id.
---
----@param client_id number client id
----@returns list of buffer ids
+---@param client_id integer client id
+---@return integer[] buffers list of buffer ids
function lsp.get_buffers_by_client_id(client_id)
local client = lsp.get_client_by_id(client_id)
return client and vim.tbl_keys(client.attached_buffers) or {}
@@ -1802,14 +2003,15 @@ end
---
--- You can also use the `stop()` function on a |vim.lsp.client| object.
--- To stop all clients:
---- <pre>lua
---- vim.lsp.stop_client(vim.lsp.get_active_clients())
---- </pre>
+---
+--- ```lua
+--- vim.lsp.stop_client(vim.lsp.get_clients())
+--- ```
---
--- By default asks the server to shutdown, unless stop was requested
--- already for this client, then force-shutdown is attempted.
---
----@param client_id number|table id or |vim.lsp.client| object, or list thereof
+---@param client_id integer|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 }
@@ -1824,26 +2026,28 @@ 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
+---@class vim.lsp.get_clients.filter
+---@field id integer|nil Match clients by id
+---@field bufnr integer|nil match clients attached to the given buffer
---@field name string|nil match clients by name
+---@field method string|nil match client by supported method name
--- Get active clients.
---
----@param filter vim.lsp.get_active_clients.filter|nil (table|nil) A table with
+---@param filter vim.lsp.get_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
----@returns (table) List of |vim.lsp.client| objects
-function lsp.get_active_clients(filter)
+--- - method (string): Only return clients supporting the given method
+---@return lsp.Client[]: List of |vim.lsp.client| objects
+function lsp.get_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
@@ -1853,6 +2057,7 @@ function lsp.get_active_clients(filter)
client
and (filter.id == nil or client.id == filter.id)
and (filter.name == nil or client.name == filter.name)
+ and (filter.method == nil or client.supports_method(filter.method, { bufnr = filter.bufnr }))
then
clients[#clients + 1] = client
end
@@ -1860,6 +2065,13 @@ function lsp.get_active_clients(filter)
return clients
end
+---@private
+---@deprecated
+function lsp.get_active_clients(filter)
+ -- TODO: add vim.deprecate call after 0.10 is out for removal in 0.12
+ return lsp.get_clients(filter)
+end
+
api.nvim_create_autocmd('VimLeavePre', {
desc = 'vim.lsp: exit handler',
callback = function()
@@ -1890,7 +2102,6 @@ api.nvim_create_autocmd('VimLeavePre', {
local poll_time = 50
- ---@private
local function check_clients_closed()
for client_id, timeout in pairs(timeouts) do
timeouts[client_id] = timeout - poll_time
@@ -1920,16 +2131,17 @@ api.nvim_create_autocmd('VimLeavePre', {
--- Sends an async request for all active clients attached to the
--- buffer.
---
----@param bufnr (number) Buffer handle, or 0 for current.
+---@param bufnr (integer) Buffer handle, or 0 for current.
---@param method (string) LSP method name
---@param params table|nil Parameters to send to the server
----@param handler function|nil See |lsp-handler|
+---@param handler? lsp-handler 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.
+---@return table<integer, integer> client_request_ids Map of client-id:request-id pairs
+---for all successful requests.
+---@return function _cancel_all_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, handler)
validate({
bufnr = { bufnr, 'n', true },
@@ -1937,34 +2149,30 @@ function lsp.buf_request(bufnr, method, params, handler)
handler = { handler, 'f', true },
})
- local supported_clients = {}
+ bufnr = resolve_bufnr(bufnr)
local method_supported = false
- for_each_buffer_client(bufnr, function(client, client_id)
- if client.supports_method(method) then
+ local clients = lsp.get_clients({ bufnr = bufnr })
+ local client_request_ids = {}
+ for _, client in ipairs(clients) do
+ if client.supports_method(method, { bufnr = bufnr }) then
method_supported = true
- table.insert(supported_clients, client_id)
+
+ local request_success, request_id = client.request(method, params, handler, 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)
+ end
-- if has client but no clients support the given method, notify the user
- if
- not tbl_isempty(all_buffer_active_clients[resolve_bufnr(bufnr)] or {}) and not method_supported
- then
+ if next(clients) and not method_supported then
vim.notify(lsp._unsupported_method(method), vim.log.levels.ERROR)
nvim_command('redraw')
return {}, function() end
end
- local client_request_ids = {}
- for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
- 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, supported_clients)
-
local function _cancel_all_requests()
for client_id, request_id in pairs(client_request_ids) do
local client = active_clients[client_id]
@@ -1975,39 +2183,36 @@ function lsp.buf_request(bufnr, method, params, handler)
return client_request_ids, _cancel_all_requests
end
----Sends an async request for all active clients attached to the buffer.
----Executes the callback on the combined result.
----Parameters are the same as |vim.lsp.buf_request()| but the return result and callback are
----different.
+--- Sends an async request for all active clients attached to the buffer and executes the `handler`
+--- callback with the combined result.
---
----@param bufnr (number) Buffer handle, or 0 for current.
+---@param bufnr (integer) Buffer handle, or 0 for current.
---@param method (string) LSP method name
---@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
---
----@returns (function) A function that will cancel all requests which is the same as the one returned from `buf_request`.
-function lsp.buf_request_all(bufnr, method, params, callback)
- local request_results = {}
+---@param handler fun(results: table<integer, {error: lsp.ResponseError, result: any}>) (function)
+--- Handler called after all requests are completed. Server results are passed as
+--- a `client_id:result` map.
+---@return function cancel Function that cancels all requests.
+function lsp.buf_request_all(bufnr, method, params, handler)
+ local results = {}
local result_count = 0
local expected_result_count = 0
local set_expected_result_count = once(function()
- for_each_buffer_client(bufnr, function(client)
- if client.supports_method(method) then
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
+ if client.supports_method(method, { bufnr = bufnr }) then
expected_result_count = expected_result_count + 1
end
- end)
+ end
end)
local function _sync_handler(err, result, ctx)
- request_results[ctx.client_id] = { error = err, result = result }
+ results[ctx.client_id] = { error = err, result = result }
result_count = result_count + 1
set_expected_result_count()
if result_count >= expected_result_count then
- callback(request_results)
+ handler(results)
end
end
@@ -2019,18 +2224,17 @@ end
--- Sends a request to all server and waits for the response of all of them.
---
--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result.
---- Parameters are the same as |vim.lsp.buf_request()| but the return result is
---- different. Wait maximum of {timeout_ms} (default 1000) ms.
+--- Parameters are the same as |vim.lsp.buf_request_all()| but the result is
+--- different. Waits a maximum of {timeout_ms} (default 1000) ms.
---
----@param bufnr (number) Buffer handle, or 0 for current.
+---@param bufnr (integer) Buffer handle, or 0 for current.
---@param method (string) LSP method name
---@param params (table|nil) Parameters to send to the server
----@param timeout_ms (number|nil) Maximum time in milliseconds to wait for a
+---@param timeout_ms (integer|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
---- reason.
+---@return table<integer, {err: lsp.ResponseError, result: any}>|nil (table) result Map of client_id:request_result.
+---@return string|nil err On timeout, cancel, or error, `err` is a string describing the failure reason, and `result` is nil.
function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
local request_results
@@ -2051,41 +2255,23 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
end
--- Send a notification to a server
----@param bufnr (number|nil) The number of the buffer
+---@param bufnr (integer|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
+---@return boolean success true if any client returns true; false otherwise
function lsp.buf_notify(bufnr, method, params)
validate({
bufnr = { bufnr, 'n', true },
method = { method, 's' },
})
local resp = false
- for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr)
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
if client.rpc.notify(method, params) then
resp = true
end
- end)
- return resp
-end
-
----@private
-local function adjust_start_col(lnum, line, items, encoding)
- local min_start_char = nil
- for _, item in pairs(items) do
- if item.filterText == nil and item.textEdit and item.textEdit.range.start.line == lnum - 1 then
- if min_start_char and min_start_char ~= item.textEdit.range.start.character then
- return nil
- end
- min_start_char = item.textEdit.range.start.character
- end
- end
- if min_start_char then
- return util._str_byteindex_enc(line, min_start_char, encoding)
- else
- return nil
end
+ return resp
end
--- Implements 'omnifunc' compatible LSP completion.
@@ -2094,80 +2280,24 @@ end
---@see |complete-items|
---@see |CompleteDone|
---
----@param findstart number 0 or 1, decides behavior
----@param base number findstart=0, text to match against
+---@param findstart integer 0 or 1, decides behavior
+---@param base integer findstart=0, text to match against
---
----@returns (number) Decided by {findstart}:
+---@return integer|table 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)
- local _ = log.debug() and log.debug('omnifunc.findstart', { findstart = findstart, base = base })
-
- 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
+ if log.debug() then
+ log.debug('omnifunc.findstart', { findstart = findstart, base = base })
end
-
- -- Then, perform standard completion request
- local _ = log.info() and log.info('base ', base)
-
- local pos = api.nvim_win_get_cursor(0)
- local line = api.nvim_get_current_line()
- local line_to_cursor = line:sub(1, pos[2])
- local _ = log.trace() and log.trace('omnifunc.line', pos, line)
-
- -- Get the start position of the current keyword
- local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
-
- local params = util.make_position_params()
-
- local items = {}
- lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result, ctx)
- if err or not result or vim.fn.mode() ~= 'i' then
- return
- end
-
- -- Completion response items may be relative to a position different than `textMatch`.
- -- Concrete example, with sumneko/lua-language-server:
- --
- -- require('plenary.asy|
- -- ▲ ▲ ▲
- -- │ │ └── cursor_pos: 20
- -- │ └────── textMatch: 17
- -- └────────────── textEdit.range.start.character: 9
- -- .newText = 'plenary.async'
- -- ^^^
- -- prefix (We'd remove everything not starting with `asy`,
- -- so we'd eliminate the `plenary.async` result
- --
- -- `adjust_start_col` is used to prefer the language server boundary.
- --
- local client = lsp.get_client_by_id(ctx.client_id)
- local encoding = client and client.offset_encoding or 'utf-16'
- local candidates = util.extract_completion_items(result)
- local startbyte = adjust_start_col(pos[1], line, candidates, encoding) or textMatch
- local prefix = line:sub(startbyte + 1, pos[2])
- local matches = util.text_document_completion_list_to_complete_items(result, prefix)
- -- TODO(ashkan): is this the best way to do this?
- vim.list_extend(items, matches)
- vim.fn.complete(startbyte + 1, items)
- end)
-
- -- Return -2 to signal that we should continue completion so that we can
- -- async complete.
- return -2
+ return require('vim.lsp._completion').omnifunc(findstart, base)
end
--- Provides an interface between the built-in client and a `formatexpr` function.
---
--- Currently only supports a single client. This can be set via
--- `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in `on_attach`
---- via ``vim.api.nvim_buf_set_option(bufnr, 'formatexpr', 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')``.
+--- via ``vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})'``.
---
---@param opts table options for customizing the formatting expression which takes the
--- following optional keys:
@@ -2176,7 +2306,7 @@ function lsp.formatexpr(opts)
opts = opts or {}
local timeout_ms = opts.timeout_ms or 500
- if vim.tbl_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then
+ if vim.list_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then
-- `formatexpr` is also called when exceeding `textwidth` in insert mode
-- fall back to internal formatting
return 1
@@ -2189,10 +2319,10 @@ function lsp.formatexpr(opts)
return 0
end
local bufnr = api.nvim_get_current_buf()
- for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do
- if client.supports_method('textDocument/rangeFormatting') then
+ for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
+ if client.supports_method(ms.textDocument_rangeFormatting) then
local params = util.make_formatting_params()
- local end_line = vim.fn.getline(end_lnum)
+ local end_line = vim.fn.getline(end_lnum) --[[@as string]]
local end_col = util._str_utfindex_enc(end_line, nil, client.offset_encoding)
params.range = {
start = {
@@ -2205,9 +2335,9 @@ function lsp.formatexpr(opts)
},
}
local response =
- client.request_sync('textDocument/rangeFormatting', params, timeout_ms, bufnr)
- if response.result then
- vim.lsp.util.apply_text_edits(response.result, 0, client.offset_encoding)
+ client.request_sync(ms.textDocument_rangeFormatting, params, timeout_ms, bufnr)
+ if response and response.result then
+ lsp.util.apply_text_edits(response.result, 0, client.offset_encoding)
return 0
end
end
@@ -2227,39 +2357,41 @@ end
---@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(...)
- return require('vim.lsp.tagfunc')(...)
+---@return table[] tags A list of matching tags
+function lsp.tagfunc(pattern, flags)
+ return require('vim.lsp.tagfunc')(pattern, flags)
end
---Checks whether a client is stopped.
---
----@param client_id (number)
----@returns true if client is stopped, false otherwise.
+---@param client_id (integer)
+---@return boolean stopped true if client is stopped, false otherwise.
function lsp.client_is_stopped(client_id)
- return active_clients[client_id] == nil
+ assert(client_id, 'missing client_id param')
+ return active_clients[client_id] == nil and not uninitialized_clients[client_id]
end
--- Gets a map of client_id:client pairs for the given buffer, where each value
--- is a |vim.lsp.client| object.
---
----@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.
+---@param bufnr (integer|nil): Buffer handle, or 0 for current
+---@return table result is table of (client_id, client) pairs
+---@deprecated Use |vim.lsp.get_clients()| instead.
function lsp.buf_get_clients(bufnr)
local result = {}
- for _, client in ipairs(lsp.get_active_clients({ bufnr = resolve_bufnr(bufnr) })) do
+ for _, client in ipairs(lsp.get_clients({ bufnr = resolve_bufnr(bufnr) })) do
result[client.id] = client
end
return result
end
--- Log level dictionary with reverse lookup as well.
---
--- Can be used to lookup the number from the name or the
--- name from the number.
--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
--- Level numbers begin with "TRACE" at 0
+--- Log level dictionary with reverse lookup as well.
+---
+--- Can be used to lookup the number from the name or the
+--- name from the number.
+--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
+--- Level numbers begin with "TRACE" at 0
+--- @nodoc
lsp.log_levels = log.levels
--- Sets the global log level for LSP logging.
@@ -2272,7 +2404,7 @@ lsp.log_levels = log.levels
---
---@see |vim.lsp.log_levels|
---
----@param level (number|string) the case insensitive level name or number
+---@param level (integer|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)
@@ -2282,22 +2414,19 @@ function lsp.set_log_level(level)
end
--- Gets the path of the logfile used by the LSP client.
----@returns (String) Path to logfile.
+---@return string path to log file
function lsp.get_log_path()
return log.get_filename()
end
+---@private
--- Invokes a function for each LSP client attached to a buffer.
---
----@param bufnr number Buffer number
+---@param bufnr integer Buffer number
---@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>lua
---- vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr)
---- print(vim.inspect(client))
---- end)
---- </pre>
+--- buffer number as arguments.
+---@deprecated use lsp.get_clients({ bufnr = bufnr }) with regular loop
function lsp.for_each_buffer_client(bufnr, fn)
return for_each_buffer_client(bufnr, fn)
end
@@ -2316,10 +2445,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(
@@ -2374,4 +2506,3 @@ lsp.commands = setmetatable({}, {
})
return lsp
--- vim:sw=2 ts=2 et