rpc.lua (22510B)
1 local log = require('vim.lsp.log') 2 local protocol = require('vim.lsp.protocol') 3 local lsp_transport = require('vim.lsp._transport') 4 local strbuffer = require('vim._core.stringbuffer') 5 local validate, schedule_wrap = vim.validate, vim.schedule_wrap 6 7 --- Embeds the given string into a table and correctly computes `Content-Length`. 8 --- 9 --- @param message string 10 --- @return string message with `Content-Length` attribute 11 local function format_message_with_content_length(message) 12 return table.concat({ 13 'Content-Length: ', 14 tostring(#message), 15 '\r\n\r\n', 16 message, 17 }) 18 end 19 20 --- Extract `content-length` from the header. 21 --- 22 --- The structure of header fields conforms to [HTTP semantics](https://tools.ietf.org/html/rfc7230#section-3.2), 23 --- i.e., `header-field = field-name : OWS field-value OWS`. OWS means optional whitespace (space/horizontal tabs). 24 --- 25 --- We ignore lines ending with `\n` that don't contain `content-length`, since some servers 26 --- write log to standard output and there's no way to avoid it. 27 --- See https://github.com/neovim/neovim/pull/35743#pullrequestreview-3379705828 28 --- @param header string The header to parse 29 --- @return integer 30 local function get_content_length(header) 31 local state = 'name' 32 local i, len = 1, #header 33 local j, name = 1, 'content-length' 34 local buf = strbuffer.new() 35 local digit = true 36 while i <= len do 37 local c = header:byte(i) 38 if state == 'name' then 39 if c >= 65 and c <= 90 then -- lower case 40 c = c + 32 41 end 42 if (c == 32 or c == 9) and j == 1 then -- luacheck: ignore 542 43 -- skip OWS for compatibility only 44 elseif c == name:byte(j) then 45 j = j + 1 46 elseif c == 58 and j == 15 then 47 state = 'colon' 48 else 49 state = 'invalid' 50 end 51 elseif state == 'colon' then 52 if c ~= 32 and c ~= 9 then -- skip OWS normally 53 state = 'value' 54 i = i - 1 55 end 56 elseif state == 'value' then 57 if c == 13 and header:byte(i + 1) == 10 then -- must end with \r\n 58 local value = buf:get() 59 return assert(digit and tonumber(value), 'value of Content-Length is not number: ' .. value) 60 else 61 buf:put(string.char(c)) 62 end 63 if c < 48 and c ~= 32 and c ~= 9 or c > 57 then 64 digit = false 65 end 66 elseif state == 'invalid' then 67 if c == 10 then -- reset for next line 68 state, j = 'name', 1 69 end 70 end 71 i = i + 1 72 end 73 error('Content-Length not found in header: ' .. header) 74 end 75 76 local M = {} 77 78 --- Mapping of error codes used by the client 79 --- @enum vim.lsp.rpc.ClientErrors 80 local client_errors = { 81 INVALID_SERVER_MESSAGE = 1, 82 INVALID_SERVER_JSON = 2, 83 NO_RESULT_CALLBACK_FOUND = 3, 84 READ_ERROR = 4, 85 NOTIFICATION_HANDLER_ERROR = 5, 86 SERVER_REQUEST_HANDLER_ERROR = 6, 87 SERVER_RESULT_CALLBACK_ERROR = 7, 88 } 89 90 --- @type table<string,integer> | table<integer,string> 91 --- @nodoc 92 M.client_errors = vim.deepcopy(client_errors) 93 for k, v in pairs(client_errors) do 94 M.client_errors[v] = k 95 end 96 97 --- Constructs an error message from an LSP error object. 98 --- 99 ---@param err table The error object 100 ---@return string error_message The formatted error message 101 function M.format_rpc_error(err) 102 validate('err', err, 'table') 103 104 -- There is ErrorCodes in the LSP specification, 105 -- but in ResponseError.code it is not used and the actual type is number. 106 local code --- @type string 107 if protocol.ErrorCodes[err.code] then 108 code = string.format('code_name = %s,', protocol.ErrorCodes[err.code]) 109 else 110 code = string.format('code_name = unknown, code = %s,', err.code) 111 end 112 113 local message_parts = { 'RPC[Error]', code } 114 if err.message then 115 table.insert(message_parts, 'message =') 116 table.insert(message_parts, string.format('%q', err.message)) 117 end 118 if err.data then 119 table.insert(message_parts, 'data =') 120 table.insert(message_parts, vim.inspect(err.data)) 121 end 122 return table.concat(message_parts, ' ') 123 end 124 125 --- Creates an RPC response table `error` to be sent to the LSP response. 126 --- 127 ---@param code integer RPC error code defined, see `vim.lsp.protocol.ErrorCodes` 128 ---@param message? string arbitrary message to send to server 129 ---@param data? any arbitrary data to send to server 130 --- 131 ---@see lsp.ErrorCodes See `vim.lsp.protocol.ErrorCodes` 132 ---@return lsp.ResponseError 133 function M.rpc_response_error(code, message, data) 134 -- TODO should this error or just pick a sane error (like InternalError)? 135 ---@type string 136 local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code') 137 return setmetatable({ 138 code = code, 139 message = message or code_name, 140 data = data, 141 }, { 142 __tostring = M.format_rpc_error, 143 }) 144 end 145 146 --- Dispatchers for LSP message types. 147 --- @class vim.lsp.rpc.Dispatchers 148 --- @inlinedoc 149 --- @field notification fun(method: vim.lsp.protocol.Method.ClientToServer.Notification, params: table) 150 --- @field server_request fun(method: vim.lsp.protocol.Method.ClientToServer.Request, params: table): any?, lsp.ResponseError? 151 --- @field on_exit fun(code: integer, signal: integer) 152 --- @field on_error fun(code: integer, err: any) 153 154 --- @type vim.lsp.rpc.Dispatchers 155 local default_dispatchers = { 156 --- Default dispatcher for notifications sent to an LSP server. 157 --- 158 ---@param method vim.lsp.protocol.Method The invoked LSP method 159 ---@param params table Parameters for the invoked LSP method 160 notification = function(method, params) 161 log.debug('notification', method, params) 162 end, 163 164 --- Default dispatcher for requests sent to an LSP server. 165 --- 166 ---@param method vim.lsp.protocol.Method The invoked LSP method 167 ---@param params table Parameters for the invoked LSP method 168 ---@return any result (always nil for the default dispatchers) 169 ---@return lsp.ResponseError error `vim.lsp.protocol.ErrorCodes.MethodNotFound` 170 server_request = function(method, params) 171 log.debug('server_request', method, params) 172 return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound) 173 end, 174 175 --- Default dispatcher for when a client exits. 176 --- 177 ---@param code integer Exit code 178 ---@param signal integer Number describing the signal used to terminate (if any) 179 on_exit = function(code, signal) 180 log.info('client_exit', { code = code, signal = signal }) 181 end, 182 183 --- Default dispatcher for client errors. 184 --- 185 ---@param code integer Error code 186 ---@param err any Details about the error 187 on_error = function(code, err) 188 log.error('client_error:', M.client_errors[code], err) 189 end, 190 } 191 192 --- @async 193 local function request_parser_loop() 194 local buf = strbuffer.new() 195 while true do 196 local msg = buf:tostring() 197 local header_end = msg:find('\r\n\r\n', 1, true) 198 if header_end then 199 local header = buf:get(header_end + 1) 200 buf:skip(2) -- skip past header boundary 201 local content_length = get_content_length(header) 202 while strbuffer.len(buf) < content_length do 203 buf:put(coroutine.yield()) 204 end 205 local body = buf:get(content_length) 206 buf:put(coroutine.yield(body)) 207 else 208 buf:put(coroutine.yield()) 209 end 210 end 211 end 212 213 --- @private 214 --- @param handle_body fun(body: string) 215 --- @param on_exit? fun() 216 --- @param on_error? fun(err: any, errkind: vim.lsp.rpc.ClientErrors) 217 function M.create_read_loop(handle_body, on_exit, on_error) 218 on_exit = on_exit or function() end 219 on_error = on_error or function() end 220 local co = coroutine.create(request_parser_loop) 221 coroutine.resume(co) 222 return function(err, chunk) 223 if err then 224 on_error(err, M.client_errors.READ_ERROR) 225 return 226 end 227 228 if not chunk then 229 on_exit() 230 return 231 end 232 233 if coroutine.status(co) == 'dead' then 234 return 235 end 236 237 while true do 238 local ok, res = coroutine.resume(co, chunk) 239 if not ok then 240 on_error(res, M.client_errors.INVALID_SERVER_MESSAGE) 241 break 242 elseif res then 243 handle_body(res) 244 chunk = '' 245 else 246 break 247 end 248 end 249 end 250 end 251 252 ---@class (private) vim.lsp.rpc.Client 253 ---@field message_index integer 254 ---@field message_callbacks table<integer, function> dict of message_id to callback 255 ---@field notify_reply_callbacks table<integer, function> dict of message_id to callback 256 ---@field transport vim.lsp.rpc.Transport 257 ---@field dispatchers vim.lsp.rpc.Dispatchers 258 local Client = {} 259 260 ---@private 261 function Client:encode_and_send(payload) 262 log.debug('rpc.send', payload) 263 if self.transport:is_closing() then 264 return false 265 end 266 local jsonstr = vim.json.encode(payload) 267 268 self.transport:write(format_message_with_content_length(jsonstr)) 269 return true 270 end 271 272 ---@package 273 --- Sends a notification to the LSP server. 274 ---@param method vim.lsp.protocol.Method The invoked LSP method 275 ---@param params any Parameters for the invoked LSP method 276 ---@return boolean `true` if notification could be sent, `false` if not 277 function Client:notify(method, params) 278 return self:encode_and_send({ 279 jsonrpc = '2.0', 280 method = method, 281 params = params, 282 }) 283 end 284 285 ---@private 286 --- sends an error object to the remote LSP process. 287 function Client:send_response(request_id, err, result) 288 return self:encode_and_send({ 289 id = request_id, 290 jsonrpc = '2.0', 291 error = err, 292 result = result, 293 }) 294 end 295 296 ---@package 297 --- Sends a request to the LSP server and runs {callback} upon response. |vim.lsp.rpc.request()| 298 --- 299 ---@param method vim.lsp.protocol.Method The invoked LSP method 300 ---@param params table? Parameters for the invoked LSP method 301 ---@param callback fun(err?: lsp.ResponseError, result: any, message_id: integer) Callback to invoke 302 ---@param notify_reply_callback? fun(message_id: integer) Callback to invoke as soon as a request is no longer pending 303 ---@return boolean success `true` if request could be sent, `false` if not 304 ---@return integer? message_id if request could be sent, `nil` if not 305 function Client:request(method, params, callback, notify_reply_callback) 306 validate('callback', callback, 'function') 307 validate('notify_reply_callback', notify_reply_callback, 'function', true) 308 self.message_index = self.message_index + 1 309 local message_id = self.message_index 310 local result = self:encode_and_send({ 311 id = message_id, 312 jsonrpc = '2.0', 313 method = method, 314 params = params, 315 }) 316 317 if not result then 318 return false 319 end 320 321 self.message_callbacks[message_id] = schedule_wrap(callback) 322 if notify_reply_callback then 323 self.notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback) 324 end 325 return result, message_id 326 end 327 328 ---@package 329 ---@param errkind vim.lsp.rpc.ClientErrors 330 ---@param ... any 331 function Client:on_error(errkind, ...) 332 assert(M.client_errors[errkind]) 333 -- TODO what to do if this fails? 334 pcall(self.dispatchers.on_error, errkind, ...) 335 end 336 337 ---@private 338 ---@param errkind integer 339 ---@param status boolean 340 ---@param head any 341 ---@param ... any 342 ---@return boolean status 343 ---@return any head 344 ---@return any? ... 345 function Client:pcall_handler(errkind, status, head, ...) 346 if not status then 347 self:on_error(errkind, head, ...) 348 return status, head 349 end 350 return status, head, ... 351 end 352 353 ---@private 354 ---@param errkind integer 355 ---@param fn function 356 ---@param ... any 357 ---@return boolean status 358 ---@return any head 359 ---@return any? ... 360 function Client:try_call(errkind, fn, ...) 361 return self:pcall_handler(errkind, pcall(fn, ...)) 362 end 363 364 -- TODO periodically check message_callbacks for old requests past a certain 365 -- time and log them. This would require storing the timestamp. I could call 366 -- them with an error then, perhaps. 367 368 --- @package 369 --- @param body string 370 function Client:handle_body(body) 371 local ok, decoded = pcall(vim.json.decode, body) 372 if not ok then 373 self:on_error(M.client_errors.INVALID_SERVER_JSON, decoded) 374 return 375 end 376 log.debug('rpc.receive', decoded) 377 378 if type(decoded) ~= 'table' then 379 self:on_error(M.client_errors.INVALID_SERVER_MESSAGE, decoded) 380 elseif type(decoded.method) == 'string' and decoded.id then 381 local err --- @type lsp.ResponseError? 382 -- Schedule here so that the users functions don't trigger an error and 383 -- we can still use the result. 384 vim.schedule(coroutine.wrap(function() 385 local status, result 386 status, result, err = self:try_call( 387 M.client_errors.SERVER_REQUEST_HANDLER_ERROR, 388 self.dispatchers.server_request, 389 decoded.method, 390 decoded.params 391 ) 392 log.debug('server_request: callback result', { status = status, result = result, err = err }) 393 if status then 394 if result == nil and err == nil then 395 error( 396 string.format( 397 'method %q: either a result or an error must be sent to the server in response', 398 decoded.method 399 ) 400 ) 401 end 402 if err then 403 assert( 404 type(err) == 'table', 405 'err must be a table. Use rpc_response_error to help format errors.' 406 ) 407 ---@type string 408 local code_name = assert( 409 protocol.ErrorCodes[err.code], 410 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.' 411 ) 412 err.message = err.message or code_name 413 end 414 else 415 -- On an exception, result will contain the error message. 416 err = M.rpc_response_error(protocol.ErrorCodes.InternalError, result) 417 result = nil 418 end 419 self:send_response(decoded.id, err, result) 420 end)) 421 -- Proceed only if exactly one of 'result' or 'error' is present, as required by the LSP spec: 422 -- - If 'error' is nil, then 'result' must be present. 423 -- - If 'result' is nil, then 'error' must be present (and not vim.NIL). 424 elseif 425 decoded.id 426 and ( 427 (decoded.error == nil and decoded.result ~= nil) 428 or (decoded.result == nil and decoded.error ~= nil and decoded.error ~= vim.NIL) 429 ) 430 then 431 -- We sent a number, so we expect a number. 432 local result_id = assert(tonumber(decoded.id), 'response id must be a number') --[[@as integer]] 433 434 -- Notify the user that a response was received for the request 435 local notify_reply_callback = self.notify_reply_callbacks[result_id] 436 if notify_reply_callback then 437 validate('notify_reply_callback', notify_reply_callback, 'function') 438 notify_reply_callback(result_id) 439 self.notify_reply_callbacks[result_id] = nil 440 end 441 442 -- Do not surface RequestCancelled to users, it is RPC-internal. 443 if decoded.error then 444 assert(type(decoded.error) == 'table') 445 if decoded.error.code == protocol.ErrorCodes.RequestCancelled then 446 log.debug('Received cancellation ack', decoded) 447 -- Clear any callback since this is cancelled now. 448 -- This is safe to do assuming that these conditions hold: 449 -- - The server will not send a result callback after this cancellation. 450 -- - If the server sent this cancellation ACK after sending the result, the user of this RPC 451 -- client will ignore the result themselves. 452 if result_id then 453 self.message_callbacks[result_id] = nil 454 end 455 return 456 end 457 end 458 459 local callback = self.message_callbacks[result_id] 460 if callback then 461 self.message_callbacks[result_id] = nil 462 validate('callback', callback, 'function') 463 if decoded.error then 464 setmetatable(decoded.error, { __tostring = M.format_rpc_error }) 465 end 466 self:try_call( 467 M.client_errors.SERVER_RESULT_CALLBACK_ERROR, 468 callback, 469 decoded.error, 470 decoded.result ~= vim.NIL and decoded.result or nil, 471 result_id 472 ) 473 else 474 self:on_error(M.client_errors.NO_RESULT_CALLBACK_FOUND, decoded) 475 log.error('No callback found for server response id ' .. result_id) 476 end 477 elseif type(decoded.method) == 'string' then 478 -- Notification 479 self:try_call( 480 M.client_errors.NOTIFICATION_HANDLER_ERROR, 481 self.dispatchers.notification, 482 decoded.method, 483 decoded.params 484 ) 485 else 486 -- Invalid server message 487 self:on_error(M.client_errors.INVALID_SERVER_MESSAGE, decoded) 488 end 489 end 490 491 ---@param dispatchers vim.lsp.rpc.Dispatchers 492 ---@param transport vim.lsp.rpc.Transport 493 ---@return vim.lsp.rpc.Client 494 local function new_client(dispatchers, transport) 495 local state = { 496 message_index = 0, 497 message_callbacks = {}, 498 notify_reply_callbacks = {}, 499 transport = transport, 500 dispatchers = dispatchers, 501 } 502 return setmetatable(state, { __index = Client }) 503 end 504 505 --- Client RPC object 506 --- @class vim.lsp.rpc.PublicClient 507 --- 508 --- See [vim.lsp.rpc.request()] 509 --- @field request fun(method: vim.lsp.protocol.Method.ClientToServer.Request, params: table?, callback: fun(err?: lsp.ResponseError, result: any, request_id: integer), notify_reply_callback?: fun(message_id: integer)):boolean,integer? 510 --- 511 --- See [vim.lsp.rpc.notify()] 512 --- @field notify fun(method: vim.lsp.protocol.Method.ClientToServer.Notification, params: any): boolean 513 --- 514 --- Indicates if the RPC is closing. 515 --- @field is_closing fun(): boolean 516 --- 517 --- Terminates the RPC client. 518 --- @field terminate fun() 519 520 ---@param client vim.lsp.rpc.Client 521 ---@return vim.lsp.rpc.PublicClient 522 local function public_client(client) 523 ---@type vim.lsp.rpc.PublicClient 524 ---@diagnostic disable-next-line: missing-fields 525 local result = {} 526 527 ---@private 528 function result.is_closing() 529 return client.transport:is_closing() 530 end 531 532 ---@private 533 function result.terminate() 534 client.transport:terminate() 535 end 536 537 --- Sends a request to the LSP server and runs {callback} upon response. 538 --- 539 ---@param method (vim.lsp.protocol.Method.ClientToServer.Request) The invoked LSP method 540 ---@param params (table?) Parameters for the invoked LSP method 541 ---@param callback fun(err: lsp.ResponseError?, result: any) Callback to invoke 542 ---@param notify_reply_callback? fun(message_id: integer) Callback to invoke as soon as a request is no longer pending 543 ---@return boolean success `true` if request could be sent, `false` if not 544 ---@return integer? message_id if request could be sent, `nil` if not 545 function result.request(method, params, callback, notify_reply_callback) 546 return client:request(method, params, callback, notify_reply_callback) 547 end 548 549 --- Sends a notification to the LSP server. 550 ---@param method (vim.lsp.protocol.Method.ClientToServer.Notification) The invoked LSP method 551 ---@param params (table?) Parameters for the invoked LSP method 552 ---@return boolean `true` if notification could be sent, `false` if not 553 function result.notify(method, params) 554 return client:notify(method, params) 555 end 556 557 return result 558 end 559 560 ---@param dispatchers vim.lsp.rpc.Dispatchers? 561 ---@return vim.lsp.rpc.Dispatchers 562 local function merge_dispatchers(dispatchers) 563 if not dispatchers then 564 return default_dispatchers 565 end 566 ---@diagnostic disable-next-line: no-unknown 567 for name, fn in pairs(dispatchers) do 568 if type(fn) ~= 'function' then 569 error(string.format('dispatcher.%s must be a function', name)) 570 end 571 end 572 ---@type vim.lsp.rpc.Dispatchers 573 local merged = { 574 notification = ( 575 dispatchers.notification and schedule_wrap(dispatchers.notification) 576 or default_dispatchers.notification 577 ), 578 on_error = ( 579 dispatchers.on_error and schedule_wrap(dispatchers.on_error) 580 or default_dispatchers.on_error 581 ), 582 on_exit = dispatchers.on_exit or default_dispatchers.on_exit, 583 server_request = dispatchers.server_request or default_dispatchers.server_request, 584 } 585 return merged 586 end 587 588 --- @param client vim.lsp.rpc.Client 589 --- @param on_exit? fun() 590 local function create_client_read_loop(client, on_exit) 591 --- @param body string 592 local function handle_body(body) 593 client:handle_body(body) 594 end 595 596 --- @param errkind vim.lsp.rpc.ClientErrors 597 local function on_error(err, errkind) 598 client:on_error(errkind, err) 599 if errkind == M.client_errors.INVALID_SERVER_MESSAGE then 600 client.transport:terminate() 601 end 602 end 603 604 return M.create_read_loop(handle_body, on_exit, on_error) 605 end 606 607 --- Create a LSP RPC client factory that connects to either: 608 --- 609 --- - a named pipe (windows) 610 --- - a domain socket (unix) 611 --- - a host and port via TCP 612 --- 613 --- Return a function that can be passed to the `cmd` field for 614 --- |vim.lsp.start()|. 615 --- 616 ---@param host_or_path string host to connect to or path to a pipe/domain socket 617 ---@param port integer? TCP port to connect to. If absent the first argument must be a pipe 618 ---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient 619 function M.connect(host_or_path, port) 620 validate('host_or_path', host_or_path, 'string') 621 validate('port', port, 'number', true) 622 623 return function(dispatchers) 624 validate('dispatchers', dispatchers, 'table', true) 625 626 dispatchers = merge_dispatchers(dispatchers) 627 628 local transport = lsp_transport.TransportConnect.new() 629 local client = new_client(dispatchers, transport) 630 local on_read = create_client_read_loop(client, function() 631 transport:terminate() 632 end) 633 transport:connect(host_or_path, port, on_read, dispatchers.on_exit) 634 635 return public_client(client) 636 end 637 end 638 639 --- Additional context for the LSP server process. 640 --- @class vim.lsp.rpc.ExtraSpawnParams 641 --- @inlinedoc 642 --- @field cwd? string Working directory for the LSP server process 643 --- @field detached? boolean Detach the LSP server process from the current process 644 --- @field env? table<string,string> Additional environment variables for LSP server process. See |vim.system()| 645 646 --- Starts an LSP server process and create an LSP RPC client object to 647 --- interact with it. Communication with the spawned process happens via stdio. For 648 --- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect()| 649 --- 650 --- @param cmd string[] Command to start the LSP server. 651 --- @param dispatchers? vim.lsp.rpc.Dispatchers 652 --- @param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams 653 --- @return vim.lsp.rpc.PublicClient 654 function M.start(cmd, dispatchers, extra_spawn_params) 655 log.info('Starting RPC client', { cmd = cmd, extra = extra_spawn_params }) 656 657 validate('cmd', cmd, 'table') 658 validate('dispatchers', dispatchers, 'table', true) 659 660 dispatchers = merge_dispatchers(dispatchers) 661 662 local transport = lsp_transport.TransportRun.new() 663 local client = new_client(dispatchers, transport) 664 local on_read = create_client_read_loop(client) 665 transport:run(cmd, extra_spawn_params, on_read, dispatchers.on_exit) 666 667 return public_client(client) 668 end 669 670 return M