diff options
Diffstat (limited to 'runtime/lua/vim/lsp/rpc.lua')
-rw-r--r-- | runtime/lua/vim/lsp/rpc.lua | 192 |
1 files changed, 111 insertions, 81 deletions
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<string,string> 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<integer,function> +---@field notify_reply_callbacks table<integer,function> ---@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 |