feat(lsp): more annotations

This commit is contained in:
Lewis Russell
2023-12-13 12:00:11 +00:00
committed by Lewis Russell
parent 320e9c1c21
commit 97bea3163a
16 changed files with 364 additions and 228 deletions

View File

@@ -1102,7 +1102,7 @@ with({handler}, {override_config}) *vim.lsp.with()*
Function to manage overriding defaults for LSP handlers. Function to manage overriding defaults for LSP handlers.
Parameters: ~ Parameters: ~
• {handler} (function) See |lsp-handler| • {handler} (lsp.Handler) See |lsp-handler|
• {override_config} (table) Table containing the keys to override • {override_config} (table) Table containing the keys to override
behavior of the {handler} behavior of the {handler}
@@ -1378,6 +1378,7 @@ on_diagnostic({_}, {result}, {ctx}, {config})
< <
Parameters: ~ Parameters: ~
• {ctx} lsp.HandlerContext
• {config} (table) Configuration table (see |vim.diagnostic.config()|). • {config} (table) Configuration table (see |vim.diagnostic.config()|).
*vim.lsp.diagnostic.on_publish_diagnostics()* *vim.lsp.diagnostic.on_publish_diagnostics()*
@@ -1406,6 +1407,7 @@ on_publish_diagnostics({_}, {result}, {ctx}, {config})
< <
Parameters: ~ Parameters: ~
• {ctx} lsp.HandlerContext
• {config} (table) Configuration table (see |vim.diagnostic.config()|). • {config} (table) Configuration table (see |vim.diagnostic.config()|).
@@ -1441,6 +1443,9 @@ get({bufnr}) *vim.lsp.codelens.get()*
on_codelens({err}, {result}, {ctx}, {_}) on_codelens({err}, {result}, {ctx}, {_})
|lsp-handler| for the method `textDocument/codeLens` |lsp-handler| for the method `textDocument/codeLens`
Parameters: ~
• {ctx} lsp.HandlerContext
refresh() *vim.lsp.codelens.refresh()* refresh() *vim.lsp.codelens.refresh()*
Refresh the codelens for the current buffer Refresh the codelens for the current buffer
@@ -1549,6 +1554,7 @@ get_at_pos({bufnr}, {row}, {col})
• type (string) token type as string, e.g. "variable" • type (string) token type as string, e.g. "variable"
• modifiers (table) token modifiers as a set. E.g., { static = true, • modifiers (table) token modifiers as a set. E.g., { static = true,
readonly = true } readonly = true }
• client_id (integer)
*vim.lsp.semantic_tokens.highlight_token()* *vim.lsp.semantic_tokens.highlight_token()*
highlight_token({token}, {bufnr}, {client_id}, {hl_group}, {opts}) highlight_token({token}, {bufnr}, {client_id}, {hl_group}, {opts})
@@ -1620,6 +1626,7 @@ hover({_}, {result}, {ctx}, {config}) *vim.lsp.handlers.hover()*
< <
Parameters: ~ Parameters: ~
• {ctx} lsp.HandlerContext
• {config} (table) Configuration table. • {config} (table) Configuration table.
• border: (default=nil) • border: (default=nil)
• Add borders to the floating window • Add borders to the floating window
@@ -1641,7 +1648,7 @@ signature_help({_}, {result}, {ctx}, {config})
Parameters: ~ Parameters: ~
• {result} (table) Response from the language server • {result} (table) Response from the language server
• {ctx} (table) Client context • {ctx} lsp.HandlerContext Client context
• {config} (table) Configuration table. • {config} (table) Configuration table.
• border: (default=nil) • border: (default=nil)
• Add borders to the floating window • Add borders to the floating window
@@ -1849,7 +1856,7 @@ make_formatting_params({options})
• {options} (table|nil) with valid `FormattingOptions` entries • {options} (table|nil) with valid `FormattingOptions` entries
Return: ~ Return: ~
`DocumentFormattingParams` object lsp.DocumentFormattingParams object
See also: ~ See also: ~
• https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
@@ -2152,7 +2159,7 @@ start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params})
for LSP server process for LSP server process
Return: ~ Return: ~
(table|nil) Client RPC object, with these methods: RpcClientPublic|nil Client RPC object, with these methods:
• `notify()` |vim.lsp.rpc.notify()| • `notify()` |vim.lsp.rpc.notify()|
• `request()` |vim.lsp.rpc.request()| • `request()` |vim.lsp.rpc.request()|
• `is_closing()` returns a boolean indicating if the RPC is closing. • `is_closing()` returns a boolean indicating if the RPC is closing.
@@ -2185,6 +2192,6 @@ resolve_capabilities({server_capabilities})
server server
Return: ~ Return: ~
(table|nil) Normalized table of capabilities lsp.ServerCapabilities|nil Normalized table of capabilities
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:

View File

@@ -478,7 +478,7 @@ do
end end
vim.g = make_dict_accessor('g', false) vim.g = make_dict_accessor('g', false)
vim.v = make_dict_accessor('v', false) vim.v = make_dict_accessor('v', false) --[[@as vim.v]]
vim.b = make_dict_accessor('b') vim.b = make_dict_accessor('b')
vim.w = make_dict_accessor('w') vim.w = make_dict_accessor('w')
vim.t = make_dict_accessor('t') vim.t = make_dict_accessor('t')

View File

@@ -0,0 +1,23 @@
--- @meta _
-- TODO(lewis6991): generate this and `:help vim-variable`
--- @class vim.v
--- The count given for the last Normal mode command. Can be used
--- to get the count before a mapping. Read-only. Example:
--- ```vim
--- :map _x :<C-U>echo "the count is " .. v:count<CR>
--- ```
--- Note: The <C-U> is required to remove the line range that you
--- get when typing ':' after a count.
--- When there are two counts, as in "3d2w", they are multiplied,
--- just like what happens in the command, "d6w" for the example.
--- Also used for evaluating the 'formatexpr' option.
--- @field count integer
---
--- Line number for the 'foldexpr' |fold-expr|, 'formatexpr',
--- 'indentexpr' and 'statuscolumn' expressions, tab page number
--- for 'guitablabel' and 'guitabtooltip'. Only valid while one of
--- these expressions is being evaluated. Read-only when in the |sandbox|.
--- @field lnum integer
vim.v = ...

View File

@@ -257,7 +257,7 @@ end
--- Validates a client configuration as given to |vim.lsp.start_client()|. --- Validates a client configuration as given to |vim.lsp.start_client()|.
--- ---
---@param config (lsp.ClientConfig) ---@param config (lsp.ClientConfig)
---@return (string|fun(dispatchers:table):table) Command ---@return (string|fun(dispatchers:vim.rpc.Dispatchers):RpcClientPublic?) Command
---@return string[] Arguments ---@return string[] Arguments
---@return string Encoding. ---@return string Encoding.
local function validate_client_config(config) local function validate_client_config(config)
@@ -290,7 +290,7 @@ local function validate_client_config(config)
'flags.debounce_text_changes must be a number with the debounce time in milliseconds' 'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
) )
local cmd, cmd_args --- @type (string|fun(dispatchers:table):table), string[] local cmd, cmd_args --- @type (string|fun(dispatchers:vim.rpc.Dispatchers):RpcClientPublic), string[]
local config_cmd = config.cmd local config_cmd = config.cmd
if type(config_cmd) == 'function' then if type(config_cmd) == 'function' then
cmd = config_cmd cmd = config_cmd
@@ -397,13 +397,14 @@ do
end, end,
}) })
---@param client lsp.Client
---@return CTGroup ---@return CTGroup
local function get_group(client) local function get_group(client)
local allow_inc_sync = if_nil(client.config.flags.allow_incremental_sync, true) local allow_inc_sync = if_nil(client.config.flags.allow_incremental_sync, true)
local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change') local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
local sync_kind = change_capability or protocol.TextDocumentSyncKind.None local sync_kind = change_capability or protocol.TextDocumentSyncKind.None
if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then
sync_kind = protocol.TextDocumentSyncKind.Full sync_kind = protocol.TextDocumentSyncKind.Full --[[@as integer]]
end end
return { return {
sync_kind = sync_kind, sync_kind = sync_kind,
@@ -572,7 +573,7 @@ do
return return
end end
local changes local changes --- @type lsp.TextDocumentContentChangeEvent[]
if sync_kind == protocol.TextDocumentSyncKind.None then if sync_kind == protocol.TextDocumentSyncKind.None then
return return
elseif sync_kind == protocol.TextDocumentSyncKind.Incremental then elseif sync_kind == protocol.TextDocumentSyncKind.Incremental then
@@ -650,6 +651,7 @@ do
end end
---@private ---@private
---@param buf_state CTBufferState
function changetracking._reset_timer(buf_state) function changetracking._reset_timer(buf_state)
local timer = buf_state.timer local timer = buf_state.timer
if timer then if timer then
@@ -663,6 +665,8 @@ do
--- Flushes any outstanding change notification. --- Flushes any outstanding change notification.
---@private ---@private
---@param client lsp.Client
---@param bufnr? integer
function changetracking.flush(client, bufnr) function changetracking.flush(client, bufnr)
local group = get_group(client) local group = get_group(client)
local state = state_by_group[group] local state = state_by_group[group]
@@ -685,7 +689,7 @@ end
--- Default handler for the 'textDocument/didOpen' LSP notification. --- Default handler for the 'textDocument/didOpen' LSP notification.
--- ---
---@param bufnr integer Number of the buffer, or 0 for current ---@param bufnr integer Number of the buffer, or 0 for current
---@param client table Client object ---@param client lsp.Client Client object
local function text_document_did_open_handler(bufnr, client) local function text_document_did_open_handler(bufnr, client)
changetracking.init(client, bufnr) changetracking.init(client, bufnr)
if not vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then if not vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
@@ -890,7 +894,7 @@ end
---@return string ---@return string
function lsp.status() function lsp.status()
local percentage = nil local percentage = nil
local messages = {} local messages = {} --- @type string[]
for _, client in ipairs(vim.lsp.get_clients()) do for _, client in ipairs(vim.lsp.get_clients()) do
for progress in client.progress do for progress in client.progress do
local value = progress.value local value = progress.value
@@ -913,12 +917,15 @@ function lsp.status()
end end
-- Determines whether the given option can be set by `set_defaults`. -- Determines whether the given option can be set by `set_defaults`.
---@param bufnr integer
---@param option string
---@return boolean
local function is_empty_or_default(bufnr, option) local function is_empty_or_default(bufnr, option)
if vim.bo[bufnr][option] == '' then if vim.bo[bufnr][option] == '' then
return true return true
end end
local info = vim.api.nvim_get_option_info2(option, { buf = bufnr }) local info = api.nvim_get_option_info2(option, { buf = bufnr })
local scriptinfo = vim.tbl_filter(function(e) local scriptinfo = vim.tbl_filter(function(e)
return e.sid == info.last_set_sid return e.sid == info.last_set_sid
end, vim.fn.getscriptinfo()) end, vim.fn.getscriptinfo())
@@ -932,6 +939,7 @@ end
---@private ---@private
---@param client lsp.Client ---@param client lsp.Client
---@param bufnr integer
function lsp._set_defaults(client, bufnr) function lsp._set_defaults(client, bufnr)
if if
client.supports_method(ms.textDocument_definition) and is_empty_or_default(bufnr, 'tagfunc') client.supports_method(ms.textDocument_definition) and is_empty_or_default(bufnr, 'tagfunc')
@@ -1131,7 +1139,7 @@ function lsp.start_client(config)
--- Returns the default handler if the user hasn't set a custom one. --- Returns the default handler if the user hasn't set a custom one.
--- ---
---@param method (string) LSP method name ---@param method (string) LSP method name
---@return lsp-handler|nil The handler for the given method, if defined, or the default from |vim.lsp.handlers| ---@return lsp.Handler|nil handler for the given method, if defined, or the default from |vim.lsp.handlers|
local function resolve_handler(method) local function resolve_handler(method)
return handlers[method] or default_handlers[method] return handlers[method] or default_handlers[method]
end end
@@ -1189,7 +1197,7 @@ function lsp.start_client(config)
--- Invoked when the client operation throws an error. --- Invoked when the client operation throws an error.
--- ---
---@param code (integer) Error code ---@param code (integer) Error code
---@param err (...) Other arguments may be passed depending on the error kind ---@param err any 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. ---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
function dispatch.on_error(code, err) function dispatch.on_error(code, err)
@@ -1197,7 +1205,9 @@ function lsp.start_client(config)
if config.on_error then if config.on_error then
local status, usererr = pcall(config.on_error, code, err) local status, usererr = pcall(config.on_error, code, err)
if not status then if not status then
local _ = log.error() and log.error(log_prefix, 'user on_error failed', { err = usererr }) if log.error() then
log.error(log_prefix, 'user on_error failed', { err = usererr })
end
err_message(log_prefix, ' user on_error failed: ', tostring(usererr)) err_message(log_prefix, ' user on_error failed: ', tostring(usererr))
end end
end end
@@ -1283,7 +1293,7 @@ function lsp.start_client(config)
end end
-- Start the RPC client. -- Start the RPC client.
local rpc local rpc --- @type RpcClientPublic?
if type(cmd) == 'function' then if type(cmd) == 'function' then
rpc = cmd(dispatch) rpc = cmd(dispatch)
else else
@@ -1306,9 +1316,10 @@ function lsp.start_client(config)
rpc = rpc, rpc = rpc,
offset_encoding = offset_encoding, offset_encoding = offset_encoding,
config = config, config = config,
attached_buffers = {}, attached_buffers = {}, --- @type table<integer,true>
handlers = handlers, handlers = handlers,
--- @type table<string,function>
commands = config.commands or {}, commands = config.commands or {},
--- @type table<integer,{ type: string, bufnr: integer, method: string}> --- @type table<integer,{ type: string, bufnr: integer, method: string}>
@@ -1346,7 +1357,7 @@ function lsp.start_client(config)
verbose = 'verbose', verbose = 'verbose',
} }
local workspace_folders --- @type table[]? local workspace_folders --- @type lsp.WorkspaceFolder[]?
local root_uri --- @type string? local root_uri --- @type string?
local root_path --- @type string? local root_path --- @type string?
if config.workspace_folders or config.root_dir then if config.workspace_folders or config.root_dir then
@@ -1426,7 +1437,9 @@ function lsp.start_client(config)
end end
end end
local _ = log.trace() and log.trace(log_prefix, 'initialize_params', initialize_params) if log.trace() then
log.trace(log_prefix, 'initialize_params', initialize_params)
end
rpc.request('initialize', initialize_params, function(init_err, result) rpc.request('initialize', initialize_params, function(init_err, result)
assert(not init_err, tostring(init_err)) assert(not init_err, tostring(init_err))
assert(result, 'server sent empty result') assert(result, 'server sent empty result')
@@ -1439,7 +1452,7 @@ function lsp.start_client(config)
-- when to send certain events to clients. -- when to send certain events to clients.
client.server_capabilities = client.server_capabilities =
assert(result.capabilities, "initialize result doesn't contain capabilities") assert(result.capabilities, "initialize result doesn't contain capabilities")
client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities) client.server_capabilities = assert(protocol.resolve_capabilities(client.server_capabilities))
if client.server_capabilities.positionEncoding then if client.server_capabilities.positionEncoding then
client.offset_encoding = client.server_capabilities.positionEncoding client.offset_encoding = client.server_capabilities.positionEncoding
@@ -1455,12 +1468,13 @@ function lsp.start_client(config)
write_error(lsp.client_errors.ON_INIT_CALLBACK_ERROR, err) write_error(lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
end end
end end
local _ = log.info() if log.info() then
and log.info( log.info(
log_prefix, log_prefix,
'server_capabilities', 'server_capabilities',
{ server_capabilities = client.server_capabilities } { server_capabilities = client.server_capabilities }
) )
end
-- Only assign after initialized. -- Only assign after initialized.
active_clients[client_id] = client active_clients[client_id] = client
@@ -1483,7 +1497,7 @@ function lsp.start_client(config)
--- ---
---@param method string LSP method name. ---@param method string LSP method name.
---@param params table|nil LSP request params. ---@param params table|nil LSP request params.
---@param handler lsp-handler|nil Response |lsp-handler| for this method. ---@param handler lsp.Handler|nil Response |lsp-handler| for this method.
---@param bufnr integer Buffer handle (0 for current). ---@param bufnr integer Buffer handle (0 for current).
---@return boolean status, integer|nil request_id {status} is a bool indicating ---@return boolean status, integer|nil request_id {status} is a bool indicating
---whether the request was successful. If it is `false`, then it will ---whether the request was successful. If it is `false`, then it will
@@ -1677,9 +1691,9 @@ function lsp.start_client(config)
--- ---
---@param command lsp.Command ---@param command lsp.Command
---@param context? {bufnr: integer} ---@param context? {bufnr: integer}
---@param handler? lsp-handler only called if a server command ---@param handler? lsp.Handler only called if a server command
function client._exec_cmd(command, context, handler) function client._exec_cmd(command, context, handler)
context = vim.deepcopy(context or {}) context = vim.deepcopy(context or {}) --[[@as lsp.HandlerContext]]
context.bufnr = context.bufnr or api.nvim_get_current_buf() context.bufnr = context.bufnr or api.nvim_get_current_buf()
context.client_id = client.id context.client_id = client.id
local cmdname = command.command local cmdname = command.command
@@ -1749,29 +1763,32 @@ function lsp.start_client(config)
return client_id return client_id
end end
---@private
---@fn text_document_did_change_handler(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size)
--- Notify all attached clients that a buffer has changed. --- Notify all attached clients that a buffer has changed.
local text_document_did_change_handler ---@param _ integer
do ---@param bufnr integer
text_document_did_change_handler = function( ---@param changedtick integer
_, ---@param firstline integer
bufnr, ---@param lastline integer
changedtick, ---@param new_lastline integer
firstline, ---@return true?
lastline, local function text_document_did_change_handler(
new_lastline _,
) bufnr,
-- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached changedtick,
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then firstline,
return true lastline,
end new_lastline
util.buf_versions[bufnr] = changedtick )
changetracking.send_changes(bufnr, firstline, lastline, new_lastline) -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
return true
end end
util.buf_versions[bufnr] = changedtick
changetracking.send_changes(bufnr, firstline, lastline, new_lastline)
end end
---Buffer lifecycle handler for textDocument/didSave ---Buffer lifecycle handler for textDocument/didSave
--- @param bufnr integer
local function text_document_did_save_handler(bufnr) local function text_document_did_save_handler(bufnr)
bufnr = resolve_bufnr(bufnr) bufnr = resolve_bufnr(bufnr)
local uri = vim.uri_from_bufnr(bufnr) local uri = vim.uri_from_bufnr(bufnr)
@@ -1797,7 +1814,7 @@ local function text_document_did_save_handler(bufnr)
end end
local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save') local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save')
if save_capability then if save_capability then
local included_text local included_text --- @type string?
if type(save_capability) == 'table' and save_capability.includeText then if type(save_capability) == 'table' and save_capability.includeText then
included_text = text(bufnr) included_text = text(bufnr)
end end
@@ -1826,8 +1843,9 @@ function lsp.buf_attach_client(bufnr, client_id)
}) })
bufnr = resolve_bufnr(bufnr) bufnr = resolve_bufnr(bufnr)
if not api.nvim_buf_is_loaded(bufnr) then if not api.nvim_buf_is_loaded(bufnr) then
local _ = log.warn() if log.warn() then
and log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr)) log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
end
return false return false
end end
local buffer_client_ids = all_buffer_active_clients[bufnr] local buffer_client_ids = all_buffer_active_clients[bufnr]
@@ -2087,7 +2105,7 @@ api.nvim_create_autocmd('VimLeavePre', {
client.stop() client.stop()
end end
local timeouts = {} local timeouts = {} --- @type table<integer,integer>
local max_timeout = 0 local max_timeout = 0
local send_kill = false local send_kill = false
@@ -2134,7 +2152,7 @@ api.nvim_create_autocmd('VimLeavePre', {
---@param bufnr (integer) Buffer handle, or 0 for current. ---@param bufnr (integer) Buffer handle, or 0 for current.
---@param method (string) LSP method name ---@param method (string) LSP method name
---@param params table|nil Parameters to send to the server ---@param params table|nil Parameters to send to the server
---@param handler? lsp-handler See |lsp-handler| ---@param handler? lsp.Handler See |lsp-handler|
--- If nil, follows resolution strategy defined in |lsp-handler-configuration| --- If nil, follows resolution strategy defined in |lsp-handler-configuration|
--- ---
---@return table<integer, integer> client_request_ids Map of client-id:request-id pairs ---@return table<integer, integer> client_request_ids Map of client-id:request-id pairs
@@ -2152,7 +2170,7 @@ function lsp.buf_request(bufnr, method, params, handler)
bufnr = resolve_bufnr(bufnr) bufnr = resolve_bufnr(bufnr)
local method_supported = false local method_supported = false
local clients = lsp.get_clients({ bufnr = bufnr }) local clients = lsp.get_clients({ bufnr = bufnr })
local client_request_ids = {} local client_request_ids = {} --- @type table<integer,integer>
for _, client in ipairs(clients) do for _, client in ipairs(clients) do
if client.supports_method(method, { bufnr = bufnr }) then if client.supports_method(method, { bufnr = bufnr }) then
method_supported = true method_supported = true
@@ -2194,7 +2212,7 @@ end
--- a `client_id:result` map. --- a `client_id:result` map.
---@return function cancel Function that cancels all requests. ---@return function cancel Function that cancels all requests.
function lsp.buf_request_all(bufnr, method, params, handler) function lsp.buf_request_all(bufnr, method, params, handler)
local results = {} local results = {} --- @type table<integer,{error:string, result:any}>
local result_count = 0 local result_count = 0
local expected_result_count = 0 local expected_result_count = 0
@@ -2324,6 +2342,7 @@ function lsp.formatexpr(opts)
local params = util.make_formatting_params() local params = util.make_formatting_params()
local end_line = vim.fn.getline(end_lnum) --[[@as string]] local end_line = vim.fn.getline(end_lnum) --[[@as string]]
local end_col = util._str_utfindex_enc(end_line, nil, client.offset_encoding) local end_col = util._str_utfindex_enc(end_line, nil, client.offset_encoding)
--- @cast params +lsp.DocumentRangeFormattingParams
params.range = { params.range = {
start = { start = {
line = start_lnum - 1, line = start_lnum - 1,
@@ -2378,7 +2397,7 @@ end
---@return table result is table of (client_id, client) pairs ---@return table result is table of (client_id, client) pairs
---@deprecated Use |vim.lsp.get_clients()| instead. ---@deprecated Use |vim.lsp.get_clients()| instead.
function lsp.buf_get_clients(bufnr) function lsp.buf_get_clients(bufnr)
local result = {} local result = {} --- @type table<integer,lsp.Client>
for _, client in ipairs(lsp.get_clients({ bufnr = resolve_bufnr(bufnr) })) do for _, client in ipairs(lsp.get_clients({ bufnr = resolve_bufnr(bufnr) })) do
result[client.id] = client result[client.id] = client
end end
@@ -2432,7 +2451,7 @@ function lsp.for_each_buffer_client(bufnr, fn)
end end
--- Function to manage overriding defaults for LSP handlers. --- Function to manage overriding defaults for LSP handlers.
---@param handler (function) See |lsp-handler| ---@param handler (lsp.Handler) See |lsp-handler|
---@param override_config (table) Table containing the keys to override behavior of the {handler} ---@param override_config (table) Table containing the keys to override behavior of the {handler}
function lsp.with(handler, override_config) function lsp.with(handler, override_config)
return function(err, result, ctx, config) return function(err, result, ctx, config)
@@ -2497,6 +2516,7 @@ end
--- arguments?: any[] --- arguments?: any[]
--- ---
--- The second argument is the `ctx` of |lsp-handler| --- The second argument is the `ctx` of |lsp-handler|
--- @type table<string,function>
lsp.commands = setmetatable({}, { lsp.commands = setmetatable({}, {
__newindex = function(tbl, key, value) __newindex = function(tbl, key, value)
assert(type(key) == 'string', 'The key for commands in `vim.lsp.commands` must be a string') assert(type(key) == 'string', 'The key for commands in `vim.lsp.commands` must be a string')

View File

@@ -1,13 +1,14 @@
---@meta ---@meta
error('Cannot require a meta file') error('Cannot require a meta file')
---@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: lsp.HandlerContext, config: table|nil): any? ---@alias lsp.Handler fun(err: lsp.ResponseError?, result: any, context: lsp.HandlerContext, config?: table): ...any
---@class lsp.HandlerContext ---@class lsp.HandlerContext
---@field method string ---@field method string
---@field client_id integer ---@field client_id integer
---@field bufnr? integer ---@field bufnr? integer
---@field params? any ---@field params? any
---@field version? integer
---@class lsp.ResponseError ---@class lsp.ResponseError
---@field code integer ---@field code integer

View File

@@ -49,7 +49,7 @@ local function request_with_options(name, params, options)
local req_handler local req_handler
if options then if options then
req_handler = function(err, result, ctx, config) req_handler = function(err, result, ctx, config)
local client = vim.lsp.get_client_by_id(ctx.client_id) local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
local handler = client.handlers[name] or vim.lsp.handlers[name] local handler = client.handlers[name] or vim.lsp.handlers[name]
handler(err, result, ctx, vim.tbl_extend('force', config or {}, options)) handler(err, result, ctx, vim.tbl_extend('force', config or {}, options))
end end
@@ -299,12 +299,12 @@ function M.rename(new_name, options)
)[1] )[1]
end end
local try_use_client local function try_use_client(idx, client)
try_use_client = function(idx, client)
if not client then if not client then
return return
end end
--- @param name string
local function rename(name) local function rename(name)
local params = util.make_position_params(win, client.offset_encoding) local params = util.make_position_params(win, client.offset_encoding)
params.newName = name params.newName = name

View File

@@ -6,7 +6,7 @@ local M = {}
--- bufnr → true|nil --- bufnr → true|nil
--- to throttle refreshes to at most one at a time --- to throttle refreshes to at most one at a time
local active_refreshes = {} local active_refreshes = {} --- @type table<integer,true>
---@type table<integer, table<integer, lsp.CodeLens[]>> ---@type table<integer, table<integer, lsp.CodeLens[]>>
--- bufnr -> client_id -> lenses --- bufnr -> client_id -> lenses
@@ -75,7 +75,7 @@ end
function M.run() function M.run()
local line = api.nvim_win_get_cursor(0)[1] local line = api.nvim_win_get_cursor(0)[1]
local bufnr = api.nvim_get_current_buf() local bufnr = api.nvim_get_current_buf()
local options = {} local options = {} --- @type {client: integer, lens: lsp.CodeLens}[]
local lenses_by_client = lens_cache_by_buf[bufnr] or {} local lenses_by_client = lens_cache_by_buf[bufnr] or {}
for client, lenses in pairs(lenses_by_client) do for client, lenses in pairs(lenses_by_client) do
for _, lens in pairs(lenses) do for _, lens in pairs(lenses) do
@@ -230,6 +230,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
if lens.command then if lens.command then
countdown() countdown()
else else
assert(client)
client.request('codeLens/resolve', lens, function(_, result) client.request('codeLens/resolve', lens, function(_, result)
if api.nvim_buf_is_loaded(bufnr) and result and result.command then if api.nvim_buf_is_loaded(bufnr) and result and result.command then
lens.command = result.command lens.command = result.command
@@ -257,10 +258,13 @@ end
--- |lsp-handler| for the method `textDocument/codeLens` --- |lsp-handler| for the method `textDocument/codeLens`
--- ---
---@param ctx lsp.HandlerContext
function M.on_codelens(err, result, ctx, _) function M.on_codelens(err, result, ctx, _)
if err then if err then
active_refreshes[ctx.bufnr] = nil active_refreshes[assert(ctx.bufnr)] = nil
local _ = log.error() and log.error('codelens', err) if log.error() then
log.error('codelens', err)
end
return return
end end
@@ -270,7 +274,7 @@ function M.on_codelens(err, result, ctx, _)
-- once resolved. -- once resolved.
M.display(result, ctx.bufnr, ctx.client_id) M.display(result, ctx.bufnr, ctx.client_id)
resolve_lenses(result, ctx.bufnr, ctx.client_id, function() resolve_lenses(result, ctx.bufnr, ctx.client_id, function()
active_refreshes[ctx.bufnr] = nil active_refreshes[assert(ctx.bufnr)] = nil
M.display(result, ctx.bufnr, ctx.client_id) M.display(result, ctx.bufnr, ctx.client_id)
end) end)
end end

View File

@@ -37,6 +37,10 @@ local function severity_vim_to_lsp(severity)
return severity return severity
end end
---@param lines string[]
---@param lnum integer
---@param col integer
---@param offset_encoding string
---@return integer ---@return integer
local function line_byte_from_position(lines, lnum, col, offset_encoding) local function line_byte_from_position(lines, lnum, col, offset_encoding)
if not lines or offset_encoding == 'utf-8' then if not lines or offset_encoding == 'utf-8' then
@@ -52,6 +56,8 @@ local function line_byte_from_position(lines, lnum, col, offset_encoding)
return col return col
end end
---@param bufnr integer
---@return string[]
local function get_buf_lines(bufnr) local function get_buf_lines(bufnr)
if vim.api.nvim_buf_is_loaded(bufnr) then if vim.api.nvim_buf_is_loaded(bufnr) then
return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
@@ -223,6 +229,7 @@ end
--- ) --- )
--- ``` --- ```
--- ---
---@param ctx lsp.HandlerContext
---@param config table Configuration table (see |vim.diagnostic.config()|). ---@param config table Configuration table (see |vim.diagnostic.config()|).
function M.on_publish_diagnostics(_, result, ctx, config) function M.on_publish_diagnostics(_, result, ctx, config)
local client_id = ctx.client_id local client_id = ctx.client_id
@@ -284,6 +291,7 @@ end
--- ) --- )
--- ``` --- ```
--- ---
---@param ctx lsp.HandlerContext
---@param config table Configuration table (see |vim.diagnostic.config()|). ---@param config table Configuration table (see |vim.diagnostic.config()|).
function M.on_diagnostic(_, result, ctx, config) function M.on_diagnostic(_, result, ctx, config)
local client_id = ctx.client_id local client_id = ctx.client_id
@@ -400,6 +408,7 @@ end
local bufstates = {} local bufstates = {}
--- Disable pull diagnostics for a buffer --- Disable pull diagnostics for a buffer
--- @param bufnr integer
--- @private --- @private
local function disable(bufnr) local function disable(bufnr)
local bufstate = bufstates[bufnr] local bufstate = bufstates[bufnr]

View File

@@ -4,6 +4,7 @@ local ms = protocol.Methods
local util = require('vim.lsp.util') local util = require('vim.lsp.util')
local api = vim.api local api = vim.api
--- @type table<string,lsp.Handler>
local M = {} local M = {}
-- FIXME: DOC: Expose in vimdocs -- FIXME: DOC: Expose in vimdocs
@@ -108,8 +109,7 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
M[ms.client_registerCapability] = function(_, result, ctx) M[ms.client_registerCapability] = function(_, result, ctx)
local client_id = ctx.client_id local client_id = ctx.client_id
---@type lsp.Client local client = assert(vim.lsp.get_client_by_id(client_id))
local client = vim.lsp.get_client_by_id(client_id)
client.dynamic_capabilities:register(result.registrations) client.dynamic_capabilities:register(result.registrations)
for bufnr, _ in pairs(client.attached_buffers) do for bufnr, _ in pairs(client.attached_buffers) do
@@ -139,7 +139,7 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
M[ms.client_unregisterCapability] = function(_, result, ctx) M[ms.client_unregisterCapability] = function(_, result, ctx)
local client_id = ctx.client_id local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id) local client = assert(vim.lsp.get_client_by_id(client_id))
client.dynamic_capabilities:unregister(result.unregisterations) client.dynamic_capabilities:unregister(result.unregisterations)
for _, unreg in ipairs(result.unregisterations) do for _, unreg in ipairs(result.unregisterations) do
@@ -158,7 +158,7 @@ M[ms.workspace_applyEdit] = function(_, workspace_edit, ctx)
) )
-- TODO(ashkan) Do something more with label? -- TODO(ashkan) Do something more with label?
local client_id = ctx.client_id local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id) local client = assert(vim.lsp.get_client_by_id(client_id))
if workspace_edit.label then if workspace_edit.label then
print('Workspace edit', workspace_edit.label) print('Workspace edit', workspace_edit.label)
end end
@@ -231,22 +231,23 @@ end
M[ms.textDocument_references] = function(_, result, ctx, config) M[ms.textDocument_references] = function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then if not result or vim.tbl_isempty(result) then
vim.notify('No references found') vim.notify('No references found')
else return
local client = vim.lsp.get_client_by_id(ctx.client_id) end
config = config or {}
local title = 'References'
local items = util.locations_to_items(result, client.offset_encoding)
if config.loclist then local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx }) config = config or {}
api.nvim_command('lopen') local title = 'References'
elseif config.on_list then local items = util.locations_to_items(result, client.offset_encoding)
assert(type(config.on_list) == 'function', 'on_list is not a function')
config.on_list({ title = title, items = items, context = ctx }) if config.loclist then
else vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx })
vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx }) api.nvim_command('lopen')
api.nvim_command('botright copen') elseif config.on_list then
end assert(type(config.on_list) == 'function', 'on_list is not a function')
config.on_list({ title = title, items = items, context = ctx })
else
vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx })
api.nvim_command('botright copen')
end end
end end
@@ -259,26 +260,27 @@ end
--- ---
---@param map_result function `((resp, bufnr) -> list)` to convert the response ---@param map_result function `((resp, bufnr) -> list)` to convert the response
---@param entity string name of the resource used in a `not found` error message ---@param entity string name of the resource used in a `not found` error message
---@param title_fn function Function to call to generate list title ---@param title_fn fun(ctx: lsp.HandlerContext): string Function to call to generate list title
---@return lsp.Handler
local function response_to_list(map_result, entity, title_fn) local function response_to_list(map_result, entity, title_fn)
return function(_, result, ctx, config) return function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then if not result or vim.tbl_isempty(result) then
vim.notify('No ' .. entity .. ' found') vim.notify('No ' .. entity .. ' found')
else return
config = config or {} end
local title = title_fn(ctx) config = config or {}
local items = map_result(result, ctx.bufnr) local title = title_fn(ctx)
local items = map_result(result, ctx.bufnr)
if config.loclist then if config.loclist then
vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx }) vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx })
api.nvim_command('lopen') api.nvim_command('lopen')
elseif config.on_list then elseif config.on_list then
assert(type(config.on_list) == 'function', 'on_list is not a function') assert(type(config.on_list) == 'function', 'on_list is not a function')
config.on_list({ title = title, items = items, context = ctx }) config.on_list({ title = title, items = items, context = ctx })
else else
vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx }) vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx })
api.nvim_command('botright copen') api.nvim_command('botright copen')
end
end end
end end
end end
@@ -304,7 +306,7 @@ M[ms.textDocument_rename] = function(_, result, ctx, _)
vim.notify("Language server couldn't provide rename result", vim.log.levels.INFO) vim.notify("Language server couldn't provide rename result", vim.log.levels.INFO)
return return
end end
local client = vim.lsp.get_client_by_id(ctx.client_id) local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
util.apply_workspace_edit(result, client.offset_encoding) util.apply_workspace_edit(result, client.offset_encoding)
end end
@@ -313,7 +315,7 @@ M[ms.textDocument_rangeFormatting] = function(_, result, ctx, _)
if not result then if not result then
return return
end end
local client = vim.lsp.get_client_by_id(ctx.client_id) local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding) util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end end
@@ -322,7 +324,7 @@ M[ms.textDocument_formatting] = function(_, result, ctx, _)
if not result then if not result then
return return
end end
local client = vim.lsp.get_client_by_id(ctx.client_id) local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding) util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end end
@@ -331,7 +333,8 @@ M[ms.textDocument_completion] = function(_, result, _, _)
if vim.tbl_isempty(result or {}) then if vim.tbl_isempty(result or {}) then
return return
end end
local row, col = unpack(api.nvim_win_get_cursor(0)) local cursor = api.nvim_win_get_cursor(0)
local row, col = cursor[1], cursor[2]
local line = assert(api.nvim_buf_get_lines(0, row - 1, row, false)[1]) local line = assert(api.nvim_buf_get_lines(0, row - 1, row, false)[1])
local line_to_cursor = line:sub(col + 1) local line_to_cursor = line:sub(col + 1)
local textMatch = vim.fn.match(line_to_cursor, '\\k*$') local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
@@ -354,6 +357,7 @@ end
--- ) --- )
--- ``` --- ```
--- ---
---@param ctx lsp.HandlerContext
---@param config table Configuration table. ---@param config table Configuration table.
--- - border: (default=nil) --- - border: (default=nil)
--- - Add borders to the floating window --- - Add borders to the floating window
@@ -394,14 +398,16 @@ M[ms.textDocument_hover] = M.hover
--- Jumps to a location. Used as a handler for multiple LSP methods. --- Jumps to a location. Used as a handler for multiple LSP methods.
---@param _ nil not used ---@param _ nil not used
---@param result (table) result of LSP method; a location or a list of locations. ---@param result (table) result of LSP method; a location or a list of locations.
---@param ctx (table) table containing the context of the request, including the method ---@param ctx (lsp.HandlerContext) table containing the context of the request, including the method
---(`textDocument/definition` can return `Location` or `Location[]` ---(`textDocument/definition` can return `Location` or `Location[]`
local function location_handler(_, result, ctx, config) local function location_handler(_, result, ctx, config)
if result == nil or vim.tbl_isempty(result) then if result == nil or vim.tbl_isempty(result) then
local _ = log.info() and log.info(ctx.method, 'No location found') if log.info() then
log.info(ctx.method, 'No location found')
end
return nil return nil
end end
local client = vim.lsp.get_client_by_id(ctx.client_id) local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
config = config or {} config = config or {}
@@ -450,7 +456,7 @@ M[ms.textDocument_implementation] = location_handler
--- ``` --- ```
--- ---
---@param result table Response from the language server ---@param result table Response from the language server
---@param ctx table Client context ---@param ctx lsp.HandlerContext Client context
---@param config table Configuration table. ---@param config table Configuration table.
--- - border: (default=nil) --- - border: (default=nil)
--- - Add borders to the floating window --- - Add borders to the floating window
@@ -470,7 +476,7 @@ function M.signature_help(_, result, ctx, config)
end end
return return
end end
local client = vim.lsp.get_client_by_id(ctx.client_id) local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
local triggers = local triggers =
vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters') vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
local ft = vim.bo[ctx.bufnr].filetype local ft = vim.bo[ctx.bufnr].filetype

View File

@@ -17,13 +17,17 @@ local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {})
--- |lsp-handler| for the method `textDocument/inlayHint` --- |lsp-handler| for the method `textDocument/inlayHint`
--- Store hints for a specific buffer and client --- Store hints for a specific buffer and client
---@param result lsp.InlayHint[]?
---@param ctx lsp.HandlerContext
---@private ---@private
function M.on_inlayhint(err, result, ctx, _) function M.on_inlayhint(err, result, ctx, _)
if err then if err then
local _ = log.error() and log.error('inlayhint', err) if log.error() then
log.error('inlayhint', err)
end
return return
end end
local bufnr = ctx.bufnr local bufnr = assert(ctx.bufnr)
if util.buf_versions[bufnr] ~= ctx.version then if util.buf_versions[bufnr] ~= ctx.version then
return return
end end
@@ -40,7 +44,7 @@ function M.on_inlayhint(err, result, ctx, _)
bufstate.version = ctx.version bufstate.version = ctx.version
end end
local hints_by_client = bufstate.client_hint local hints_by_client = bufstate.client_hint
local client = vim.lsp.get_client_by_id(client_id) local client = assert(vim.lsp.get_client_by_id(client_id))
local new_hints_by_lnum = vim.defaulttable() local new_hints_by_lnum = vim.defaulttable()
local num_unprocessed = #result local num_unprocessed = #result
@@ -52,6 +56,8 @@ function M.on_inlayhint(err, result, ctx, _)
end end
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
---@param position lsp.Position
---@return integer
local function pos_to_byte(position) local function pos_to_byte(position)
local col = position.character local col = position.character
if col > 0 then if col > 0 then
@@ -78,6 +84,7 @@ function M.on_inlayhint(err, result, ctx, _)
end end
--- |lsp-handler| for the method `textDocument/inlayHint/refresh` --- |lsp-handler| for the method `textDocument/inlayHint/refresh`
---@param ctx lsp.HandlerContext
---@private ---@private
function M.on_refresh(err, _, ctx, _) function M.on_refresh(err, _, ctx, _)
if err then if err then
@@ -212,7 +219,7 @@ local function clear(bufnr)
end end
local bufstate = bufstates[bufnr] local bufstate = bufstates[bufnr]
local client_lens = (bufstate or {}).client_hint or {} local client_lens = (bufstate or {}).client_hint or {}
local client_ids = vim.tbl_keys(client_lens) local client_ids = vim.tbl_keys(client_lens) --- @type integer[]
for _, iter_client_id in ipairs(client_ids) do for _, iter_client_id in ipairs(client_ids) do
if bufstate then if bufstate then
bufstate.client_hint[iter_client_id] = {} bufstate.client_hint[iter_client_id] = {}
@@ -236,7 +243,7 @@ end
--- Refresh inlay hints, only if we have attached clients that support it --- Refresh inlay hints, only if we have attached clients that support it
---@param bufnr (integer) Buffer handle, or 0 for current ---@param bufnr (integer) Buffer handle, or 0 for current
---@param opts? table Additional options to pass to util._refresh ---@param opts? lsp.util.RefreshOptions Additional options to pass to util._refresh
---@private ---@private
local function _refresh(bufnr, opts) local function _refresh(bufnr, opts)
opts = opts or {} opts = opts or {}
@@ -312,7 +319,7 @@ api.nvim_set_decoration_provider(namespace, {
if bufstate.version ~= util.buf_versions[bufnr] then if bufstate.version ~= util.buf_versions[bufnr] then
return return
end end
local hints_by_client = bufstate.client_hint local hints_by_client = assert(bufstate.client_hint)
for lnum = topline, botline do for lnum = topline, botline do
if bufstate.applied[lnum] ~= bufstate.version then if bufstate.applied[lnum] ~= bufstate.version then
@@ -321,14 +328,15 @@ api.nvim_set_decoration_provider(namespace, {
local line_hints = hints_by_lnum[lnum] or {} local line_hints = hints_by_lnum[lnum] or {}
for _, hint in pairs(line_hints) do for _, hint in pairs(line_hints) do
local text = '' local text = ''
if type(hint.label) == 'string' then local label = hint.label
text = hint.label if type(label) == 'string' then
text = label
else else
for _, part in ipairs(hint.label) do for _, part in ipairs(label) do
text = text .. part.value text = text .. part.value
end end
end end
local vt = {} local vt = {} --- @type {[1]: string, [2]: string?}[]
if hint.paddingLeft then if hint.paddingLeft then
vt[#vt + 1] = { ' ' } vt[#vt + 1] = { ' ' }
end end

View File

@@ -91,7 +91,9 @@ do
-- --
-- Recommended usage: -- Recommended usage:
-- ``` -- ```
-- local _ = log.warn() and log.warn("123") -- if log.warn() then
-- log.warn("123")
-- end
-- ``` -- ```
-- --
-- This way you can avoid string allocations if the log level isn't high enough. -- This way you can avoid string allocations if the log level isn't high enough.

View File

@@ -891,7 +891,7 @@ end
--- Creates a normalized object describing LSP server capabilities. --- Creates a normalized object describing LSP server capabilities.
---@param server_capabilities table Table of capabilities supported by the server ---@param server_capabilities table Table of capabilities supported by the server
---@return table|nil Normalized table of capabilities ---@return lsp.ServerCapabilities|nil Normalized table of capabilities
function protocol.resolve_capabilities(server_capabilities) function protocol.resolve_capabilities(server_capabilities)
local TextDocumentSyncKind = protocol.TextDocumentSyncKind local TextDocumentSyncKind = protocol.TextDocumentSyncKind
local textDocumentSync = server_capabilities.textDocumentSync local textDocumentSync = server_capabilities.textDocumentSync

View File

@@ -26,23 +26,42 @@ local function format_message_with_content_length(encoded_message)
}) })
end end
local function log_error(...)
if log.error() then
log.error(...)
end
end
local function log_info(...)
if log.info() then
log.info(...)
end
end
local function log_debug(...)
if log.debug() then
log.debug(...)
end
end
--- Parses an LSP Message's header --- Parses an LSP Message's header
--- ---
---@param header string: The header to parse. ---@param header string: The header to parse.
---@return table # parsed headers ---@return table # parsed headers
local function parse_headers(header) local function parse_headers(header)
assert(type(header) == 'string', 'header must be a string') assert(type(header) == 'string', 'header must be a string')
local headers = {} local headers = {} --- @type table<string,string>
for line in vim.gsplit(header, '\r\n', { plain = true }) do for line in vim.gsplit(header, '\r\n', { plain = true }) do
if line == '' then if line == '' then
break break
end end
--- @type string?, string?
local key, value = line:match('^%s*(%S+)%s*:%s*(.+)%s*$') local key, value = line:match('^%s*(%S+)%s*:%s*(.+)%s*$')
if key then if key then
key = key:lower():gsub('%-', '_') key = key:lower():gsub('%-', '_') --- @type string
headers[key] = value headers[key] = value
else else
local _ = log.error() and log.error('invalid header line %q', line) log_error('invalid header line %q', line)
error(string.format('invalid header line %q', line)) error(string.format('invalid header line %q', line))
end end
end end
@@ -96,17 +115,17 @@ local function request_parser_loop()
end end
local body = table.concat(body_chunks) local body = table.concat(body_chunks)
-- Yield our data. -- Yield our data.
buffer = rest
.. ( --- @type string
coroutine.yield(headers, body) local data = coroutine.yield(headers, body)
or error('Expected more data for the body. The server may have died.') or error('Expected more data for the body. The server may have died.')
) -- TODO hmm. buffer = rest .. data
else else
-- Get more data since we don't have enough. -- Get more data since we don't have enough.
buffer = buffer --- @type string
.. ( local data = coroutine.yield()
coroutine.yield() or error('Expected more data for the header. The server may have died.') or error('Expected more data for the header. The server may have died.')
) -- TODO hmm. buffer = buffer .. data
end end
end end
end end
@@ -138,7 +157,7 @@ function M.format_rpc_error(err)
-- There is ErrorCodes in the LSP specification, -- There is ErrorCodes in the LSP specification,
-- but in ResponseError.code it is not used and the actual type is number. -- but in ResponseError.code it is not used and the actual type is number.
local code local code --- @type string
if protocol.ErrorCodes[err.code] then if protocol.ErrorCodes[err.code] then
code = string.format('code_name = %s,', protocol.ErrorCodes[err.code]) code = string.format('code_name = %s,', protocol.ErrorCodes[err.code])
else else
@@ -174,48 +193,51 @@ function M.rpc_response_error(code, message, data)
}) })
end end
local default_dispatchers = {} --- @class vim.rpc.Dispatchers
--- @field notification fun(method: string, params: table)
--- @field server_request fun(method: string, params: table): any?, string?
--- @field on_exit fun(code: integer, signal: integer)
--- @field on_error fun(code: integer, err: any)
---@private --- @type vim.rpc.Dispatchers
--- Default dispatcher for notifications sent to an LSP server. local default_dispatchers = {
--- --- Default dispatcher for notifications sent to an LSP server.
---@param method (string) The invoked LSP method ---
---@param params (table): Parameters for the invoked LSP method ---@param method (string) The invoked LSP method
function default_dispatchers.notification(method, params) ---@param params (table): Parameters for the invoked LSP method
local _ = log.debug() and log.debug('notification', method, params) notification = function(method, params)
end log_debug('notification', method, params)
end,
---@private --- Default dispatcher for requests sent to an LSP server.
--- Default dispatcher for requests sent to an LSP server. ---
--- ---@param method (string) The invoked LSP method
---@param method (string) The invoked LSP method ---@param params (table): Parameters for the invoked LSP method
---@param params (table): Parameters for the invoked LSP method ---@return nil
---@return nil ---@return table, `vim.lsp.protocol.ErrorCodes.MethodNotFound`
---@return table `vim.lsp.protocol.ErrorCodes.MethodNotFound` server_request = function(method, params)
function default_dispatchers.server_request(method, params) log_debug('server_request', method, params)
local _ = log.debug() and log.debug('server_request', method, params) return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound) end,
end
---@private --- Default dispatcher for when a client exits.
--- Default dispatcher for when a client exits. ---
--- ---@param code (integer): Exit code
---@param code (integer): Exit code ---@param signal (integer): Number describing the signal used to terminate (if
---@param signal (integer): Number describing the signal used to terminate (if ---any)
---any) on_exit = function(code, signal)
function default_dispatchers.on_exit(code, signal) log_info('client_exit', { code = code, signal = signal })
local _ = log.info() and log.info('client_exit', { code = code, signal = signal }) end,
end
---@private --- Default dispatcher for client errors.
--- Default dispatcher for client errors. ---
--- ---@param code (integer): Error code
---@param code (integer): Error code ---@param err (any): Details about the error
---@param err (any): Details about the error ---any)
---any) on_error = function(code, err)
function default_dispatchers.on_error(code, err) log_error('client_error:', M.client_errors[code], err)
local _ = log.error() and log.error('client_error:', M.client_errors[code], err) end,
end }
---@private ---@private
function M.create_read_loop(handle_body, on_no_chunk, on_error) function M.create_read_loop(handle_body, on_no_chunk, on_error)
@@ -248,8 +270,8 @@ end
---@class RpcClient ---@class RpcClient
---@field message_index integer ---@field message_index integer
---@field message_callbacks table ---@field message_callbacks table<integer,function>
---@field notify_reply_callbacks table ---@field notify_reply_callbacks table<integer,function>
---@field transport table ---@field transport table
---@field dispatchers table ---@field dispatchers table
@@ -258,7 +280,7 @@ local Client = {}
---@private ---@private
function Client:encode_and_send(payload) function Client:encode_and_send(payload)
local _ = log.debug() and log.debug('rpc.send', payload) log_debug('rpc.send', payload)
if self.transport.is_closing() then if self.transport.is_closing() then
return false return false
end end
@@ -267,7 +289,7 @@ function Client:encode_and_send(payload)
return true return true
end end
---@private ---@package
--- Sends a notification to the LSP server. --- Sends a notification to the LSP server.
---@param method (string) The invoked LSP method ---@param method (string) The invoked LSP method
---@param params (any): Parameters for the invoked LSP method ---@param params (any): Parameters for the invoked LSP method
@@ -291,7 +313,7 @@ function Client:send_response(request_id, err, result)
}) })
end end
---@private ---@package
--- Sends a request to the LSP server and runs {callback} upon response. --- Sends a request to the LSP server and runs {callback} upon response.
--- ---
---@param method (string) The invoked LSP method ---@param method (string) The invoked LSP method
@@ -329,7 +351,7 @@ function Client:request(method, params, callback, notify_reply_callback)
end end
end end
---@private ---@package
function Client:on_error(errkind, ...) function Client:on_error(errkind, ...)
assert(M.client_errors[errkind]) assert(M.client_errors[errkind])
-- TODO what to do if this fails? -- TODO what to do if this fails?
@@ -354,17 +376,17 @@ end
-- time and log them. This would require storing the timestamp. I could call -- time and log them. This would require storing the timestamp. I could call
-- them with an error then, perhaps. -- them with an error then, perhaps.
---@private ---@package
function Client:handle_body(body) function Client:handle_body(body)
local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } }) local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } })
if not ok then if not ok then
self:on_error(M.client_errors.INVALID_SERVER_JSON, decoded) self:on_error(M.client_errors.INVALID_SERVER_JSON, decoded)
return return
end end
local _ = log.debug() and log.debug('rpc.receive', decoded) log_debug('rpc.receive', decoded)
if type(decoded.method) == 'string' and decoded.id then if type(decoded.method) == 'string' and decoded.id then
local err local err --- @type table?
-- Schedule here so that the users functions don't trigger an error and -- Schedule here so that the users functions don't trigger an error and
-- we can still use the result. -- we can still use the result.
schedule(function() schedule(function()
@@ -376,11 +398,10 @@ function Client:handle_body(body)
decoded.method, decoded.method,
decoded.params decoded.params
) )
local _ = log.debug() log_debug(
and log.debug( 'server_request: callback result',
'server_request: callback result', { status = status, result = result, err = err }
{ status = status, result = result, err = err } )
)
if status then if status then
if result == nil and err == nil then if result == nil and err == nil then
error( error(
@@ -431,7 +452,7 @@ function Client:handle_body(body)
if decoded.error then if decoded.error then
local mute_error = false local mute_error = false
if decoded.error.code == protocol.ErrorCodes.RequestCancelled then if decoded.error.code == protocol.ErrorCodes.RequestCancelled then
local _ = log.debug() and log.debug('Received cancellation ack', decoded) log_debug('Received cancellation ack', decoded)
mute_error = true mute_error = true
end end
@@ -467,7 +488,7 @@ function Client:handle_body(body)
) )
else else
self:on_error(M.client_errors.NO_RESULT_CALLBACK_FOUND, decoded) self:on_error(M.client_errors.NO_RESULT_CALLBACK_FOUND, decoded)
local _ = log.error() and log.error('No callback found for server response id ' .. result_id) log_error('No callback found for server response id ' .. result_id)
end end
elseif type(decoded.method) == 'string' then elseif type(decoded.method) == 'string' then
-- Notification -- Notification
@@ -495,7 +516,14 @@ local function new_client(dispatchers, transport)
return setmetatable(state, { __index = Client }) return setmetatable(state, { __index = Client })
end end
--- @class RpcClientPublic
--- @field is_closing fun(): boolean
--- @field terminate fun()
--- @field request fun(method: string, params: table?, callback: function, notify_reply_callbacks?: function)
--- @field notify fun(methid: string, params: table?): boolean
---@param client RpcClient ---@param client RpcClient
---@return RpcClientPublic
local function public_client(client) local function public_client(client)
local result = {} local result = {}
@@ -531,12 +559,14 @@ local function public_client(client)
return result return result
end end
--- @param dispatchers vim.rpc.Dispatchers?
--- @return vim.rpc.Dispatchers
local function merge_dispatchers(dispatchers) local function merge_dispatchers(dispatchers)
if dispatchers then if dispatchers then
local user_dispatchers = dispatchers local user_dispatchers = dispatchers
dispatchers = {} dispatchers = {}
for dispatch_name, default_dispatch in pairs(default_dispatchers) do for dispatch_name, default_dispatch in pairs(default_dispatchers) do
local user_dispatcher = user_dispatchers[dispatch_name] local user_dispatcher = user_dispatchers[dispatch_name] --- @type function
if user_dispatcher then if user_dispatcher then
if type(user_dispatcher) ~= 'function' then if type(user_dispatcher) ~= 'function' then
error(string.format('dispatcher.%s must be a function', dispatch_name)) error(string.format('dispatcher.%s must be a function', dispatch_name))
@@ -547,8 +577,10 @@ local function merge_dispatchers(dispatchers)
then then
user_dispatcher = schedule_wrap(user_dispatcher) user_dispatcher = schedule_wrap(user_dispatcher)
end end
--- @diagnostic disable-next-line:no-unknown
dispatchers[dispatch_name] = user_dispatcher dispatchers[dispatch_name] = user_dispatcher
else else
--- @diagnostic disable-next-line:no-unknown
dispatchers[dispatch_name] = default_dispatch dispatchers[dispatch_name] = default_dispatch
end end
end end
@@ -567,7 +599,7 @@ end
function M.connect(host, port) function M.connect(host, port)
return function(dispatchers) return function(dispatchers)
dispatchers = merge_dispatchers(dispatchers) dispatchers = merge_dispatchers(dispatchers)
local tcp = uv.new_tcp() local tcp = assert(uv.new_tcp())
local closing = false local closing = false
local transport = { local transport = {
write = function(msg) write = function(msg)
@@ -624,15 +656,13 @@ end
--- server process. May contain: --- server process. May contain:
--- - {cwd} (string) Working directory for the LSP server process --- - {cwd} (string) Working directory for the LSP server process
--- - {env} (table) Additional environment variables for LSP server process --- - {env} (table) Additional environment variables for LSP server process
---@return table|nil Client RPC object, with these methods: ---@return RpcClientPublic|nil Client RPC object, with these methods:
--- - `notify()` |vim.lsp.rpc.notify()| --- - `notify()` |vim.lsp.rpc.notify()|
--- - `request()` |vim.lsp.rpc.request()| --- - `request()` |vim.lsp.rpc.request()|
--- - `is_closing()` returns a boolean indicating if the RPC is closing. --- - `is_closing()` returns a boolean indicating if the RPC is closing.
--- - `terminate()` terminates the RPC client. --- - `terminate()` terminates the RPC client.
function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
if log.info() then log_info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
end
validate({ validate({
cmd = { cmd, 's' }, cmd = { cmd, 's' },
@@ -671,8 +701,8 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
end) end)
local stderr_handler = function(_, chunk) local stderr_handler = function(_, chunk)
if chunk and log.error() then if chunk then
log.error('rpc', cmd, 'stderr', chunk) log_error('rpc', cmd, 'stderr', chunk)
end end
end end
@@ -697,13 +727,13 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
if not ok then if not ok then
local err = sysobj_or_err --[[@as string]] local err = sysobj_or_err --[[@as string]]
local msg = string.format('Spawning language server with cmd: `%s` failed', cmd) local sfx --- @type string
if string.match(err, 'ENOENT') then if string.match(err, 'ENOENT') then
msg = msg sfx = '. The language server is either not installed, missing from PATH, or not executable.'
.. '. The language server is either not installed, missing from PATH, or not executable.'
else else
msg = msg .. string.format(' with error message: %s', err) sfx = string.format(' with error message: %s', err)
end end
local msg = string.format('Spawning language server with cmd: `%s` failed%s', cmd, sfx)
vim.notify(msg, vim.log.levels.WARN) vim.notify(msg, vim.log.levels.WARN)
return return
end end

View File

@@ -10,7 +10,7 @@ local uv = vim.uv
--- @field start_col integer start column 0-based --- @field start_col integer start column 0-based
--- @field end_col integer end column 0-based --- @field end_col integer end column 0-based
--- @field type string token type as string --- @field type string token type as string
--- @field modifiers table token modifiers as a set. E.g., { static = true, readonly = true } --- @field modifiers table<string,boolean> token modifiers as a set. E.g., { static = true, readonly = true }
--- @field marked boolean whether this token has had extmarks applied --- @field marked boolean whether this token has had extmarks applied
--- ---
--- @class STCurrentResult --- @class STCurrentResult
@@ -21,8 +21,8 @@ local uv = vim.uv
--- @field namespace_cleared? boolean whether the namespace was cleared for this result yet --- @field namespace_cleared? boolean whether the namespace was cleared for this result yet
--- ---
--- @class STActiveRequest --- @class STActiveRequest
--- @field request_id integer the LSP request ID of the most recent request sent to the server --- @field request_id? integer the LSP request ID of the most recent request sent to the server
--- @field version integer the document version associated with the most recent request --- @field version? integer the document version associated with the most recent request
--- ---
--- @class STClientState --- @class STClientState
--- @field namespace integer --- @field namespace integer
@@ -72,9 +72,11 @@ end
--- Extracts modifier strings from the encoded number in the token array --- Extracts modifier strings from the encoded number in the token array
--- ---
---@param x integer
---@param modifiers_table table<integer,string>
---@return table<string, boolean> ---@return table<string, boolean>
local function modifiers_from_number(x, modifiers_table) local function modifiers_from_number(x, modifiers_table)
local modifiers = {} local modifiers = {} ---@type table<string,boolean>
local idx = 1 local idx = 1
while x > 0 do while x > 0 do
if bit.band(x, 1) == 1 then if bit.band(x, 1) == 1 then
@@ -89,20 +91,24 @@ end
--- Converts a raw token list to a list of highlight ranges used by the on_win callback --- Converts a raw token list to a list of highlight ranges used by the on_win callback
--- ---
---@param data integer[]
---@param bufnr integer
---@param client lsp.Client
---@param request STActiveRequest
---@return STTokenRange[] ---@return STTokenRange[]
local function tokens_to_ranges(data, bufnr, client, request) local function tokens_to_ranges(data, bufnr, client, request)
local legend = client.server_capabilities.semanticTokensProvider.legend local legend = client.server_capabilities.semanticTokensProvider.legend
local token_types = legend.tokenTypes local token_types = legend.tokenTypes
local token_modifiers = legend.tokenModifiers local token_modifiers = legend.tokenModifiers
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
local ranges = {} local ranges = {} ---@type STTokenRange[]
local start = uv.hrtime() local start = uv.hrtime()
local ms_to_ns = 1000 * 1000 local ms_to_ns = 1000 * 1000
local yield_interval_ns = 5 * ms_to_ns local yield_interval_ns = 5 * ms_to_ns
local co, is_main = coroutine.running() local co, is_main = coroutine.running()
local line local line ---@type integer?
local start_char = 0 local start_char = 0
for i = 1, #data, 5 do for i = 1, #data, 5 do
-- if this function is called from the main coroutine, let it run to completion with no yield -- if this function is called from the main coroutine, let it run to completion with no yield
@@ -167,6 +173,7 @@ end
--- ---
---@private ---@private
---@param bufnr integer ---@param bufnr integer
---@return STHighlighter
function STHighlighter.new(bufnr) function STHighlighter.new(bufnr)
local self = setmetatable({}, { __index = STHighlighter }) local self = setmetatable({}, { __index = STHighlighter })
@@ -221,7 +228,7 @@ function STHighlighter.new(bufnr)
return self return self
end end
---@private ---@package
function STHighlighter:destroy() function STHighlighter:destroy()
for client_id, _ in pairs(self.client_state) do for client_id, _ in pairs(self.client_state) do
self:detach(client_id) self:detach(client_id)
@@ -231,7 +238,7 @@ function STHighlighter:destroy()
STHighlighter.active[self.bufnr] = nil STHighlighter.active[self.bufnr] = nil
end end
---@private ---@package
function STHighlighter:attach(client_id) function STHighlighter:attach(client_id)
local state = self.client_state[client_id] local state = self.client_state[client_id]
if not state then if not state then
@@ -244,7 +251,7 @@ function STHighlighter:attach(client_id)
end end
end end
---@private ---@package
function STHighlighter:detach(client_id) function STHighlighter:detach(client_id)
local state = self.client_state[client_id] local state = self.client_state[client_id]
if state then if state then
@@ -267,7 +274,7 @@ end
--- Finally, if the request was successful, the requestId and document version --- Finally, if the request was successful, the requestId and document version
--- are saved to facilitate document synchronization in the response. --- are saved to facilitate document synchronization in the response.
--- ---
---@private ---@package
function STHighlighter:send_request() function STHighlighter:send_request()
local version = util.buf_versions[self.bufnr] local version = util.buf_versions[self.bufnr]
@@ -303,7 +310,8 @@ function STHighlighter:send_request()
-- look client up again using ctx.client_id instead of using a captured -- look client up again using ctx.client_id instead of using a captured
-- client object -- client object
local c = vim.lsp.get_client_by_id(ctx.client_id) local c = vim.lsp.get_client_by_id(ctx.client_id)
local highlighter = STHighlighter.active[ctx.bufnr] local bufnr = assert(ctx.bufnr)
local highlighter = STHighlighter.active[bufnr]
if not err and c and highlighter then if not err and c and highlighter then
coroutine.wrap(STHighlighter.process_response)(highlighter, response, c, version) coroutine.wrap(STHighlighter.process_response)(highlighter, response, c, version)
end end
@@ -328,6 +336,7 @@ end
--- Finally, a redraw command is issued to force nvim to redraw the screen to --- Finally, a redraw command is issued to force nvim to redraw the screen to
--- pick up changed highlight tokens. --- pick up changed highlight tokens.
--- ---
---@param response lsp.SemanticTokens|lsp.SemanticTokensDelta
---@private ---@private
function STHighlighter:process_response(response, client, version) function STHighlighter:process_response(response, client, version)
local state = self.client_state[client.id] local state = self.client_state[client.id]
@@ -348,15 +357,15 @@ function STHighlighter:process_response(response, client, version)
-- if we have a response to a delta request, update the state of our tokens -- if we have a response to a delta request, update the state of our tokens
-- appropriately. if it's a full response, just use that -- appropriately. if it's a full response, just use that
local tokens local tokens ---@type integer[]
local token_edits = response.edits local token_edits = response.edits
if token_edits then if token_edits then
table.sort(token_edits, function(a, b) table.sort(token_edits, function(a, b)
return a.start < b.start return a.start < b.start
end) end)
tokens = {} tokens = {} --- @type integer[]
local old_tokens = state.current_result.tokens local old_tokens = assert(state.current_result.tokens)
local idx = 1 local idx = 1
for _, token_edit in ipairs(token_edits) do for _, token_edit in ipairs(token_edits) do
vim.list_extend(tokens, old_tokens, idx, token_edit.start) vim.list_extend(tokens, old_tokens, idx, token_edit.start)
@@ -404,7 +413,9 @@ end
--- handler to avoid the "blink" that occurs due to the timing between the --- handler to avoid the "blink" that occurs due to the timing between the
--- response handler and the actual redraw. --- response handler and the actual redraw.
--- ---
---@private ---@package
---@param topline integer
---@param botline integer
function STHighlighter:on_win(topline, botline) function STHighlighter:on_win(topline, botline)
for client_id, state in pairs(self.client_state) do for client_id, state in pairs(self.client_state) do
local current_result = state.current_result local current_result = state.current_result
@@ -450,7 +461,7 @@ function STHighlighter:on_win(topline, botline)
end end
local ft = vim.bo[self.bufnr].filetype local ft = vim.bo[self.bufnr].filetype
local highlights = current_result.highlights local highlights = assert(current_result.highlights)
local first = lower_bound(highlights, topline, 1, #highlights + 1) local first = lower_bound(highlights, topline, 1, #highlights + 1)
local last = upper_bound(highlights, botline, first, #highlights + 1) - 1 local last = upper_bound(highlights, botline, first, #highlights + 1) - 1
@@ -480,7 +491,7 @@ end
--- Reset the buffer's highlighting state and clears the extmark highlights. --- Reset the buffer's highlighting state and clears the extmark highlights.
--- ---
---@private ---@package
function STHighlighter:reset() function STHighlighter:reset()
for client_id, state in pairs(self.client_state) do for client_id, state in pairs(self.client_state) do
api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1)
@@ -499,7 +510,7 @@ end
--- in the on_win callback. The rest of the current results are saved --- in the on_win callback. The rest of the current results are saved
--- in case the server supports delta requests. --- in case the server supports delta requests.
--- ---
---@private ---@package
---@param client_id integer ---@param client_id integer
function STHighlighter:mark_dirty(client_id) function STHighlighter:mark_dirty(client_id)
local state = self.client_state[client_id] local state = self.client_state[client_id]
@@ -521,7 +532,7 @@ function STHighlighter:mark_dirty(client_id)
end end
end end
---@private ---@package
function STHighlighter:on_change() function STHighlighter:on_change()
self:reset_timer() self:reset_timer()
if self.debounce > 0 then if self.debounce > 0 then
@@ -636,6 +647,9 @@ function M.stop(bufnr, client_id)
end end
end end
--- @class STTokenRangeInspect : STTokenRange
--- @field client_id integer
--- Return the semantic token(s) at the given position. --- Return the semantic token(s) at the given position.
--- If called without arguments, returns the token under the cursor. --- If called without arguments, returns the token under the cursor.
--- ---
@@ -643,13 +657,14 @@ end
---@param row integer|nil Position row (default cursor position) ---@param row integer|nil Position row (default cursor position)
---@param col integer|nil Position column (default cursor position) ---@param col integer|nil Position column (default cursor position)
--- ---
---@return table|nil (table|nil) List of tokens at position. Each token has ---@return STTokenRangeInspect[]|nil (table|nil) List of tokens at position. Each token has
--- the following fields: --- the following fields:
--- - line (integer) line number, 0-based --- - line (integer) line number, 0-based
--- - start_col (integer) start column, 0-based --- - start_col (integer) start column, 0-based
--- - end_col (integer) end column, 0-based --- - end_col (integer) end column, 0-based
--- - type (string) token type as string, e.g. "variable" --- - type (string) token type as string, e.g. "variable"
--- - modifiers (table) token modifiers as a set. E.g., { static = true, readonly = true } --- - modifiers (table) token modifiers as a set. E.g., { static = true, readonly = true }
--- - client_id (integer)
function M.get_at_pos(bufnr, row, col) function M.get_at_pos(bufnr, row, col)
if bufnr == nil or bufnr == 0 then if bufnr == nil or bufnr == 0 then
bufnr = api.nvim_get_current_buf() bufnr = api.nvim_get_current_buf()
@@ -665,13 +680,14 @@ function M.get_at_pos(bufnr, row, col)
row, col = cursor[1] - 1, cursor[2] row, col = cursor[1] - 1, cursor[2]
end end
local tokens = {} local tokens = {} --- @type STTokenRangeInspect[]
for client_id, client in pairs(highlighter.client_state) do for client_id, client in pairs(highlighter.client_state) do
local highlights = client.current_result.highlights local highlights = client.current_result.highlights
if highlights then if highlights then
local idx = lower_bound(highlights, row, 1, #highlights + 1) local idx = lower_bound(highlights, row, 1, #highlights + 1)
for i = idx, #highlights do for i = idx, #highlights do
local token = highlights[i] local token = highlights[i]
--- @cast token STTokenRangeInspect
if token.line > row then if token.line > row then
break break

View File

@@ -58,7 +58,7 @@ local function byte_to_utf(line, byte, offset_encoding)
-- convert to 0 based indexing for str_utfindex -- convert to 0 based indexing for str_utfindex
byte = byte - 1 byte = byte - 1
local utf_idx local utf_idx --- @type integer
local _ local _
-- Convert the byte range to utf-{8,16,32} and convert 1-based (lua) indexing to 0-based -- Convert the byte range to utf-{8,16,32} and convert 1-based (lua) indexing to 0-based
if offset_encoding == 'utf-16' then if offset_encoding == 'utf-16' then
@@ -73,8 +73,11 @@ local function byte_to_utf(line, byte, offset_encoding)
return utf_idx + 1 return utf_idx + 1
end end
---@param line string
---@param offset_encoding string
---@return integer
local function compute_line_length(line, offset_encoding) local function compute_line_length(line, offset_encoding)
local length local length --- @type integer
local _ local _
if offset_encoding == 'utf-16' then if offset_encoding == 'utf-16' then
_, length = str_utfindex(line) _, length = str_utfindex(line)
@@ -94,7 +97,7 @@ end
---@return integer byte_idx of first change position ---@return integer byte_idx of first change position
---@return integer char_idx of first change position ---@return integer char_idx of first change position
local function align_end_position(line, byte, offset_encoding) local function align_end_position(line, byte, offset_encoding)
local char local char --- @type integer
-- If on the first byte, or an empty string: the trivial case -- If on the first byte, or an empty string: the trivial case
if byte == 1 or #line == 0 then if byte == 1 or #line == 0 then
char = byte char = byte
@@ -120,8 +123,8 @@ local function align_end_position(line, byte, offset_encoding)
end end
--- Finds the first line, byte, and char index of the difference between the previous and current lines buffer normalized to the previous codepoint. --- Finds the first line, byte, and char index of the difference between the previous and current lines buffer normalized to the previous codepoint.
---@param prev_lines table list of lines from previous buffer ---@param prev_lines string[] list of lines from previous buffer
---@param curr_lines table list of lines from current buffer ---@param curr_lines string[] list of lines from current buffer
---@param firstline integer firstline from on_lines, adjusted to 1-index ---@param firstline integer firstline from on_lines, adjusted to 1-index
---@param lastline integer lastline from on_lines, adjusted to 1-index ---@param lastline integer lastline from on_lines, adjusted to 1-index
---@param new_lastline integer new_lastline from on_lines, adjusted to 1-index ---@param new_lastline integer new_lastline from on_lines, adjusted to 1-index
@@ -135,14 +138,14 @@ local function compute_start_range(
new_lastline, new_lastline,
offset_encoding offset_encoding
) )
local char_idx local char_idx --- @type integer?
local byte_idx local byte_idx --- @type integer?
-- If firstline == lastline, no existing text is changed. All edit operations -- If firstline == lastline, no existing text is changed. All edit operations
-- occur on a new line pointed to by lastline. This occurs during insertion of -- occur on a new line pointed to by lastline. This occurs during insertion of
-- new lines(O), the new newline is inserted at the line indicated by -- new lines(O), the new newline is inserted at the line indicated by
-- new_lastline. -- new_lastline.
if firstline == lastline then if firstline == lastline then
local line_idx local line_idx --- @type integer
local line = prev_lines[firstline - 1] local line = prev_lines[firstline - 1]
if line then if line then
line_idx = firstline - 1 line_idx = firstline - 1
@@ -343,6 +346,12 @@ end
-- codeunits for utf-32 -- codeunits for utf-32
-- Line endings count here as 2 chars for \r\n (dos), 1 char for \n (unix), and 1 char for \r (mac) -- Line endings count here as 2 chars for \r\n (dos), 1 char for \n (unix), and 1 char for \r (mac)
-- These correspond to Windows, Linux/macOS (OSX and newer), and macOS (version 9 and prior) -- These correspond to Windows, Linux/macOS (OSX and newer), and macOS (version 9 and prior)
---@param lines string[]
---@param start_range table
---@param end_range table
---@param offset_encoding string
---@param line_ending string
---@return integer
local function compute_range_length(lines, start_range, end_range, offset_encoding, line_ending) local function compute_range_length(lines, start_range, end_range, offset_encoding, line_ending)
local line_ending_length = #line_ending local line_ending_length = #line_ending
-- Single line case -- Single line case
@@ -351,7 +360,7 @@ local function compute_range_length(lines, start_range, end_range, offset_encodi
end end
local start_line = lines[start_range.line_idx] local start_line = lines[start_range.line_idx]
local range_length local range_length --- @type integer
if start_line and #start_line > 0 then if start_line and #start_line > 0 then
range_length = compute_line_length(start_line, offset_encoding) range_length = compute_line_length(start_line, offset_encoding)
- start_range.char_idx - start_range.char_idx
@@ -387,6 +396,7 @@ end
---@param lastline integer line to begin search in old_lines for last difference ---@param lastline integer line to begin search in old_lines for last difference
---@param new_lastline integer line to begin search in new_lines for last difference ---@param new_lastline integer line to begin search in new_lines for last difference
---@param offset_encoding string encoding requested by language server ---@param offset_encoding string encoding requested by language server
---@param line_ending string
---@return table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent ---@return table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent
function M.compute_diff( function M.compute_diff(
prev_lines, prev_lines,

View File

@@ -2089,7 +2089,7 @@ end
--- Creates a `DocumentFormattingParams` object for the current buffer and cursor position. --- Creates a `DocumentFormattingParams` object for the current buffer and cursor position.
--- ---
---@param options table|nil with valid `FormattingOptions` entries ---@param options table|nil with valid `FormattingOptions` entries
---@return `DocumentFormattingParams` object ---@return lsp.DocumentFormattingParams object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
function M.make_formatting_params(options) function M.make_formatting_params(options)
validate({ options = { options, 't', true } }) validate({ options = { options, 't', true } })
@@ -2228,6 +2228,6 @@ end
M._get_line_byte_from_position = get_line_byte_from_position M._get_line_byte_from_position = get_line_byte_from_position
---@nodoc ---@nodoc
M.buf_versions = {} M.buf_versions = {} ---@type table<integer,integer>
return M return M