From ce4ea638c703275aedadb3794efc56dcb782c908 Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Tue, 9 Jan 2024 18:04:27 -0500 Subject: [PATCH] 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/doc/lsp.txt | 47 +++++----- runtime/lua/vim/_system.lua | 4 +- runtime/lua/vim/lsp.lua | 8 +- runtime/lua/vim/lsp/rpc.lua | 172 ++++++++++++++++++------------------ 4 files changed, 120 insertions(+), 111 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 7e1c0a4a15..427cd40761 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -2089,7 +2089,10 @@ Lua module: vim.lsp.rpc *lsp-rpc* connect({host}, {port}) *vim.lsp.rpc.connect()* Create a LSP RPC client factory that connects via TCP to the given host - and port + and port. + + Return a function that can be passed to the `cmd` field for + |vim.lsp.start_client()| or |vim.lsp.start()|. Parameters: ~ • {host} (`string`) host to connect to @@ -2097,14 +2100,15 @@ connect({host}, {port}) *vim.lsp.rpc.connect()* 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 *vim.lsp.rpc.domain_socket_connect()* domain_socket_connect({pipe_path}) 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) + name on Windows). + + Return a function that can be passed to the `cmd` field for + |vim.lsp.start_client()| or |vim.lsp.start()|. Parameters: ~ • {pipe_path} (`string`) file path of the domain socket (Unix) or name @@ -2112,8 +2116,6 @@ domain_socket_connect({pipe_path}) 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 format_rpc_error({err}) *vim.lsp.rpc.format_rpc_error()* Constructs an error message from an LSP error object. @@ -2122,7 +2124,7 @@ format_rpc_error({err}) *vim.lsp.rpc.format_rpc_error()* • {err} (`table`) The error object Return: ~ - (`string`) The formatted error message + (`string`) error_message The formatted error message notify({method}, {params}) *vim.lsp.rpc.notify()* Sends a notification to the LSP server. @@ -2144,24 +2146,29 @@ request({method}, {params}, {callback}, {notify_reply_callback}) method • {callback} (`fun(err: lsp.ResponseError?, result: any)`) Callback to invoke - • {notify_reply_callback} (`function?`) Callback to invoke as soon as a - request is no longer pending + • {notify_reply_callback} (`fun(message_id: integer)?`) Callback to + invoke as soon as a request is no longer + pending - Return: ~ - (`boolean success, integer? request_id`) true, message_id if request - could be sent, `false` if not + Return (multiple): ~ + (`boolean`) success `true` if request could be sent, `false` if not + (`integer?`) message_id if request could be sent, `nil` if not *vim.lsp.rpc.rpc_response_error()* rpc_response_error({code}, {message}, {data}) - Creates an RPC response object/table. + Creates an RPC response table `error` to be sent to the LSP response. Parameters: ~ - • {code} (`integer`) RPC error code defined by JSON RPC + • {code} (`integer`) RPC error code defined, see + `vim.lsp.protocol.ErrorCodes` • {message} (`string?`) arbitrary message to send to server • {data} (`any?`) arbitrary data to send to server Return: ~ - (`vim.lsp.rpc.Error`) + (`lsp.ResponseError`) + + See also: ~ + • lsp.ErrorCodes See `vim.lsp.protocol.ErrorCodes` *vim.lsp.rpc.start()* start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params}) @@ -2174,14 +2181,14 @@ start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params}) • {cmd} (`string`) Command to start the LSP server. • {cmd_args} (`string[]`) List of additional string arguments to pass to {cmd}. - • {dispatchers} (`table?`) Dispatchers for LSP message types. - Valid dispatcher names are: + • {dispatchers} (`vim.lsp.rpc.Dispatchers?`) Dispatchers for LSP + message types. Valid dispatcher names are: • `"notification"` • `"server_request"` • `"on_error"` • `"on_exit"` - • {extra_spawn_params} (`table?`) Additional context for the LSP server - process. May contain: + • {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 @@ -2191,7 +2198,7 @@ start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params}) variables for LSP server process Return: ~ - (`table?`) client RPC object, with these methods: + (`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. diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua index 9e935b7e95..efad3f88cf 100644 --- a/runtime/lua/vim/_system.lua +++ b/runtime/lua/vim/_system.lua @@ -61,7 +61,7 @@ end --- @field wait fun(self: vim.SystemObj, timeout?: integer): vim.SystemCompleted --- @field kill fun(self: vim.SystemObj, signal: integer|string) --- @field write fun(self: vim.SystemObj, data?: string|string[]) ---- @field is_closing fun(self: vim.SystemObj): boolean? +--- @field is_closing fun(self: vim.SystemObj): boolean local SystemObj = {} --- @param state vim.SystemState @@ -140,7 +140,7 @@ end --- @return boolean function SystemObj:is_closing() local handle = self._state.handle - return handle == nil or handle:is_closing() + return handle == nil or handle:is_closing() or false end ---@param output fun(err:string?, data: string?)|false diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index b2aa943359..a02a93d559 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -258,7 +258,7 @@ end --- Validates a client configuration as given to |vim.lsp.start_client()|. --- ---@param config (lsp.ClientConfig) ----@return (string|fun(dispatchers:vim.rpc.Dispatchers):RpcClientPublic?) Command +---@return (string|fun(dispatchers:vim.rpc.Dispatchers):vim.lsp.rpc.PublicClient?) Command ---@return string[] Arguments ---@return string Encoding. local function validate_client_config(config) @@ -291,7 +291,7 @@ local function validate_client_config(config) 'flags.debounce_text_changes must be a number with the debounce time in milliseconds' ) - local cmd, cmd_args --- @type (string|fun(dispatchers:vim.rpc.Dispatchers):RpcClientPublic), string[] + local cmd, cmd_args --- @type (string|fun(dispatchers:vim.rpc.Dispatchers):vim.lsp.rpc.PublicClient), string[] local config_cmd = config.cmd if type(config_cmd) == 'function' then cmd = config_cmd @@ -826,6 +826,8 @@ function lsp.start_client(config) --- ---@param method (string) LSP method name ---@param params (table) The parameters for that method + ---@return any result + ---@return lsp.ResponseError error code and message set in case an exception happens during the request. function dispatch.server_request(method, params) if log.trace() then log.trace('server_request', method, params) @@ -953,7 +955,7 @@ function lsp.start_client(config) end -- Start the RPC client. - local rpc --- @type RpcClientPublic? + local rpc --- @type vim.lsp.rpc.PublicClient? if type(cmd) == 'function' then rpc = cmd(dispatch) else 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]]