aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp/rpc.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/lsp/rpc.lua')
-rw-r--r--runtime/lua/vim/lsp/rpc.lua192
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