From 97bea3163a3fe50359e7f6ffda747e28974a818a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 13 Dec 2023 12:00:11 +0000 Subject: feat(lsp): more annotations --- runtime/lua/vim/lsp/rpc.lua | 192 +++++++++++++++++++++++++------------------- 1 file changed, 111 insertions(+), 81 deletions(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 6ab5708721..61ad1e479c 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -26,23 +26,42 @@ local function format_message_with_content_length(encoded_message) }) 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 --- ---@param header string: The header to parse. ---@return table # parsed headers local function parse_headers(header) assert(type(header) == 'string', 'header must be a string') - local headers = {} + local headers = {} --- @type table for line in vim.gsplit(header, '\r\n', { plain = true }) do if line == '' then break end + --- @type string?, string? local key, value = line:match('^%s*(%S+)%s*:%s*(.+)%s*$') if key then - key = key:lower():gsub('%-', '_') + key = key:lower():gsub('%-', '_') --- @type string headers[key] = value 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)) end end @@ -96,17 +115,17 @@ local function request_parser_loop() end local body = table.concat(body_chunks) -- Yield our data. - buffer = rest - .. ( - coroutine.yield(headers, body) - or error('Expected more data for the body. The server may have died.') - ) -- TODO hmm. + + --- @type string + local data = coroutine.yield(headers, body) + or error('Expected more data for the body. The server may have died.') + buffer = rest .. data else -- Get more data since we don't have enough. - buffer = buffer - .. ( - coroutine.yield() or error('Expected more data for the header. The server may have died.') - ) -- TODO hmm. + --- @type string + local data = coroutine.yield() + or error('Expected more data for the header. The server may have died.') + buffer = buffer .. data end end end @@ -138,7 +157,7 @@ function M.format_rpc_error(err) -- There is ErrorCodes in the LSP specification, -- 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 code = string.format('code_name = %s,', protocol.ErrorCodes[err.code]) else @@ -174,48 +193,51 @@ function M.rpc_response_error(code, message, data) }) 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 ---- 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 -function default_dispatchers.notification(method, params) - local _ = log.debug() and log.debug('notification', method, params) -end - ----@private ---- Default dispatcher for requests sent to an LSP server. ---- ----@param method (string) The invoked LSP method ----@param params (table): Parameters for the invoked LSP method ----@return nil ----@return table `vim.lsp.protocol.ErrorCodes.MethodNotFound` -function default_dispatchers.server_request(method, params) - local _ = log.debug() and log.debug('server_request', method, params) - return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound) -end - ----@private ---- Default dispatcher for when a client exits. ---- ----@param code (integer): Exit code ----@param signal (integer): Number describing the signal used to terminate (if ----any) -function default_dispatchers.on_exit(code, signal) - local _ = log.info() and log.info('client_exit', { code = code, signal = signal }) -end +--- @type vim.rpc.Dispatchers +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 + notification = function(method, params) + log_debug('notification', method, params) + end, ----@private ---- Default dispatcher for client errors. ---- ----@param code (integer): Error code ----@param err (any): Details about the error ----any) -function default_dispatchers.on_error(code, err) - local _ = log.error() and log.error('client_error:', M.client_errors[code], err) -end + --- Default dispatcher for requests sent to an LSP server. + --- + ---@param method (string) The invoked LSP method + ---@param params (table): Parameters for the invoked LSP method + ---@return nil + ---@return table, `vim.lsp.protocol.ErrorCodes.MethodNotFound` + server_request = function(method, params) + log_debug('server_request', method, params) + return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound) + end, + + --- Default dispatcher for when a client exits. + --- + ---@param code (integer): Exit code + ---@param signal (integer): Number describing the signal used to terminate (if + ---any) + on_exit = function(code, signal) + log_info('client_exit', { code = code, signal = signal }) + end, + + --- Default dispatcher for client errors. + --- + ---@param code (integer): Error code + ---@param err (any): Details about the error + ---any) + on_error = function(code, err) + log_error('client_error:', M.client_errors[code], err) + end, +} ---@private function M.create_read_loop(handle_body, on_no_chunk, on_error) @@ -248,8 +270,8 @@ end ---@class RpcClient ---@field message_index integer ----@field message_callbacks table ----@field notify_reply_callbacks table +---@field message_callbacks table +---@field notify_reply_callbacks table ---@field transport table ---@field dispatchers table @@ -258,7 +280,7 @@ local Client = {} ---@private 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 return false end @@ -267,7 +289,7 @@ function Client:encode_and_send(payload) return true end ----@private +---@package --- Sends a notification to the LSP server. ---@param method (string) 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 ----@private +---@package --- Sends a request to the LSP server and runs {callback} upon response. --- ---@param method (string) The invoked LSP method @@ -329,7 +351,7 @@ function Client:request(method, params, callback, notify_reply_callback) end end ----@private +---@package function Client:on_error(errkind, ...) assert(M.client_errors[errkind]) -- 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 -- them with an error then, perhaps. ----@private +---@package function Client:handle_body(body) local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } }) if not ok then self:on_error(M.client_errors.INVALID_SERVER_JSON, decoded) return end - local _ = log.debug() and log.debug('rpc.receive', decoded) + log_debug('rpc.receive', decoded) 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 -- we can still use the result. schedule(function() @@ -376,11 +398,10 @@ function Client:handle_body(body) decoded.method, decoded.params ) - local _ = log.debug() - and log.debug( - 'server_request: callback result', - { status = status, result = result, err = err } - ) + log_debug( + 'server_request: callback result', + { status = status, result = result, err = err } + ) if status then if result == nil and err == nil then error( @@ -431,7 +452,7 @@ function Client:handle_body(body) if decoded.error then local mute_error = false 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 end @@ -467,7 +488,7 @@ function Client:handle_body(body) ) else 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 elseif type(decoded.method) == 'string' then -- Notification @@ -495,7 +516,14 @@ local function new_client(dispatchers, transport) return setmetatable(state, { __index = Client }) 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 +---@return RpcClientPublic local function public_client(client) local result = {} @@ -531,12 +559,14 @@ local function public_client(client) return result end +--- @param dispatchers vim.rpc.Dispatchers? +--- @return vim.rpc.Dispatchers local function merge_dispatchers(dispatchers) if dispatchers then local user_dispatchers = dispatchers dispatchers = {} 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 type(user_dispatcher) ~= 'function' then error(string.format('dispatcher.%s must be a function', dispatch_name)) @@ -547,8 +577,10 @@ local function merge_dispatchers(dispatchers) then user_dispatcher = schedule_wrap(user_dispatcher) end + --- @diagnostic disable-next-line:no-unknown dispatchers[dispatch_name] = user_dispatcher else + --- @diagnostic disable-next-line:no-unknown dispatchers[dispatch_name] = default_dispatch end end @@ -567,7 +599,7 @@ end function M.connect(host, port) return function(dispatchers) dispatchers = merge_dispatchers(dispatchers) - local tcp = uv.new_tcp() + local tcp = assert(uv.new_tcp()) local closing = false local transport = { write = function(msg) @@ -624,15 +656,13 @@ end --- server process. May contain: --- - {cwd} (string) Working directory for the 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()| --- - `request()` |vim.lsp.rpc.request()| --- - `is_closing()` returns a boolean indicating if the RPC is closing. --- - `terminate()` terminates the RPC client. 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 }) - end + log_info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) validate({ cmd = { cmd, 's' }, @@ -671,8 +701,8 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) end) local stderr_handler = function(_, chunk) - if chunk and log.error() then - log.error('rpc', cmd, 'stderr', chunk) + if chunk then + log_error('rpc', cmd, 'stderr', chunk) end end @@ -697,13 +727,13 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) if not ok then 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 - msg = msg - .. '. The language server is either not installed, missing from PATH, or not executable.' + sfx = '. The language server is either not installed, missing from PATH, or not executable.' else - msg = msg .. string.format(' with error message: %s', err) + sfx = string.format(' with error message: %s', err) end + local msg = string.format('Spawning language server with cmd: `%s` failed%s', cmd, sfx) vim.notify(msg, vim.log.levels.WARN) return end -- cgit From 3f788e73b34521f093846d362bf51e68bc9a3827 Mon Sep 17 00:00:00 2001 From: TheLeoP <53507599+TheLeoP@users.noreply.github.com> Date: Tue, 2 Jan 2024 04:08:36 -0500 Subject: feat(lsp): support connect via named pipes/unix domain sockets (#26032) Closes https://github.com/neovim/neovim/issues/26031 Co-authored-by: Mathias Fussenegger --- runtime/lua/vim/lsp/rpc.lua | 253 ++++++++++++++++++++++++++++++++------------ 1 file changed, 186 insertions(+), 67 deletions(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 61ad1e479c..b0d98829a6 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -5,8 +5,31 @@ local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedu local is_win = uv.os_uname().version:find('Windows') +---@alias vim.lsp.rpc.Dispatcher fun(method: string, params: table):nil, vim.lsp.rpc.Error? +---@alias vim.lsp.rpc.on_error fun(code: integer, ...: any) +---@alias vim.lsp.rpc.on_exit fun(code: integer, signal: integer) + +---@class vim.lsp.rpc.Dispatchers +---@field notification vim.lsp.rpc.Dispatcher +---@field server_request vim.lsp.rpc.Dispatcher +---@field on_exit vim.lsp.rpc.on_error +---@field on_error vim.lsp.rpc.on_exit + +---@class vim.lsp.rpc.PublicClient +---@field request fun(method: string, params?: table, callback: fun(err: lsp.ResponseError | nil, result: any), notify_reply_callback:function?) +---@field notify fun(method: string, params: any) +---@field is_closing fun(): boolean +---@field terminate fun(): nil + +---@class vim.lsp.rpc.Client +---@field message_index integer +---@field message_callbacks table dict of message_id to callback +---@field notify_reply_callbacks table dict of message_id to callback +---@field transport vim.lsp.rpc.Transport +---@field dispatchers vim.lsp.rpc.Dispatchers + --- Checks whether a given path exists and is a directory. ----@param filename (string) path to check +---@param filename string path to check ---@return boolean local function is_dir(filename) local stat = uv.fs_stat(filename) @@ -15,14 +38,14 @@ end --- Embeds the given string into a table and correctly computes `Content-Length`. --- ----@param encoded_message (string) ----@return string containing encoded message and `Content-Length` attribute -local function format_message_with_content_length(encoded_message) +---@param message string +---@return string message with `Content-Length` attribute +local function format_message_with_content_length(message) return table.concat({ 'Content-Length: ', - tostring(#encoded_message), + tostring(#message), '\r\n\r\n', - encoded_message, + message, }) end @@ -44,13 +67,17 @@ local function log_debug(...) end end +---@class vim.lsp.rpc.Headers: {string: any} +---@field content_length integer + --- Parses an LSP Message's header --- ----@param header string: The header to parse. ----@return table # parsed headers +---@param header string The header to parse. +---@return vim.lsp.rpc.Headers#parsed headers local function parse_headers(header) assert(type(header) == 'string', 'header must be a string') - local headers = {} --- @type table + --- @type vim.lsp.rpc.Headers + local headers = {} for line in vim.gsplit(header, '\r\n', { plain = true }) do if line == '' then break @@ -92,15 +119,25 @@ local function request_parser_loop() -- be searching for. -- TODO(ashkan) I'd like to remove this, but it seems permanent :( local buffer_start = buffer:find(header_start_pattern) + if not buffer_start then + error( + string.format( + "Headers were expected, a different response was received. The server response was '%s'.", + buffer + ) + ) + end local headers = parse_headers(buffer:sub(buffer_start, start - 1)) local content_length = headers.content_length -- Use table instead of just string to buffer the message. It prevents -- a ton of strings allocating. -- ref. http://www.lua.org/pil/11.6.html + ---@type string[] local body_chunks = { buffer:sub(finish + 1) } local body_length = #body_chunks[1] -- Keep waiting for data until we have enough. while body_length < content_length do + ---@type string local chunk = coroutine.yield() or error('Expected more data for the body. The server may have died.') -- TODO hmm. table.insert(body_chunks, chunk) @@ -148,8 +185,8 @@ M.client_errors = vim.tbl_add_reverse_lookup(M.client_errors) --- Constructs an error message from an LSP error object. --- ----@param err (table) The error object ----@returns (string) The formatted error message +---@param err table The error object +---@return string#The formatted error message function M.format_rpc_error(err) validate({ err = { err, 't' }, @@ -176,11 +213,17 @@ function M.format_rpc_error(err) return table.concat(message_parts, ' ') end +---@class vim.lsp.rpc.Error +---@field code integer RPC error code defined by JSON RPC, see `vim.lsp.protocol.ErrorCodes` +---@field message? string arbitrary message to send to server +---@field data? any arbitrary data to send to server + --- Creates an RPC response object/table. --- ----@param code integer RPC error code defined in `vim.lsp.protocol.ErrorCodes` ----@param message string|nil arbitrary message to send to server ----@param data any|nil arbitrary data to send to server +---@param code integer RPC error code defined by JSON RPC +---@param message? string arbitrary message to send to server +---@param data? any arbitrary data to send to server +---@return vim.lsp.rpc.Error function M.rpc_response_error(code, message, data) -- TODO should this error or just pick a sane error (like InternalError)? local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code') @@ -204,7 +247,7 @@ 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 params (table) Parameters for the invoked LSP method notification = function(method, params) log_debug('notification', method, params) end, @@ -212,9 +255,9 @@ local default_dispatchers = { --- Default dispatcher for requests sent to an LSP server. --- ---@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 table, `vim.lsp.protocol.ErrorCodes.MethodNotFound` + ---@return vim.lsp.rpc.Error#`vim.lsp.protocol.ErrorCodes.MethodNotFound` server_request = function(method, params) log_debug('server_request', method, params) return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound) @@ -222,8 +265,8 @@ local default_dispatchers = { --- Default dispatcher for when a client exits. --- - ---@param code (integer): Exit code - ---@param signal (integer): Number describing the signal used to terminate (if + ---@param code (integer) Exit code + ---@param signal (integer) Number describing the signal used to terminate (if ---any) on_exit = function(code, signal) log_info('client_exit', { code = code, signal = signal }) @@ -231,17 +274,19 @@ local default_dispatchers = { --- Default dispatcher for client errors. --- - ---@param code (integer): Error code - ---@param err (any): Details about the error + ---@param code (integer) Error code + ---@param err (any) Details about the error ---any) on_error = function(code, err) log_error('client_error:', M.client_errors[code], err) end, } +---@cast default_dispatchers vim.lsp.rpc.Dispatchers + ---@private function M.create_read_loop(handle_body, on_no_chunk, on_error) - local parse_chunk = coroutine.wrap(request_parser_loop) + local parse_chunk = coroutine.wrap(request_parser_loop) --[[@as fun(chunk: string?): vim.lsp.rpc.Headers?, string?]] parse_chunk() return function(err, chunk) if err then @@ -268,14 +313,7 @@ function M.create_read_loop(handle_body, on_no_chunk, on_error) end end ----@class RpcClient ----@field message_index integer ----@field message_callbacks table ----@field notify_reply_callbacks table ----@field transport table ----@field dispatchers table - ----@class RpcClient +---@class vim.lsp.rpc.Client local Client = {} ---@private @@ -284,15 +322,18 @@ function Client:encode_and_send(payload) if self.transport.is_closing() then return false end - local encoded = vim.json.encode(payload) - self.transport.write(format_message_with_content_length(encoded)) + local jsonstr = assert( + vim.json.encode(payload), + string.format("Couldn't encode payload '%s'", vim.inspect(payload)) + ) + self.transport.write(format_message_with_content_length(jsonstr)) return true end ---@package --- Sends a notification to the LSP server. ----@param method (string) The invoked LSP method ----@param params (any): Parameters for the invoked LSP method +---@param method string The invoked LSP method +---@param params any Parameters for the invoked LSP method ---@return boolean `true` if notification could be sent, `false` if not function Client:notify(method, params) return self:encode_and_send({ @@ -316,10 +357,10 @@ end ---@package --- Sends a request to the LSP server and runs {callback} upon response. --- ----@param method (string) The invoked LSP method ----@param params (table|nil) Parameters for the invoked LSP method ----@param callback fun(err: lsp.ResponseError|nil, result: any) Callback to invoke ----@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending +---@param method string The invoked LSP method +---@param params? table Parameters for the invoked LSP method +---@param callback fun(err?: lsp.ResponseError, result: any) Callback to invoke +---@param notify_reply_callback? function Callback to invoke as soon as a request is no longer pending ---@return boolean success, integer|nil request_id true, request_id if request could be sent, `false` if not function Client:request(method, params, callback, notify_reply_callback) validate({ @@ -352,6 +393,8 @@ function Client:request(method, params, callback, notify_reply_callback) end ---@package +---@param errkind integer +---@param ... any function Client:on_error(errkind, ...) assert(M.client_errors[errkind]) -- TODO what to do if this fails? @@ -359,6 +402,13 @@ function Client:on_error(errkind, ...) end ---@private +---@param errkind integer +---@param status boolean +---@param head any +---@param ... any +---@return boolean status +---@return any head +---@return any|nil ... function Client:pcall_handler(errkind, status, head, ...) if not status then self:on_error(errkind, head, ...) @@ -368,6 +418,12 @@ function Client:pcall_handler(errkind, status, head, ...) end ---@private +---@param errkind integer +---@param fn function +---@param ... any +---@return boolean status +---@return any head +---@return any|nil ... function Client:try_call(errkind, fn, ...) return self:pcall_handler(errkind, pcall(fn, ...)) end @@ -386,7 +442,7 @@ function Client:handle_body(body) log_debug('rpc.receive', decoded) if type(decoded.method) == 'string' and decoded.id then - local err --- @type table? + local err --- @type vim.lsp.rpc.Error? -- Schedule here so that the users functions don't trigger an error and -- we can still use the result. schedule(function() @@ -412,6 +468,7 @@ function Client:handle_body(body) ) end if err then + ---@cast err vim.lsp.rpc.Error assert( type(err) == 'table', 'err must be a table. Use rpc_response_error to help format errors.' @@ -504,7 +561,14 @@ function Client:handle_body(body) end end ----@return RpcClient +---@class vim.lsp.rpc.Transport +---@field write fun(msg: string): nil +---@field is_closing fun(): boolean|nil +---@field terminate fun(): nil + +---@param dispatchers vim.lsp.rpc.Dispatchers +---@param transport vim.lsp.rpc.Transport +---@return vim.lsp.rpc.Client local function new_client(dispatchers, transport) local state = { message_index = 0, @@ -516,14 +580,8 @@ local function new_client(dispatchers, transport) return setmetatable(state, { __index = Client }) 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 ----@return RpcClientPublic +---@param client vim.lsp.rpc.Client +---@return vim.lsp.rpc.PublicClient local function public_client(client) local result = {} @@ -540,9 +598,9 @@ local function public_client(client) --- Sends a request to the LSP server and runs {callback} upon response. --- ---@param method (string) The invoked LSP method - ---@param params (table|nil) Parameters for the invoked LSP method + ---@param params (table?) Parameters for the invoked LSP method ---@param callback fun(err: lsp.ResponseError | nil, result: any) Callback to invoke - ---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending + ---@param notify_reply_callback (function?) Callback to invoke as soon as a request is no longer pending ---@return boolean success, integer|nil request_id true, message_id if request could be sent, `false` if not function result.request(method, params, callback, notify_reply_callback) return client:request(method, params, callback, notify_reply_callback) @@ -550,7 +608,7 @@ local function public_client(client) --- Sends a notification to the LSP server. ---@param method (string) The invoked LSP method - ---@param params (table|nil): Parameters for the invoked LSP method + ---@param params (table?) Parameters for the invoked LSP method ---@return boolean `true` if notification could be sent, `false` if not function result.notify(method, params) return client:notify(method, params) @@ -559,13 +617,15 @@ local function public_client(client) return result end ---- @param dispatchers vim.rpc.Dispatchers? ---- @return vim.rpc.Dispatchers +---@param dispatchers? vim.lsp.rpc.Dispatchers +---@return vim.lsp.rpc.Dispatchers local function merge_dispatchers(dispatchers) if dispatchers then local user_dispatchers = dispatchers dispatchers = {} for dispatch_name, default_dispatch in pairs(default_dispatchers) do + ---@cast dispatch_name string + ---@cast default_dispatch function local user_dispatcher = user_dispatchers[dispatch_name] --- @type function if user_dispatcher then if type(user_dispatcher) ~= 'function' then @@ -593,9 +653,9 @@ end --- Create a LSP RPC client factory that connects via TCP to the given host --- and port --- ----@param host string ----@param port integer ----@return function +---@param host string host to connect to +---@param port integer port to connect to +---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient # function intended to be passed to |vim.lsp.start_client()| or |vim.lsp.start()| on the field cmd function M.connect(host, port) return function(dispatchers) dispatchers = merge_dispatchers(dispatchers) @@ -640,23 +700,82 @@ function M.connect(host, port) end end +--- Create a LSP RPC client factory that connects via named pipes (Windows) +--- or unix domain sockets (Unix) to the given pipe_path (file path on +--- Unix and name on Windows) +--- +---@param pipe_path string file path of the domain socket (Unix) or name of the named pipe (Windows) to connect to +---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient#function intended to be passed to |vim.lsp.start_client()| or |vim.lsp.start()| on the field cmd +function M.domain_socket_connect(pipe_path) + return function(dispatchers) + dispatchers = merge_dispatchers(dispatchers) + local pipe = + assert(uv.new_pipe(false), string.format('pipe with name %s could not be opened.', pipe_path)) + local closing = false + local transport = { + write = vim.schedule_wrap(function(msg) + pipe:write(msg) + end), + is_closing = function() + return closing + end, + terminate = function() + if not closing then + closing = true + pipe:shutdown() + pipe:close() + dispatchers.on_exit(0, 0) + end + end, + } + local client = new_client(dispatchers, transport) + pipe:connect(pipe_path, function(err) + if err then + vim.schedule(function() + vim.notify( + string.format('Could not connect to :%s, reason: %s', pipe_path, vim.inspect(err)), + vim.log.levels.WARN + ) + end) + return + end + local handle_body = function(body) + client:handle_body(body) + end + pipe:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err) + client:on_error(M.client_errors.READ_ERROR, read_err) + end)) + end) + + return public_client(client) + end +end + +---@class vim.lsp.rpc.ExtraSpawnParams +---@field cwd? string Working directory for the LSP server process +---@field detached? boolean Detach the LSP server process from the current process +---@field env? table Additional environment variables for LSP server process. See |vim.system| + --- Starts an LSP server process and create an LSP RPC client object to --- interact with it. Communication with the spawned process happens via stdio. For --- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect()| --- ----@param cmd (string) Command to start the LSP server. ----@param cmd_args (table) List of additional string arguments to pass to {cmd}. ----@param dispatchers table|nil Dispatchers for LSP message types. Valid ----dispatcher names are: ---- - `"notification"` ---- - `"server_request"` ---- - `"on_error"` ---- - `"on_exit"` ----@param extra_spawn_params table|nil Additional context for the LSP +---@param cmd string Command to start the LSP server. +---@param cmd_args string[] List of additional string arguments to pass to {cmd}. +--- +---@param dispatchers? vim.lsp.rpc.Dispatchers (table|nil) Dispatchers for LSP message types. +--- Valid dispatcher names are: +--- - `"notification"` +--- - `"server_request"` +--- - `"on_error"` +--- - `"on_exit"` +--- +---@param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams (table|nil) Additional context for the LSP --- server process. May contain: --- - {cwd} (string) Working directory for the LSP server process ---- - {env} (table) Additional environment variables for LSP server process ----@return RpcClientPublic|nil Client RPC object, with these methods: +--- - {detached?} (boolean) Detach the LSP server process from the current process. Defaults to false on Windows and true otherwise. +--- - {env?} (table) Additional environment variables for LSP server process +---@return vim.lsp.rpc.PublicClient? (table|nil) client RPC object, with these methods: --- - `notify()` |vim.lsp.rpc.notify()| --- - `request()` |vim.lsp.rpc.request()| --- - `is_closing()` returns a boolean indicating if the RPC is closing. -- cgit From e0112aa1d21aa01eca867f28f77bcca28aae3b39 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Tue, 2 Jan 2024 10:19:22 +0100 Subject: refactor(lsp): fix remaining luals warnings in lsp.rpc --- runtime/lua/vim/lsp/rpc.lua | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index b0d98829a6..51d6bf4f9f 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -226,6 +226,7 @@ end ---@return vim.lsp.rpc.Error function M.rpc_response_error(code, message, data) -- TODO should this error or just pick a sane error (like InternalError)? + ---@type string local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code') return setmetatable({ code = code, @@ -473,6 +474,7 @@ function Client:handle_body(body) type(err) == 'table', 'err must be a table. Use rpc_response_error to help format errors.' ) + ---@type string local code_name = assert( protocol.ErrorCodes[err.code], 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.' @@ -620,34 +622,24 @@ end ---@param dispatchers? vim.lsp.rpc.Dispatchers ---@return vim.lsp.rpc.Dispatchers local function merge_dispatchers(dispatchers) - if dispatchers then - local user_dispatchers = dispatchers - dispatchers = {} - for dispatch_name, default_dispatch in pairs(default_dispatchers) do - ---@cast dispatch_name string - ---@cast default_dispatch function - local user_dispatcher = user_dispatchers[dispatch_name] --- @type function - if user_dispatcher then - if type(user_dispatcher) ~= 'function' then - error(string.format('dispatcher.%s must be a function', dispatch_name)) - end - -- server_request is wrapped elsewhere. - if - not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason. - then - user_dispatcher = schedule_wrap(user_dispatcher) - end - --- @diagnostic disable-next-line:no-unknown - dispatchers[dispatch_name] = user_dispatcher - else - --- @diagnostic disable-next-line:no-unknown - dispatchers[dispatch_name] = default_dispatch - end + if not dispatchers then + return default_dispatchers + end + ---@diagnostic disable-next-line: no-unknown + for name, fn in pairs(dispatchers) do + if type(fn) ~= 'function' then + error(string.format('dispatcher.%s must be a function', name)) end - else - dispatchers = default_dispatchers end - return dispatchers + return { + notification = dispatchers.notification and vim.schedule_wrap(dispatchers.notification) + or default_dispatchers.notification, + on_error = dispatchers.on_error and vim.schedule_wrap(dispatchers.on_error) + or default_dispatchers.on_error, + + on_exit = dispatchers.on_exit or default_dispatchers.on_exit, + server_request = dispatchers.server_request or default_dispatchers.server_request, + } end --- Create a LSP RPC client factory that connects via TCP to the given host -- cgit From ce4ea638c703275aedadb3794efc56dcb782c908 Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Tue, 9 Jan 2024 18:04:27 -0500 Subject: fix(lsp): fix incorrect typing and doc for `vim.lsp.rpc` Typings introduced in #26032 and #26552 have a few conflicts, so we merge and clean them up. We also fix some incorrect type annotation in the `vim.lsp.rpc` package. See the associated PR for more details. Summary: - vim.rpc.Dispatchers -> vim.lsp.rpc.Dispatchers - vim.lsp.rpc.Error -> lsp.ResponseError - Revise docs --- runtime/lua/vim/lsp/rpc.lua | 172 ++++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 86 deletions(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 51d6bf4f9f..660b126ce4 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -5,29 +5,6 @@ local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedu local is_win = uv.os_uname().version:find('Windows') ----@alias vim.lsp.rpc.Dispatcher fun(method: string, params: table):nil, vim.lsp.rpc.Error? ----@alias vim.lsp.rpc.on_error fun(code: integer, ...: any) ----@alias vim.lsp.rpc.on_exit fun(code: integer, signal: integer) - ----@class vim.lsp.rpc.Dispatchers ----@field notification vim.lsp.rpc.Dispatcher ----@field server_request vim.lsp.rpc.Dispatcher ----@field on_exit vim.lsp.rpc.on_error ----@field on_error vim.lsp.rpc.on_exit - ----@class vim.lsp.rpc.PublicClient ----@field request fun(method: string, params?: table, callback: fun(err: lsp.ResponseError | nil, result: any), notify_reply_callback:function?) ----@field notify fun(method: string, params: any) ----@field is_closing fun(): boolean ----@field terminate fun(): nil - ----@class vim.lsp.rpc.Client ----@field message_index integer ----@field message_callbacks table dict of message_id to callback ----@field notify_reply_callbacks table dict of message_id to callback ----@field transport vim.lsp.rpc.Transport ----@field dispatchers vim.lsp.rpc.Dispatchers - --- Checks whether a given path exists and is a directory. ---@param filename string path to check ---@return boolean @@ -186,7 +163,7 @@ M.client_errors = vim.tbl_add_reverse_lookup(M.client_errors) --- Constructs an error message from an LSP error object. --- ---@param err table The error object ----@return string#The formatted error message +---@return string error_message The formatted error message function M.format_rpc_error(err) validate({ err = { err, 't' }, @@ -213,17 +190,14 @@ function M.format_rpc_error(err) return table.concat(message_parts, ' ') end ----@class vim.lsp.rpc.Error ----@field code integer RPC error code defined by JSON RPC, see `vim.lsp.protocol.ErrorCodes` ----@field message? string arbitrary message to send to server ----@field data? any arbitrary data to send to server - ---- Creates an RPC response object/table. +--- Creates an RPC response table `error` to be sent to the LSP response. --- ----@param code integer RPC error code defined by JSON RPC +---@param code integer RPC error code defined, see `vim.lsp.protocol.ErrorCodes` ---@param message? string arbitrary message to send to server ---@param data? any arbitrary data to send to server ----@return vim.lsp.rpc.Error +--- +---@see lsp.ErrorCodes See `vim.lsp.protocol.ErrorCodes` +---@return lsp.ResponseError function M.rpc_response_error(code, message, data) -- TODO should this error or just pick a sane error (like InternalError)? ---@type string @@ -237,28 +211,28 @@ function M.rpc_response_error(code, message, data) }) end ---- @class vim.rpc.Dispatchers +--- @class vim.lsp.rpc.Dispatchers --- @field notification fun(method: string, params: table) ---- @field server_request fun(method: string, params: table): any?, string? +--- @field server_request fun(method: string, params: table): any?, lsp.ResponseError? --- @field on_exit fun(code: integer, signal: integer) --- @field on_error fun(code: integer, err: any) ---- @type vim.rpc.Dispatchers +--- @type vim.lsp.rpc.Dispatchers 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 + ---@param params table Parameters for the invoked LSP method notification = function(method, params) log_debug('notification', method, params) end, --- Default dispatcher for requests sent to an LSP server. --- - ---@param method (string) The invoked LSP method - ---@param params (table) Parameters for the invoked LSP method - ---@return nil - ---@return vim.lsp.rpc.Error#`vim.lsp.protocol.ErrorCodes.MethodNotFound` + ---@param method string The invoked LSP method + ---@param params table Parameters for the invoked LSP method + ---@return any result (always nil for the default dispatchers) + ---@return lsp.ResponseError error `vim.lsp.protocol.ErrorCodes.MethodNotFound` server_request = function(method, params) log_debug('server_request', method, params) return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound) @@ -266,25 +240,21 @@ local default_dispatchers = { --- Default dispatcher for when a client exits. --- - ---@param code (integer) Exit code - ---@param signal (integer) Number describing the signal used to terminate (if - ---any) + ---@param code integer Exit code + ---@param signal integer Number describing the signal used to terminate (if any) on_exit = function(code, signal) log_info('client_exit', { code = code, signal = signal }) end, --- Default dispatcher for client errors. --- - ---@param code (integer) Error code - ---@param err (any) Details about the error - ---any) + ---@param code integer Error code + ---@param err any Details about the error on_error = function(code, err) log_error('client_error:', M.client_errors[code], err) end, } ----@cast default_dispatchers vim.lsp.rpc.Dispatchers - ---@private function M.create_read_loop(handle_body, on_no_chunk, on_error) local parse_chunk = coroutine.wrap(request_parser_loop) --[[@as fun(chunk: string?): vim.lsp.rpc.Headers?, string?]] @@ -314,6 +284,14 @@ function M.create_read_loop(handle_body, on_no_chunk, on_error) end end +---@private +---@class vim.lsp.rpc.Client +---@field message_index integer +---@field message_callbacks table dict of message_id to callback +---@field notify_reply_callbacks table dict of message_id to callback +---@field transport vim.lsp.rpc.Transport +---@field dispatchers vim.lsp.rpc.Dispatchers + ---@class vim.lsp.rpc.Client local Client = {} @@ -356,13 +334,14 @@ function Client:send_response(request_id, err, result) end ---@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. |vim.lsp.rpc.request()| --- ---@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 ---@param callback fun(err?: lsp.ResponseError, result: any) Callback to invoke ----@param notify_reply_callback? function Callback to invoke as soon as a request is no longer pending ----@return boolean success, integer|nil request_id true, request_id if request could be sent, `false` if not +---@param notify_reply_callback fun(message_id: integer)|nil Callback to invoke as soon as a request is no longer pending +---@return boolean success `true` if request could be sent, `false` if not +---@return integer? message_id if request could be sent, `nil` if not function Client:request(method, params, callback, notify_reply_callback) validate({ callback = { callback, 'f' }, @@ -382,14 +361,14 @@ function Client:request(method, params, callback, notify_reply_callback) if message_callbacks then message_callbacks[message_id] = schedule_wrap(callback) else - return false + return false, nil end if notify_reply_callback and notify_reply_callbacks then notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback) end return result, message_id else - return false + return false, nil end end @@ -443,7 +422,7 @@ function Client:handle_body(body) log_debug('rpc.receive', decoded) if type(decoded.method) == 'string' and decoded.id then - local err --- @type vim.lsp.rpc.Error? + local err --- @type lsp.ResponseError|nil -- Schedule here so that the users functions don't trigger an error and -- we can still use the result. schedule(function() @@ -469,7 +448,7 @@ function Client:handle_body(body) ) end if err then - ---@cast err vim.lsp.rpc.Error + ---@cast err lsp.ResponseError assert( type(err) == 'table', 'err must be a table. Use rpc_response_error to help format errors.' @@ -564,9 +543,9 @@ function Client:handle_body(body) end ---@class vim.lsp.rpc.Transport ----@field write fun(msg: string): nil ----@field is_closing fun(): boolean|nil ----@field terminate fun(): nil +---@field write fun(msg: string) +---@field is_closing fun(): boolean +---@field terminate fun() ---@param dispatchers vim.lsp.rpc.Dispatchers ---@param transport vim.lsp.rpc.Transport @@ -582,9 +561,17 @@ local function new_client(dispatchers, transport) return setmetatable(state, { __index = Client }) end +---@class vim.lsp.rpc.PublicClient +---@field request fun(method: string, params: table?, callback: fun(err: lsp.ResponseError|nil, result: any), notify_reply_callback: fun(integer)|nil):boolean,integer? see |vim.lsp.rpc.request()| +---@field notify fun(method: string, params: any):boolean see |vim.lsp.rpc.notify()| +---@field is_closing fun(): boolean +---@field terminate fun() + ---@param client vim.lsp.rpc.Client ---@return vim.lsp.rpc.PublicClient local function public_client(client) + ---@type vim.lsp.rpc.PublicClient + ---@diagnostic disable-next-line: missing-fields local result = {} ---@private @@ -601,9 +588,10 @@ local function public_client(client) --- ---@param method (string) The invoked LSP method ---@param params (table?) Parameters for the invoked LSP method - ---@param callback fun(err: lsp.ResponseError | nil, result: any) Callback to invoke - ---@param notify_reply_callback (function?) Callback to invoke as soon as a request is no longer pending - ---@return boolean success, integer|nil request_id true, message_id if request could be sent, `false` if not + ---@param callback fun(err: lsp.ResponseError|nil, result: any) Callback to invoke + ---@param notify_reply_callback fun(message_id: integer)|nil Callback to invoke as soon as a request is no longer pending + ---@return boolean success `true` if request could be sent, `false` if not + ---@return integer? message_id if request could be sent, `nil` if not function result.request(method, params, callback, notify_reply_callback) return client:request(method, params, callback, notify_reply_callback) end @@ -619,7 +607,7 @@ local function public_client(client) return result end ----@param dispatchers? vim.lsp.rpc.Dispatchers +---@param dispatchers vim.lsp.rpc.Dispatchers? ---@return vim.lsp.rpc.Dispatchers local function merge_dispatchers(dispatchers) if not dispatchers then @@ -631,23 +619,30 @@ local function merge_dispatchers(dispatchers) error(string.format('dispatcher.%s must be a function', name)) end end - return { - notification = dispatchers.notification and vim.schedule_wrap(dispatchers.notification) - or default_dispatchers.notification, - on_error = dispatchers.on_error and vim.schedule_wrap(dispatchers.on_error) - or default_dispatchers.on_error, - + ---@type vim.lsp.rpc.Dispatchers + local merged = { + notification = ( + dispatchers.notification and vim.schedule_wrap(dispatchers.notification) + or default_dispatchers.notification + ), + on_error = ( + dispatchers.on_error and vim.schedule_wrap(dispatchers.on_error) + or default_dispatchers.on_error + ), on_exit = dispatchers.on_exit or default_dispatchers.on_exit, server_request = dispatchers.server_request or default_dispatchers.server_request, } + return merged end ---- Create a LSP RPC client factory that connects via TCP to the given host ---- and port +--- Create a LSP RPC client factory that connects via TCP to the given host and port. +--- +--- Return a function that can be passed to the `cmd` field for +--- |vim.lsp.start_client()| or |vim.lsp.start()|. --- ---@param host string host to connect to ---@param port integer port to connect to ----@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient # function intended to be passed to |vim.lsp.start_client()| or |vim.lsp.start()| on the field cmd +---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient function M.connect(host, port) return function(dispatchers) dispatchers = merge_dispatchers(dispatchers) @@ -694,10 +689,13 @@ end --- Create a LSP RPC client factory that connects via named pipes (Windows) --- or unix domain sockets (Unix) to the given pipe_path (file path on ---- Unix and name on Windows) +--- Unix and name on Windows). +--- +--- Return a function that can be passed to the `cmd` field for +--- |vim.lsp.start_client()| or |vim.lsp.start()|. --- ---@param pipe_path string file path of the domain socket (Unix) or name of the named pipe (Windows) to connect to ----@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient#function intended to be passed to |vim.lsp.start_client()| or |vim.lsp.start()| on the field cmd +---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient function M.domain_socket_connect(pipe_path) return function(dispatchers) dispatchers = merge_dispatchers(dispatchers) @@ -755,23 +753,25 @@ end ---@param cmd string Command to start the LSP server. ---@param cmd_args string[] List of additional string arguments to pass to {cmd}. --- ----@param dispatchers? vim.lsp.rpc.Dispatchers (table|nil) Dispatchers for LSP message types. +---@param dispatchers? vim.lsp.rpc.Dispatchers Dispatchers for LSP message types. --- Valid dispatcher names are: --- - `"notification"` --- - `"server_request"` --- - `"on_error"` --- - `"on_exit"` --- ----@param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams (table|nil) Additional context for the LSP +---@param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams Additional context for the LSP --- server process. May contain: ---- - {cwd} (string) Working directory for the LSP server process ---- - {detached?} (boolean) Detach the LSP server process from the current process. Defaults to false on Windows and true otherwise. ---- - {env?} (table) Additional environment variables for LSP server process ----@return vim.lsp.rpc.PublicClient? (table|nil) client RPC object, with these methods: ---- - `notify()` |vim.lsp.rpc.notify()| ---- - `request()` |vim.lsp.rpc.request()| ---- - `is_closing()` returns a boolean indicating if the RPC is closing. ---- - `terminate()` terminates the RPC client. +--- - {cwd} (string) Working directory for the LSP server process +--- - {detached?} (boolean) Detach the LSP server process from the current process. +--- Defaults to false on Windows and true otherwise. +--- - {env?} (table) Additional environment variables for LSP server process +--- +---@return vim.lsp.rpc.PublicClient? Client RPC object, with these methods: +--- - `notify()` |vim.lsp.rpc.notify()| +--- - `request()` |vim.lsp.rpc.request()| +--- - `is_closing()` returns a boolean indicating if the RPC is closing. +--- - `terminate()` terminates the RPC client. function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) log_info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) @@ -846,7 +846,7 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) end local msg = string.format('Spawning language server with cmd: `%s` failed%s', cmd, sfx) vim.notify(msg, vim.log.levels.WARN) - return + return nil end sysobj = sysobj_or_err --[[@as vim.SystemObj]] -- cgit From 1f9da3d0835af2cfe937de250c2cde3a59e1677e Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Feb 2024 09:24:47 +0000 Subject: refactor(lsp): tidy up logging --- runtime/lua/vim/lsp/rpc.lua | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 660b126ce4..1aacf63392 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -26,24 +26,6 @@ local function format_message_with_content_length(message) }) 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 - ---@class vim.lsp.rpc.Headers: {string: any} ---@field content_length integer @@ -65,7 +47,7 @@ local function parse_headers(header) key = key:lower():gsub('%-', '_') --- @type string headers[key] = value else - log_error('invalid header line %q', line) + log.error('invalid header line %q', line) error(string.format('invalid header line %q', line)) end end @@ -224,7 +206,7 @@ local default_dispatchers = { ---@param method string The invoked LSP method ---@param params table Parameters for the invoked LSP method notification = function(method, params) - log_debug('notification', method, params) + log.debug('notification', method, params) end, --- Default dispatcher for requests sent to an LSP server. @@ -234,7 +216,7 @@ local default_dispatchers = { ---@return any result (always nil for the default dispatchers) ---@return lsp.ResponseError error `vim.lsp.protocol.ErrorCodes.MethodNotFound` server_request = function(method, params) - log_debug('server_request', method, params) + log.debug('server_request', method, params) return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound) end, @@ -243,7 +225,7 @@ local default_dispatchers = { ---@param code integer Exit code ---@param signal integer Number describing the signal used to terminate (if any) on_exit = function(code, signal) - log_info('client_exit', { code = code, signal = signal }) + log.info('client_exit', { code = code, signal = signal }) end, --- Default dispatcher for client errors. @@ -251,7 +233,7 @@ local default_dispatchers = { ---@param code integer Error code ---@param err any Details about the error on_error = function(code, err) - log_error('client_error:', M.client_errors[code], err) + log.error('client_error:', M.client_errors[code], err) end, } @@ -297,7 +279,7 @@ local Client = {} ---@private function Client:encode_and_send(payload) - log_debug('rpc.send', payload) + log.debug('rpc.send', payload) if self.transport.is_closing() then return false end @@ -419,7 +401,7 @@ function Client:handle_body(body) self:on_error(M.client_errors.INVALID_SERVER_JSON, decoded) return end - log_debug('rpc.receive', decoded) + log.debug('rpc.receive', decoded) if type(decoded.method) == 'string' and decoded.id then local err --- @type lsp.ResponseError|nil @@ -434,7 +416,7 @@ function Client:handle_body(body) decoded.method, decoded.params ) - log_debug( + log.debug( 'server_request: callback result', { status = status, result = result, err = err } ) @@ -490,7 +472,7 @@ function Client:handle_body(body) if decoded.error then local mute_error = false if decoded.error.code == protocol.ErrorCodes.RequestCancelled then - log_debug('Received cancellation ack', decoded) + log.debug('Received cancellation ack', decoded) mute_error = true end @@ -526,7 +508,7 @@ function Client:handle_body(body) ) else self:on_error(M.client_errors.NO_RESULT_CALLBACK_FOUND, decoded) - log_error('No callback found for server response id ' .. result_id) + log.error('No callback found for server response id ' .. result_id) end elseif type(decoded.method) == 'string' then -- Notification @@ -773,7 +755,7 @@ end --- - `is_closing()` returns a boolean indicating if the RPC is closing. --- - `terminate()` terminates the RPC client. function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) - 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 }) validate({ cmd = { cmd, 's' }, @@ -813,7 +795,7 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) local stderr_handler = function(_, chunk) if chunk then - log_error('rpc', cmd, 'stderr', chunk) + log.error('rpc', cmd, 'stderr', chunk) end end -- cgit From c73d67d283c296bdb7a44a0283346e7b61d837f0 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 10 Feb 2024 14:03:44 -0800 Subject: refactor(lsp): add type annotations --- runtime/lua/vim/lsp/rpc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 1aacf63392..23f70826e5 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -498,7 +498,7 @@ function Client:handle_body(body) if decoded.error then decoded.error = setmetatable(decoded.error, { __tostring = M.format_rpc_error, - }) + }) --- @type table end self:try_call( M.client_errors.SERVER_RESULT_CALLBACK_ERROR, -- cgit From ed1b66bd998b98ee8cf76b5a23c323352588dd56 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 11 Feb 2024 12:37:20 +0000 Subject: refactor(lsp): move more code to client.lua The dispatchers used by the RPC client should be defined in the client, so they have been moved there. Due to this, it also made sense to move all code related to client configuration and the creation of the RPC client there too. Now vim.lsp.start_client is significantly simplified and now mostly contains logic for tracking open clients. - Renamed client.new -> client.start --- runtime/lua/vim/lsp/rpc.lua | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 23f70826e5..e849bb4f2a 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -732,8 +732,7 @@ end --- interact with it. Communication with the spawned process happens via stdio. For --- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect()| --- ----@param cmd string Command to start the LSP server. ----@param cmd_args string[] List of additional string arguments to pass to {cmd}. +---@param cmd string[] Command to start the LSP server. --- ---@param dispatchers? vim.lsp.rpc.Dispatchers Dispatchers for LSP message types. --- Valid dispatcher names are: @@ -754,12 +753,11 @@ end --- - `request()` |vim.lsp.rpc.request()| --- - `is_closing()` returns a boolean indicating if the RPC is closing. --- - `terminate()` terminates the RPC client. -function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) - log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) +function M.start(cmd, dispatchers, extra_spawn_params) + log.info('Starting RPC client', { cmd = cmd, extra = extra_spawn_params }) validate({ - cmd = { cmd, 's' }, - cmd_args = { cmd_args, 't' }, + cmd = { cmd, 't' }, dispatchers = { dispatchers, 't', true }, }) @@ -795,7 +793,7 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) local stderr_handler = function(_, chunk) if chunk then - log.error('rpc', cmd, 'stderr', chunk) + log.error('rpc', cmd[1], 'stderr', chunk) end end @@ -804,10 +802,7 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) detached = extra_spawn_params.detached end - local cmd1 = { cmd } - vim.list_extend(cmd1, cmd_args) - - local ok, sysobj_or_err = pcall(vim.system, cmd1, { + local ok, sysobj_or_err = pcall(vim.system, cmd, { stdin = true, stdout = stdout_handler, stderr = stderr_handler, -- cgit From 9beb40a4db5613601fc1a4b828a44e5977eca046 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 15 Feb 2024 17:16:04 +0000 Subject: feat(docs): replace lua2dox.lua Problem: The documentation flow (`gen_vimdoc.py`) has several issues: - it's not very versatile - depends on doxygen - doesn't work well with Lua code as it requires an awkward filter script to convert it into pseudo-C. - The intermediate XML files and filters makes it too much like a rube goldberg machine. Solution: Re-implement the flow using Lua, LPEG and treesitter. - `gen_vimdoc.py` is now replaced with `gen_vimdoc.lua` and replicates a portion of the logic. - `lua2dox.lua` is gone! - No more XML files. - Doxygen is now longer used and instead we now use: - LPEG for comment parsing (see `scripts/luacats_grammar.lua` and `scripts/cdoc_grammar.lua`). - LPEG for C parsing (see `scripts/cdoc_parser.lua`) - Lua patterns for Lua parsing (see `scripts/luacats_parser.lua`). - Treesitter for Markdown parsing (see `scripts/text_utils.lua`). - The generated `runtime/doc/*.mpack` files have been removed. - `scripts/gen_eval_files.lua` now instead uses `scripts/cdoc_parser.lua` directly. - Text wrapping is implemented in `scripts/text_utils.lua` and appears to produce more consistent results (the main contributer to the diff of this change). --- runtime/lua/vim/lsp/rpc.lua | 2 -- 1 file changed, 2 deletions(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index e849bb4f2a..1455ab51fa 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -273,8 +273,6 @@ end ---@field notify_reply_callbacks table dict of message_id to callback ---@field transport vim.lsp.rpc.Transport ---@field dispatchers vim.lsp.rpc.Dispatchers - ----@class vim.lsp.rpc.Client local Client = {} ---@private -- cgit From aa62898ae329ec7ef978b4e7263c6f41b28f2503 Mon Sep 17 00:00:00 2001 From: notomo Date: Thu, 29 Feb 2024 01:36:28 +0900 Subject: fix(lsp): correct the error message's cmd on spawning (#27632) --- runtime/lua/vim/lsp/rpc.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 1455ab51fa..e52c06a1bd 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -819,7 +819,8 @@ function M.start(cmd, dispatchers, extra_spawn_params) else sfx = string.format(' with error message: %s', err) end - local msg = string.format('Spawning language server with cmd: `%s` failed%s', cmd, sfx) + local msg = + string.format('Spawning language server with cmd: `%s` failed%s', vim.inspect(cmd), sfx) vim.notify(msg, vim.log.levels.WARN) return nil end -- cgit From a5fe8f59d98398d04bed8586cee73864bbcdde92 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 27 Feb 2024 15:20:32 +0000 Subject: docs: improve/add documentation of Lua types - Added `@inlinedoc` so single use Lua types can be inlined into the functions docs. E.g. ```lua --- @class myopts --- @inlinedoc --- --- Documentation for some field --- @field somefield integer --- @param opts myOpts function foo(opts) end ``` Will be rendered as ``` foo(opts) Parameters: - {opts} (table) Object with the fields: - somefield (integer) Documentation for some field ``` - Marked many classes with with `@nodoc` or `(private)`. We can eventually introduce these when we want to. --- runtime/lua/vim/lsp/rpc.lua | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index e52c06a1bd..8e014b1063 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -26,7 +26,7 @@ local function format_message_with_content_length(message) }) end ----@class vim.lsp.rpc.Headers: {string: any} +---@class (private) vim.lsp.rpc.Headers: {string: any} ---@field content_length integer --- Parses an LSP Message's header @@ -193,7 +193,9 @@ function M.rpc_response_error(code, message, data) }) end +--- Dispatchers for LSP message types. --- @class vim.lsp.rpc.Dispatchers +--- @inlinedoc --- @field notification fun(method: string, params: table) --- @field server_request fun(method: string, params: table): any?, lsp.ResponseError? --- @field on_exit fun(code: integer, signal: integer) @@ -266,8 +268,7 @@ function M.create_read_loop(handle_body, on_no_chunk, on_error) end end ----@private ----@class vim.lsp.rpc.Client +---@class (private) vim.lsp.rpc.Client ---@field message_index integer ---@field message_callbacks table dict of message_id to callback ---@field notify_reply_callbacks table dict of message_id to callback @@ -522,7 +523,7 @@ function Client:handle_body(body) end end ----@class vim.lsp.rpc.Transport +---@class (private) vim.lsp.rpc.Transport ---@field write fun(msg: string) ---@field is_closing fun(): boolean ---@field terminate fun() @@ -721,32 +722,21 @@ function M.domain_socket_connect(pipe_path) end end ----@class vim.lsp.rpc.ExtraSpawnParams ----@field cwd? string Working directory for the LSP server process ----@field detached? boolean Detach the LSP server process from the current process ----@field env? table Additional environment variables for LSP server process. See |vim.system| +--- Additional context for the LSP server process. +--- @class vim.lsp.rpc.ExtraSpawnParams +--- @inlinedoc +--- @field cwd? string Working directory for the LSP server process +--- @field detached? boolean Detach the LSP server process from the current process +--- @field env? table Additional environment variables for LSP server process. See |vim.system()| --- Starts an LSP server process and create an LSP RPC client object to --- interact with it. Communication with the spawned process happens via stdio. For --- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect()| --- ----@param cmd string[] Command to start the LSP server. ---- ----@param dispatchers? vim.lsp.rpc.Dispatchers Dispatchers for LSP message types. ---- Valid dispatcher names are: ---- - `"notification"` ---- - `"server_request"` ---- - `"on_error"` ---- - `"on_exit"` ---- ----@param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams Additional context for the LSP ---- server process. May contain: ---- - {cwd} (string) Working directory for the LSP server process ---- - {detached?} (boolean) Detach the LSP server process from the current process. ---- Defaults to false on Windows and true otherwise. ---- - {env?} (table) Additional environment variables for LSP server process ---- ----@return vim.lsp.rpc.PublicClient? Client RPC object, with these methods: +--- @param cmd string[] Command to start the LSP server. +--- @param dispatchers? vim.lsp.rpc.Dispatchers +--- @param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams +--- @return vim.lsp.rpc.PublicClient? : Client RPC object, with these methods: --- - `notify()` |vim.lsp.rpc.notify()| --- - `request()` |vim.lsp.rpc.request()| --- - `is_closing()` returns a boolean indicating if the RPC is closing. -- cgit From e52c25b7617ac6401b080f76b0e227161dfef230 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 2 Mar 2024 13:11:23 -0800 Subject: feat(lua): deprecate vim.tbl_add_reverse_lookup --- runtime/lua/vim/lsp/rpc.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/rpc.lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 8e014b1063..984e4f040a 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -130,7 +130,7 @@ local M = {} --- Mapping of error codes used by the client --- @nodoc -M.client_errors = { +local client_errors = { INVALID_SERVER_MESSAGE = 1, INVALID_SERVER_JSON = 2, NO_RESULT_CALLBACK_FOUND = 3, @@ -140,7 +140,12 @@ M.client_errors = { SERVER_RESULT_CALLBACK_ERROR = 7, } -M.client_errors = vim.tbl_add_reverse_lookup(M.client_errors) +--- @type table +--- @nodoc +M.client_errors = vim.deepcopy(client_errors) +for k, v in pairs(client_errors) do + M.client_errors[v] = k +end --- Constructs an error message from an LSP error object. --- -- cgit