rpc.lua 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. local vim = vim
  2. local uv = vim.loop
  3. local log = require('vim.lsp.log')
  4. local protocol = require('vim.lsp.protocol')
  5. local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
  6. local is_win = uv.os_uname().version:find('Windows')
  7. ---@private
  8. --- Checks whether a given path exists and is a directory.
  9. ---@param filename (string) path to check
  10. ---@returns (bool)
  11. local function is_dir(filename)
  12. local stat = uv.fs_stat(filename)
  13. return stat and stat.type == 'directory' or false
  14. end
  15. ---@private
  16. --- Merges current process env with the given env and returns the result as
  17. --- a list of "k=v" strings.
  18. ---
  19. --- <pre>
  20. --- Example:
  21. ---
  22. --- in: { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", }
  23. --- out: { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", }
  24. --- </pre>
  25. ---@param env (table) table of environment variable assignments
  26. ---@returns (table) list of `"k=v"` strings
  27. local function env_merge(env)
  28. if env == nil then
  29. return env
  30. end
  31. -- Merge.
  32. env = vim.tbl_extend('force', vim.fn.environ(), env)
  33. local final_env = {}
  34. for k, v in pairs(env) do
  35. assert(type(k) == 'string', 'env must be a dict')
  36. table.insert(final_env, k .. '=' .. tostring(v))
  37. end
  38. return final_env
  39. end
  40. ---@private
  41. --- Embeds the given string into a table and correctly computes `Content-Length`.
  42. ---
  43. ---@param encoded_message (string)
  44. ---@returns (table) table containing encoded message and `Content-Length` attribute
  45. local function format_message_with_content_length(encoded_message)
  46. return table.concat({
  47. 'Content-Length: ',
  48. tostring(#encoded_message),
  49. '\r\n\r\n',
  50. encoded_message,
  51. })
  52. end
  53. ---@private
  54. --- Parses an LSP Message's header
  55. ---
  56. ---@param header string: The header to parse.
  57. ---@return table parsed headers
  58. local function parse_headers(header)
  59. assert(type(header) == 'string', 'header must be a string')
  60. local headers = {}
  61. for line in vim.gsplit(header, '\r\n', true) do
  62. if line == '' then
  63. break
  64. end
  65. local key, value = line:match('^%s*(%S+)%s*:%s*(.+)%s*$')
  66. if key then
  67. key = key:lower():gsub('%-', '_')
  68. headers[key] = value
  69. else
  70. local _ = log.error() and log.error('invalid header line %q', line)
  71. error(string.format('invalid header line %q', line))
  72. end
  73. end
  74. headers.content_length = tonumber(headers.content_length)
  75. or error(string.format('Content-Length not found in headers. %q', header))
  76. return headers
  77. end
  78. -- This is the start of any possible header patterns. The gsub converts it to a
  79. -- case insensitive pattern.
  80. local header_start_pattern = ('content'):gsub('%w', function(c)
  81. return '[' .. c .. c:upper() .. ']'
  82. end)
  83. ---@private
  84. --- The actual workhorse.
  85. local function request_parser_loop()
  86. local buffer = '' -- only for header part
  87. while true do
  88. -- A message can only be complete if it has a double CRLF and also the full
  89. -- payload, so first let's check for the CRLFs
  90. local start, finish = buffer:find('\r\n\r\n', 1, true)
  91. -- Start parsing the headers
  92. if start then
  93. -- This is a workaround for servers sending initial garbage before
  94. -- sending headers, such as if a bash script sends stdout. It assumes
  95. -- that we know all of the headers ahead of time. At this moment, the
  96. -- only valid headers start with "Content-*", so that's the thing we will
  97. -- be searching for.
  98. -- TODO(ashkan) I'd like to remove this, but it seems permanent :(
  99. local buffer_start = buffer:find(header_start_pattern)
  100. local headers = parse_headers(buffer:sub(buffer_start, start - 1))
  101. local content_length = headers.content_length
  102. -- Use table instead of just string to buffer the message. It prevents
  103. -- a ton of strings allocating.
  104. -- ref. http://www.lua.org/pil/11.6.html
  105. local body_chunks = { buffer:sub(finish + 1) }
  106. local body_length = #body_chunks[1]
  107. -- Keep waiting for data until we have enough.
  108. while body_length < content_length do
  109. local chunk = coroutine.yield()
  110. or error('Expected more data for the body. The server may have died.') -- TODO hmm.
  111. table.insert(body_chunks, chunk)
  112. body_length = body_length + #chunk
  113. end
  114. local last_chunk = body_chunks[#body_chunks]
  115. body_chunks[#body_chunks] = last_chunk:sub(1, content_length - body_length - 1)
  116. local rest = ''
  117. if body_length > content_length then
  118. rest = last_chunk:sub(content_length - body_length)
  119. end
  120. local body = table.concat(body_chunks)
  121. -- Yield our data.
  122. buffer = rest
  123. .. (
  124. coroutine.yield(headers, body)
  125. or error('Expected more data for the body. The server may have died.')
  126. ) -- TODO hmm.
  127. else
  128. -- Get more data since we don't have enough.
  129. buffer = buffer
  130. .. (
  131. coroutine.yield() or error('Expected more data for the header. The server may have died.')
  132. ) -- TODO hmm.
  133. end
  134. end
  135. end
  136. --- Mapping of error codes used by the client
  137. local client_errors = {
  138. INVALID_SERVER_MESSAGE = 1,
  139. INVALID_SERVER_JSON = 2,
  140. NO_RESULT_CALLBACK_FOUND = 3,
  141. READ_ERROR = 4,
  142. NOTIFICATION_HANDLER_ERROR = 5,
  143. SERVER_REQUEST_HANDLER_ERROR = 6,
  144. SERVER_RESULT_CALLBACK_ERROR = 7,
  145. }
  146. client_errors = vim.tbl_add_reverse_lookup(client_errors)
  147. --- Constructs an error message from an LSP error object.
  148. ---
  149. ---@param err (table) The error object
  150. ---@returns (string) The formatted error message
  151. local function format_rpc_error(err)
  152. validate({
  153. err = { err, 't' },
  154. })
  155. -- There is ErrorCodes in the LSP specification,
  156. -- but in ResponseError.code it is not used and the actual type is number.
  157. local code
  158. if protocol.ErrorCodes[err.code] then
  159. code = string.format('code_name = %s,', protocol.ErrorCodes[err.code])
  160. else
  161. code = string.format('code_name = unknown, code = %s,', err.code)
  162. end
  163. local message_parts = { 'RPC[Error]', code }
  164. if err.message then
  165. table.insert(message_parts, 'message =')
  166. table.insert(message_parts, string.format('%q', err.message))
  167. end
  168. if err.data then
  169. table.insert(message_parts, 'data =')
  170. table.insert(message_parts, vim.inspect(err.data))
  171. end
  172. return table.concat(message_parts, ' ')
  173. end
  174. --- Creates an RPC response object/table.
  175. ---
  176. ---@param code number RPC error code defined in `vim.lsp.protocol.ErrorCodes`
  177. ---@param message string|nil arbitrary message to send to server
  178. ---@param data any|nil arbitrary data to send to server
  179. local function rpc_response_error(code, message, data)
  180. -- TODO should this error or just pick a sane error (like InternalError)?
  181. local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code')
  182. return setmetatable({
  183. code = code,
  184. message = message or code_name,
  185. data = data,
  186. }, {
  187. __tostring = format_rpc_error,
  188. })
  189. end
  190. local default_dispatchers = {}
  191. ---@private
  192. --- Default dispatcher for notifications sent to an LSP server.
  193. ---
  194. ---@param method (string) The invoked LSP method
  195. ---@param params (table): Parameters for the invoked LSP method
  196. function default_dispatchers.notification(method, params)
  197. local _ = log.debug() and log.debug('notification', method, params)
  198. end
  199. ---@private
  200. --- Default dispatcher for requests sent to an LSP server.
  201. ---
  202. ---@param method (string) The invoked LSP method
  203. ---@param params (table): Parameters for the invoked LSP method
  204. ---@returns `nil` and `vim.lsp.protocol.ErrorCodes.MethodNotFound`.
  205. function default_dispatchers.server_request(method, params)
  206. local _ = log.debug() and log.debug('server_request', method, params)
  207. return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound)
  208. end
  209. ---@private
  210. --- Default dispatcher for when a client exits.
  211. ---
  212. ---@param code (number): Exit code
  213. ---@param signal (number): Number describing the signal used to terminate (if
  214. ---any)
  215. function default_dispatchers.on_exit(code, signal)
  216. local _ = log.info() and log.info('client_exit', { code = code, signal = signal })
  217. end
  218. ---@private
  219. --- Default dispatcher for client errors.
  220. ---
  221. ---@param code (number): Error code
  222. ---@param err (any): Details about the error
  223. ---any)
  224. function default_dispatchers.on_error(code, err)
  225. local _ = log.error() and log.error('client_error:', client_errors[code], err)
  226. end
  227. ---@private
  228. local function create_read_loop(handle_body, on_no_chunk, on_error)
  229. local parse_chunk = coroutine.wrap(request_parser_loop)
  230. parse_chunk()
  231. return function(err, chunk)
  232. if err then
  233. on_error(err)
  234. return
  235. end
  236. if not chunk then
  237. if on_no_chunk then
  238. on_no_chunk()
  239. end
  240. return
  241. end
  242. while true do
  243. local headers, body = parse_chunk(chunk)
  244. if headers then
  245. handle_body(body)
  246. chunk = ''
  247. else
  248. break
  249. end
  250. end
  251. end
  252. end
  253. ---@class RpcClient
  254. ---@field message_index number
  255. ---@field message_callbacks table
  256. ---@field notify_reply_callbacks table
  257. ---@field transport table
  258. ---@field dispatchers table
  259. ---@class RpcClient
  260. local Client = {}
  261. ---@private
  262. function Client:encode_and_send(payload)
  263. local _ = log.debug() and log.debug('rpc.send', payload)
  264. if self.transport.is_closing() then
  265. return false
  266. end
  267. local encoded = vim.json.encode(payload)
  268. self.transport.write(format_message_with_content_length(encoded))
  269. return true
  270. end
  271. ---@private
  272. --- Sends a notification to the LSP server.
  273. ---@param method (string) The invoked LSP method
  274. ---@param params (table|nil): Parameters for the invoked LSP method
  275. ---@returns (bool) `true` if notification could be sent, `false` if not
  276. function Client:notify(method, params)
  277. return self:encode_and_send({
  278. jsonrpc = '2.0',
  279. method = method,
  280. params = params,
  281. })
  282. end
  283. ---@private
  284. --- sends an error object to the remote LSP process.
  285. function Client:send_response(request_id, err, result)
  286. return self:encode_and_send({
  287. id = request_id,
  288. jsonrpc = '2.0',
  289. error = err,
  290. result = result,
  291. })
  292. end
  293. ---@private
  294. --- Sends a request to the LSP server and runs {callback} upon response.
  295. ---
  296. ---@param method (string) The invoked LSP method
  297. ---@param params (table|nil) Parameters for the invoked LSP method
  298. ---@param callback (function) Callback to invoke
  299. ---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending
  300. ---@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not
  301. function Client:request(method, params, callback, notify_reply_callback)
  302. validate({
  303. callback = { callback, 'f' },
  304. notify_reply_callback = { notify_reply_callback, 'f', true },
  305. })
  306. self.message_index = self.message_index + 1
  307. local message_id = self.message_index
  308. local result = self:encode_and_send({
  309. id = message_id,
  310. jsonrpc = '2.0',
  311. method = method,
  312. params = params,
  313. })
  314. local message_callbacks = self.message_callbacks
  315. local notify_reply_callbacks = self.notify_reply_callbacks
  316. if result then
  317. if message_callbacks then
  318. message_callbacks[message_id] = schedule_wrap(callback)
  319. else
  320. return false
  321. end
  322. if notify_reply_callback and notify_reply_callbacks then
  323. notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback)
  324. end
  325. return result, message_id
  326. else
  327. return false
  328. end
  329. end
  330. ---@private
  331. function Client:on_error(errkind, ...)
  332. assert(client_errors[errkind])
  333. -- TODO what to do if this fails?
  334. pcall(self.dispatchers.on_error, errkind, ...)
  335. end
  336. ---@private
  337. function Client:pcall_handler(errkind, status, head, ...)
  338. if not status then
  339. self:on_error(errkind, head, ...)
  340. return status, head
  341. end
  342. return status, head, ...
  343. end
  344. ---@private
  345. function Client:try_call(errkind, fn, ...)
  346. return self:pcall_handler(errkind, pcall(fn, ...))
  347. end
  348. -- TODO periodically check message_callbacks for old requests past a certain
  349. -- time and log them. This would require storing the timestamp. I could call
  350. -- them with an error then, perhaps.
  351. ---@private
  352. function Client:handle_body(body)
  353. local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } })
  354. if not ok then
  355. self:on_error(client_errors.INVALID_SERVER_JSON, decoded)
  356. return
  357. end
  358. local _ = log.debug() and log.debug('rpc.receive', decoded)
  359. if type(decoded.method) == 'string' and decoded.id then
  360. local err
  361. -- Schedule here so that the users functions don't trigger an error and
  362. -- we can still use the result.
  363. schedule(function()
  364. local status, result
  365. status, result, err = self:try_call(
  366. client_errors.SERVER_REQUEST_HANDLER_ERROR,
  367. self.dispatchers.server_request,
  368. decoded.method,
  369. decoded.params
  370. )
  371. local _ = log.debug()
  372. and log.debug(
  373. 'server_request: callback result',
  374. { status = status, result = result, err = err }
  375. )
  376. if status then
  377. if result == nil and err == nil then
  378. error(
  379. string.format(
  380. 'method %q: either a result or an error must be sent to the server in response',
  381. decoded.method
  382. )
  383. )
  384. end
  385. if err then
  386. assert(
  387. type(err) == 'table',
  388. 'err must be a table. Use rpc_response_error to help format errors.'
  389. )
  390. local code_name = assert(
  391. protocol.ErrorCodes[err.code],
  392. 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.'
  393. )
  394. err.message = err.message or code_name
  395. end
  396. else
  397. -- On an exception, result will contain the error message.
  398. err = rpc_response_error(protocol.ErrorCodes.InternalError, result)
  399. result = nil
  400. end
  401. self:send_response(decoded.id, err, result)
  402. end)
  403. -- This works because we are expecting vim.NIL here
  404. elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
  405. -- We sent a number, so we expect a number.
  406. local result_id = assert(tonumber(decoded.id), 'response id must be a number')
  407. -- Notify the user that a response was received for the request
  408. local notify_reply_callbacks = self.notify_reply_callbacks
  409. local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id]
  410. if notify_reply_callback then
  411. validate({
  412. notify_reply_callback = { notify_reply_callback, 'f' },
  413. })
  414. notify_reply_callback(result_id)
  415. notify_reply_callbacks[result_id] = nil
  416. end
  417. local message_callbacks = self.message_callbacks
  418. -- Do not surface RequestCancelled to users, it is RPC-internal.
  419. if decoded.error then
  420. local mute_error = false
  421. if decoded.error.code == protocol.ErrorCodes.RequestCancelled then
  422. local _ = log.debug() and log.debug('Received cancellation ack', decoded)
  423. mute_error = true
  424. end
  425. if mute_error then
  426. -- Clear any callback since this is cancelled now.
  427. -- This is safe to do assuming that these conditions hold:
  428. -- - The server will not send a result callback after this cancellation.
  429. -- - If the server sent this cancellation ACK after sending the result, the user of this RPC
  430. -- client will ignore the result themselves.
  431. if result_id and message_callbacks then
  432. message_callbacks[result_id] = nil
  433. end
  434. return
  435. end
  436. end
  437. local callback = message_callbacks and message_callbacks[result_id]
  438. if callback then
  439. message_callbacks[result_id] = nil
  440. validate({
  441. callback = { callback, 'f' },
  442. })
  443. if decoded.error then
  444. decoded.error = setmetatable(decoded.error, {
  445. __tostring = format_rpc_error,
  446. })
  447. end
  448. self:try_call(
  449. client_errors.SERVER_RESULT_CALLBACK_ERROR,
  450. callback,
  451. decoded.error,
  452. decoded.result
  453. )
  454. else
  455. self:on_error(client_errors.NO_RESULT_CALLBACK_FOUND, decoded)
  456. local _ = log.error() and log.error('No callback found for server response id ' .. result_id)
  457. end
  458. elseif type(decoded.method) == 'string' then
  459. -- Notification
  460. self:try_call(
  461. client_errors.NOTIFICATION_HANDLER_ERROR,
  462. self.dispatchers.notification,
  463. decoded.method,
  464. decoded.params
  465. )
  466. else
  467. -- Invalid server message
  468. self:on_error(client_errors.INVALID_SERVER_MESSAGE, decoded)
  469. end
  470. end
  471. ---@private
  472. ---@return RpcClient
  473. local function new_client(dispatchers, transport)
  474. local state = {
  475. message_index = 0,
  476. message_callbacks = {},
  477. notify_reply_callbacks = {},
  478. transport = transport,
  479. dispatchers = dispatchers,
  480. }
  481. return setmetatable(state, { __index = Client })
  482. end
  483. ---@private
  484. ---@param client RpcClient
  485. local function public_client(client)
  486. local result = {}
  487. ---@private
  488. function result.is_closing()
  489. return client.transport.is_closing()
  490. end
  491. ---@private
  492. function result.terminate()
  493. client.transport.terminate()
  494. end
  495. --- Sends a request to the LSP server and runs {callback} upon response.
  496. ---
  497. ---@param method (string) The invoked LSP method
  498. ---@param params (table|nil) Parameters for the invoked LSP method
  499. ---@param callback (function) Callback to invoke
  500. ---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending
  501. ---@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not
  502. function result.request(method, params, callback, notify_reply_callback)
  503. return client:request(method, params, callback, notify_reply_callback)
  504. end
  505. --- Sends a notification to the LSP server.
  506. ---@param method (string) The invoked LSP method
  507. ---@param params (table|nil): Parameters for the invoked LSP method
  508. ---@returns (bool) `true` if notification could be sent, `false` if not
  509. function result.notify(method, params)
  510. return client:notify(method, params)
  511. end
  512. return result
  513. end
  514. ---@private
  515. local function merge_dispatchers(dispatchers)
  516. if dispatchers then
  517. local user_dispatchers = dispatchers
  518. dispatchers = {}
  519. for dispatch_name, default_dispatch in pairs(default_dispatchers) do
  520. local user_dispatcher = user_dispatchers[dispatch_name]
  521. if user_dispatcher then
  522. if type(user_dispatcher) ~= 'function' then
  523. error(string.format('dispatcher.%s must be a function', dispatch_name))
  524. end
  525. -- server_request is wrapped elsewhere.
  526. if
  527. not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
  528. then
  529. user_dispatcher = schedule_wrap(user_dispatcher)
  530. end
  531. dispatchers[dispatch_name] = user_dispatcher
  532. else
  533. dispatchers[dispatch_name] = default_dispatch
  534. end
  535. end
  536. else
  537. dispatchers = default_dispatchers
  538. end
  539. return dispatchers
  540. end
  541. --- Create a LSP RPC client factory that connects via TCP to the given host
  542. --- and port
  543. ---
  544. ---@param host string
  545. ---@param port number
  546. ---@return function
  547. local function connect(host, port)
  548. return function(dispatchers)
  549. dispatchers = merge_dispatchers(dispatchers)
  550. local tcp = uv.new_tcp()
  551. local closing = false
  552. local transport = {
  553. write = function(msg)
  554. tcp:write(msg)
  555. end,
  556. is_closing = function()
  557. return closing
  558. end,
  559. terminate = function()
  560. if not closing then
  561. closing = true
  562. tcp:shutdown()
  563. tcp:close()
  564. dispatchers.on_exit(0, 0)
  565. end
  566. end,
  567. }
  568. local client = new_client(dispatchers, transport)
  569. tcp:connect(host, port, function(err)
  570. if err then
  571. vim.schedule(function()
  572. vim.notify(
  573. string.format('Could not connect to %s:%s, reason: %s', host, port, vim.inspect(err)),
  574. vim.log.levels.WARN
  575. )
  576. end)
  577. return
  578. end
  579. local handle_body = function(body)
  580. client:handle_body(body)
  581. end
  582. tcp:read_start(create_read_loop(handle_body, transport.terminate, function(read_err)
  583. client:on_error(client_errors.READ_ERROR, read_err)
  584. end))
  585. end)
  586. return public_client(client)
  587. end
  588. end
  589. --- Starts an LSP server process and create an LSP RPC client object to
  590. --- interact with it. Communication with the spawned process happens via stdio. For
  591. --- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect()|
  592. ---
  593. ---@param cmd (string) Command to start the LSP server.
  594. ---@param cmd_args (table) List of additional string arguments to pass to {cmd}.
  595. ---@param dispatchers table|nil Dispatchers for LSP message types. Valid
  596. ---dispatcher names are:
  597. --- - `"notification"`
  598. --- - `"server_request"`
  599. --- - `"on_error"`
  600. --- - `"on_exit"`
  601. ---@param extra_spawn_params table|nil Additional context for the LSP
  602. --- server process. May contain:
  603. --- - {cwd} (string) Working directory for the LSP server process
  604. --- - {env} (table) Additional environment variables for LSP server process
  605. ---@returns Client RPC object.
  606. ---
  607. ---@returns Methods:
  608. --- - `notify()` |vim.lsp.rpc.notify()|
  609. --- - `request()` |vim.lsp.rpc.request()|
  610. --- - `is_closing()` returns a boolean indicating if the RPC is closing.
  611. --- - `terminate()` terminates the RPC client.
  612. local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
  613. local _ = log.info()
  614. and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
  615. validate({
  616. cmd = { cmd, 's' },
  617. cmd_args = { cmd_args, 't' },
  618. dispatchers = { dispatchers, 't', true },
  619. })
  620. if extra_spawn_params and extra_spawn_params.cwd then
  621. assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory')
  622. end
  623. dispatchers = merge_dispatchers(dispatchers)
  624. local stdin = uv.new_pipe(false)
  625. local stdout = uv.new_pipe(false)
  626. local stderr = uv.new_pipe(false)
  627. local handle, pid
  628. local client = new_client(dispatchers, {
  629. write = function(msg)
  630. stdin:write(msg)
  631. end,
  632. is_closing = function()
  633. return handle == nil or handle:is_closing()
  634. end,
  635. terminate = function()
  636. if handle then
  637. handle:kill(15)
  638. end
  639. end,
  640. })
  641. ---@private
  642. --- Callback for |vim.loop.spawn()| Closes all streams and runs the `on_exit` dispatcher.
  643. ---@param code (number) Exit code
  644. ---@param signal (number) Signal that was used to terminate (if any)
  645. local function onexit(code, signal)
  646. stdin:close()
  647. stdout:close()
  648. stderr:close()
  649. handle:close()
  650. dispatchers.on_exit(code, signal)
  651. end
  652. local spawn_params = {
  653. args = cmd_args,
  654. stdio = { stdin, stdout, stderr },
  655. detached = not is_win,
  656. }
  657. if extra_spawn_params then
  658. spawn_params.cwd = extra_spawn_params.cwd
  659. spawn_params.env = env_merge(extra_spawn_params.env)
  660. if extra_spawn_params.detached ~= nil then
  661. spawn_params.detached = extra_spawn_params.detached
  662. end
  663. end
  664. handle, pid = uv.spawn(cmd, spawn_params, onexit)
  665. if handle == nil then
  666. stdin:close()
  667. stdout:close()
  668. stderr:close()
  669. local msg = string.format('Spawning language server with cmd: `%s` failed', cmd)
  670. if string.match(pid, 'ENOENT') then
  671. msg = msg
  672. .. '. The language server is either not installed, missing from PATH, or not executable.'
  673. else
  674. msg = msg .. string.format(' with error message: %s', pid)
  675. end
  676. vim.notify(msg, vim.log.levels.WARN)
  677. return
  678. end
  679. stderr:read_start(function(_, chunk)
  680. if chunk then
  681. local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk)
  682. end
  683. end)
  684. local handle_body = function(body)
  685. client:handle_body(body)
  686. end
  687. stdout:read_start(create_read_loop(handle_body, nil, function(err)
  688. client:on_error(client_errors.READ_ERROR, err)
  689. end))
  690. return public_client(client)
  691. end
  692. return {
  693. start = start,
  694. connect = connect,
  695. rpc_response_error = rpc_response_error,
  696. format_rpc_error = format_rpc_error,
  697. client_errors = client_errors,
  698. create_read_loop = create_read_loop,
  699. }
  700. -- vim:sw=2 ts=2 et