handlers.lua 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. local log = require('vim.lsp.log')
  2. local protocol = require('vim.lsp.protocol')
  3. local ms = protocol.Methods
  4. local util = require('vim.lsp.util')
  5. local api = vim.api
  6. local completion = require('vim.lsp.completion')
  7. --- @type table<string, lsp.Handler>
  8. local M = {}
  9. --- @deprecated
  10. --- Client to server response handlers.
  11. --- @type table<vim.lsp.protocol.Method.ClientToServer, lsp.Handler>
  12. local RCS = {}
  13. --- Server to client request handlers.
  14. --- @type table<vim.lsp.protocol.Method.ServerToClient, lsp.Handler>
  15. local RSC = {}
  16. --- Server to client notification handlers.
  17. --- @type table<vim.lsp.protocol.Method.ServerToClient, lsp.Handler>
  18. local NSC = {}
  19. --- Writes to error buffer.
  20. ---@param ... string Will be concatenated before being written
  21. local function err_message(...)
  22. vim.notify(table.concat(vim.iter({ ... }):flatten():totable()), vim.log.levels.ERROR)
  23. api.nvim_command('redraw')
  24. end
  25. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
  26. RCS[ms.workspace_executeCommand] = function(_, _, _)
  27. -- Error handling is done implicitly by wrapping all handlers; see end of this file
  28. end
  29. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
  30. ---@param params lsp.ProgressParams
  31. ---@param ctx lsp.HandlerContext
  32. ---@diagnostic disable-next-line:no-unknown
  33. RSC[ms.dollar_progress] = function(_, params, ctx)
  34. local client = vim.lsp.get_client_by_id(ctx.client_id)
  35. if not client then
  36. err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
  37. return vim.NIL
  38. end
  39. local kind = nil
  40. local value = params.value
  41. if type(value) == 'table' then
  42. kind = value.kind --- @type string
  43. -- Carry over title of `begin` messages to `report` and `end` messages
  44. -- So that consumers always have it available, even if they consume a
  45. -- subset of the full sequence
  46. if kind == 'begin' then
  47. client.progress.pending[params.token] = value.title
  48. else
  49. value.title = client.progress.pending[params.token]
  50. if kind == 'end' then
  51. client.progress.pending[params.token] = nil
  52. end
  53. end
  54. end
  55. client.progress:push(params)
  56. api.nvim_exec_autocmds('LspProgress', {
  57. pattern = kind,
  58. modeline = false,
  59. data = { client_id = ctx.client_id, params = params },
  60. })
  61. end
  62. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
  63. ---@param params lsp.WorkDoneProgressCreateParams
  64. ---@param ctx lsp.HandlerContext
  65. RSC[ms.window_workDoneProgress_create] = function(_, params, ctx)
  66. local client = vim.lsp.get_client_by_id(ctx.client_id)
  67. if not client then
  68. err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
  69. return vim.NIL
  70. end
  71. client.progress:push(params)
  72. return vim.NIL
  73. end
  74. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
  75. ---@param params lsp.ShowMessageRequestParams
  76. RSC[ms.window_showMessageRequest] = function(_, params)
  77. local actions = params.actions or {}
  78. local co, is_main = coroutine.running()
  79. if co and not is_main then
  80. local opts = {
  81. kind = 'lsp_message',
  82. prompt = params.message .. ': ',
  83. format_item = function(action)
  84. return (action.title:gsub('\r\n', '\\r\\n')):gsub('\n', '\\n')
  85. end,
  86. }
  87. vim.ui.select(actions, opts, function(choice)
  88. -- schedule to ensure resume doesn't happen _before_ yield with
  89. -- default synchronous vim.ui.select
  90. vim.schedule(function()
  91. coroutine.resume(co, choice or vim.NIL)
  92. end)
  93. end)
  94. return coroutine.yield()
  95. else
  96. local option_strings = { params.message, '\nRequest Actions:' }
  97. for i, action in ipairs(actions) do
  98. local title = action.title:gsub('\r\n', '\\r\\n')
  99. title = title:gsub('\n', '\\n')
  100. table.insert(option_strings, string.format('%d. %s', i, title))
  101. end
  102. local choice = vim.fn.inputlist(option_strings)
  103. if choice < 1 or choice > #actions then
  104. return vim.NIL
  105. else
  106. return actions[choice]
  107. end
  108. end
  109. end
  110. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
  111. --- @param params lsp.RegistrationParams
  112. RSC[ms.client_registerCapability] = function(_, params, ctx)
  113. local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
  114. client:_register(params.registrations)
  115. for bufnr in pairs(client.attached_buffers) do
  116. vim.lsp._set_defaults(client, bufnr)
  117. end
  118. return vim.NIL
  119. end
  120. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
  121. --- @param params lsp.UnregistrationParams
  122. RSC[ms.client_unregisterCapability] = function(_, params, ctx)
  123. local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
  124. client:_unregister(params.unregisterations)
  125. return vim.NIL
  126. end
  127. -- TODO(lewis6991): Do we need to notify other servers?
  128. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
  129. RSC[ms.workspace_applyEdit] = function(_, params, ctx)
  130. assert(
  131. params,
  132. 'workspace/applyEdit must be called with `ApplyWorkspaceEditParams`. Server is violating the specification'
  133. )
  134. -- TODO(ashkan) Do something more with label?
  135. local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
  136. if params.label then
  137. print('Workspace edit', params.label)
  138. end
  139. local status, result = pcall(util.apply_workspace_edit, params.edit, client.offset_encoding)
  140. return {
  141. applied = status,
  142. failureReason = result,
  143. }
  144. end
  145. ---@param table table e.g., { foo = { bar = "z" } }
  146. ---@param section string indicating the field of the table, e.g., "foo.bar"
  147. ---@return any|nil setting value read from the table, or `nil` not found
  148. local function lookup_section(table, section)
  149. local keys = vim.split(section, '.', { plain = true }) --- @type string[]
  150. return vim.tbl_get(table, unpack(keys))
  151. end
  152. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
  153. --- @param params lsp.ConfigurationParams
  154. RSC[ms.workspace_configuration] = function(_, params, ctx)
  155. local client = vim.lsp.get_client_by_id(ctx.client_id)
  156. if not client then
  157. err_message(
  158. 'LSP[',
  159. ctx.client_id,
  160. '] client has shut down after sending a workspace/configuration request'
  161. )
  162. return
  163. end
  164. if not params.items then
  165. return {}
  166. end
  167. local response = {}
  168. for _, item in ipairs(params.items) do
  169. if item.section then
  170. local value = lookup_section(client.settings, item.section)
  171. -- For empty sections with no explicit '' key, return settings as is
  172. if value == nil and item.section == '' then
  173. value = client.settings
  174. end
  175. if value == nil then
  176. value = vim.NIL
  177. end
  178. table.insert(response, value)
  179. end
  180. end
  181. return response
  182. end
  183. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders
  184. RSC[ms.workspace_workspaceFolders] = function(_, _, ctx)
  185. local client = vim.lsp.get_client_by_id(ctx.client_id)
  186. if not client then
  187. err_message('LSP[id=', ctx.client_id, '] client has shut down after sending the message')
  188. return
  189. end
  190. return client.workspace_folders or vim.NIL
  191. end
  192. NSC[ms.textDocument_publishDiagnostics] = function(...)
  193. return vim.lsp.diagnostic.on_publish_diagnostics(...)
  194. end
  195. --- @private
  196. RCS[ms.textDocument_diagnostic] = function(...)
  197. return vim.lsp.diagnostic.on_diagnostic(...)
  198. end
  199. --- @private
  200. RCS[ms.textDocument_codeLens] = function(...)
  201. return vim.lsp.codelens.on_codelens(...)
  202. end
  203. --- @private
  204. RCS[ms.textDocument_inlayHint] = function(...)
  205. return vim.lsp.inlay_hint.on_inlayhint(...)
  206. end
  207. --- Return a function that converts LSP responses to list items and opens the list
  208. ---
  209. --- The returned function has an optional {config} parameter that accepts |vim.lsp.ListOpts|
  210. ---
  211. ---@param map_result fun(resp, bufnr: integer, position_encoding: 'utf-8'|'utf-16'|'utf-32'): table to convert the response
  212. ---@param entity string name of the resource used in a `not found` error message
  213. ---@param title_fn fun(ctx: lsp.HandlerContext): string Function to call to generate list title
  214. ---@return lsp.Handler
  215. local function response_to_list(map_result, entity, title_fn)
  216. --- @diagnostic disable-next-line:redundant-parameter
  217. return function(_, result, ctx, config)
  218. if not result or vim.tbl_isempty(result) then
  219. vim.notify('No ' .. entity .. ' found')
  220. return
  221. end
  222. config = config or {}
  223. local title = title_fn(ctx)
  224. local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
  225. local items = map_result(result, ctx.bufnr, client.offset_encoding)
  226. local list = { title = title, items = items, context = ctx }
  227. if config.on_list then
  228. assert(vim.is_callable(config.on_list), 'on_list is not a function')
  229. config.on_list(list)
  230. elseif config.loclist then
  231. vim.fn.setloclist(0, {}, ' ', list)
  232. vim.cmd.lopen()
  233. else
  234. vim.fn.setqflist({}, ' ', list)
  235. vim.cmd('botright copen')
  236. end
  237. end
  238. end
  239. --- @deprecated remove in 0.13
  240. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
  241. RCS[ms.textDocument_documentSymbol] = response_to_list(
  242. util.symbols_to_items,
  243. 'document symbols',
  244. function(ctx)
  245. local fname = vim.fn.fnamemodify(vim.uri_to_fname(ctx.params.textDocument.uri), ':.')
  246. return string.format('Symbols in %s', fname)
  247. end
  248. )
  249. --- @deprecated remove in 0.13
  250. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
  251. RCS[ms.workspace_symbol] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
  252. return string.format("Symbols matching '%s'", ctx.params.query)
  253. end)
  254. --- @deprecated remove in 0.13
  255. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
  256. RCS[ms.textDocument_rename] = function(_, result, ctx)
  257. if not result then
  258. vim.notify("Language server couldn't provide rename result", vim.log.levels.INFO)
  259. return
  260. end
  261. local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
  262. util.apply_workspace_edit(result, client.offset_encoding)
  263. end
  264. --- @deprecated remove in 0.13
  265. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
  266. RCS[ms.textDocument_rangeFormatting] = function(_, result, ctx)
  267. if not result then
  268. return
  269. end
  270. local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
  271. util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
  272. end
  273. --- @deprecated remove in 0.13
  274. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
  275. RCS[ms.textDocument_formatting] = function(_, result, ctx)
  276. if not result then
  277. return
  278. end
  279. local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
  280. util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
  281. end
  282. --- @deprecated remove in 0.13
  283. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
  284. RCS[ms.textDocument_completion] = function(_, result, _)
  285. if vim.tbl_isempty(result or {}) then
  286. return
  287. end
  288. local cursor = api.nvim_win_get_cursor(0)
  289. local row, col = cursor[1], cursor[2]
  290. local line = assert(api.nvim_buf_get_lines(0, row - 1, row, false)[1])
  291. local line_to_cursor = line:sub(col + 1)
  292. local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
  293. local prefix = line_to_cursor:sub(textMatch + 1)
  294. local matches = completion._lsp_to_complete_items(result, prefix)
  295. vim.fn.complete(textMatch + 1, matches)
  296. end
  297. --- @deprecated
  298. --- |lsp-handler| for the method "textDocument/hover"
  299. ---
  300. --- ```lua
  301. --- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
  302. --- vim.lsp.handlers.hover, {
  303. --- -- Use a sharp border with `FloatBorder` highlights
  304. --- border = "single",
  305. --- -- add the title in hover float window
  306. --- title = "hover"
  307. --- }
  308. --- )
  309. --- ```
  310. ---
  311. ---@param _ lsp.ResponseError?
  312. ---@param result lsp.Hover
  313. ---@param ctx lsp.HandlerContext
  314. ---@param config table Configuration table.
  315. --- - border: (default=nil)
  316. --- - Add borders to the floating window
  317. --- - See |vim.lsp.util.open_floating_preview()| for more options.
  318. --- @diagnostic disable-next-line:redundant-parameter
  319. function M.hover(_, result, ctx, config)
  320. config = config or {}
  321. config.focus_id = ctx.method
  322. if api.nvim_get_current_buf() ~= ctx.bufnr then
  323. -- Ignore result since buffer changed. This happens for slow language servers.
  324. return
  325. end
  326. if not (result and result.contents) then
  327. if config.silent ~= true then
  328. vim.notify('No information available')
  329. end
  330. return
  331. end
  332. local format = 'markdown'
  333. local contents ---@type string[]
  334. if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
  335. format = 'plaintext'
  336. contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
  337. else
  338. contents = util.convert_input_to_markdown_lines(result.contents)
  339. end
  340. if vim.tbl_isempty(contents) then
  341. if config.silent ~= true then
  342. vim.notify('No information available')
  343. end
  344. return
  345. end
  346. return util.open_floating_preview(contents, format, config)
  347. end
  348. --- @deprecated remove in 0.13
  349. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
  350. --- @diagnostic disable-next-line: deprecated
  351. RCS[ms.textDocument_hover] = M.hover
  352. local sig_help_ns = api.nvim_create_namespace('nvim.lsp.signature_help')
  353. --- @deprecated remove in 0.13
  354. --- |lsp-handler| for the method "textDocument/signatureHelp".
  355. ---
  356. --- The active parameter is highlighted with |hl-LspSignatureActiveParameter|.
  357. ---
  358. --- ```lua
  359. --- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
  360. --- vim.lsp.handlers.signature_help, {
  361. --- -- Use a sharp border with `FloatBorder` highlights
  362. --- border = "single"
  363. --- }
  364. --- )
  365. --- ```
  366. ---
  367. ---@param _ lsp.ResponseError?
  368. ---@param result lsp.SignatureHelp? Response from the language server
  369. ---@param ctx lsp.HandlerContext Client context
  370. ---@param config table Configuration table.
  371. --- - border: (default=nil)
  372. --- - Add borders to the floating window
  373. --- - See |vim.lsp.util.open_floating_preview()| for more options
  374. --- @diagnostic disable-next-line:redundant-parameter
  375. function M.signature_help(_, result, ctx, config)
  376. config = config or {}
  377. config.focus_id = ctx.method
  378. if api.nvim_get_current_buf() ~= ctx.bufnr then
  379. -- Ignore result since buffer changed. This happens for slow language servers.
  380. return
  381. end
  382. -- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler
  383. -- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore
  384. if not (result and result.signatures and result.signatures[1]) then
  385. if config.silent ~= true then
  386. print('No signature help available')
  387. end
  388. return
  389. end
  390. local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
  391. local triggers =
  392. vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
  393. local ft = vim.bo[ctx.bufnr].filetype
  394. local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
  395. if not lines or vim.tbl_isempty(lines) then
  396. if config.silent ~= true then
  397. print('No signature help available')
  398. end
  399. return
  400. end
  401. local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
  402. -- Highlight the active parameter.
  403. if hl then
  404. vim.hl.range(
  405. fbuf,
  406. sig_help_ns,
  407. 'LspSignatureActiveParameter',
  408. { hl[1], hl[2] },
  409. { hl[3], hl[4] }
  410. )
  411. end
  412. return fbuf, fwin
  413. end
  414. --- @deprecated remove in 0.13
  415. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
  416. --- @diagnostic disable-next-line:deprecated
  417. RCS[ms.textDocument_signatureHelp] = M.signature_help
  418. --- @deprecated remove in 0.13
  419. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
  420. RCS[ms.textDocument_documentHighlight] = function(_, result, ctx)
  421. if not result then
  422. return
  423. end
  424. local client_id = ctx.client_id
  425. local client = vim.lsp.get_client_by_id(client_id)
  426. if not client then
  427. return
  428. end
  429. util.buf_highlight_references(ctx.bufnr, result, client.offset_encoding)
  430. end
  431. --- @private
  432. ---
  433. --- Displays call hierarchy in the quickfix window.
  434. ---
  435. --- @param direction 'from'|'to' `"from"` for incoming calls and `"to"` for outgoing calls
  436. --- @overload fun(direction:'from'): fun(_, result: lsp.CallHierarchyIncomingCall[]?)
  437. --- @overload fun(direction:'to'): fun(_, result: lsp.CallHierarchyOutgoingCall[]?)
  438. local function make_call_hierarchy_handler(direction)
  439. --- @param result lsp.CallHierarchyIncomingCall[]|lsp.CallHierarchyOutgoingCall[]
  440. return function(_, result)
  441. if not result then
  442. return
  443. end
  444. local items = {}
  445. for _, call_hierarchy_call in pairs(result) do
  446. --- @type lsp.CallHierarchyItem
  447. local call_hierarchy_item = call_hierarchy_call[direction]
  448. for _, range in pairs(call_hierarchy_call.fromRanges) do
  449. table.insert(items, {
  450. filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)),
  451. text = call_hierarchy_item.name,
  452. lnum = range.start.line + 1,
  453. col = range.start.character + 1,
  454. })
  455. end
  456. end
  457. vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items })
  458. vim.cmd('botright copen')
  459. end
  460. end
  461. --- @deprecated remove in 0.13
  462. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_incomingCalls
  463. RCS[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from')
  464. --- @deprecated remove in 0.13
  465. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls
  466. RCS[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to')
  467. --- Displays type hierarchy in the quickfix window.
  468. local function make_type_hierarchy_handler()
  469. --- @param result lsp.TypeHierarchyItem[]
  470. return function(_, result, ctx, _)
  471. if not result then
  472. return
  473. end
  474. local function format_item(item)
  475. if not item.detail or #item.detail == 0 then
  476. return item.name
  477. end
  478. return string.format('%s %s', item.name, item.detail)
  479. end
  480. local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
  481. local items = {}
  482. for _, type_hierarchy_item in pairs(result) do
  483. local col = util._get_line_byte_from_position(
  484. ctx.bufnr,
  485. type_hierarchy_item.range.start,
  486. client.offset_encoding
  487. )
  488. table.insert(items, {
  489. filename = assert(vim.uri_to_fname(type_hierarchy_item.uri)),
  490. text = format_item(type_hierarchy_item),
  491. lnum = type_hierarchy_item.range.start.line + 1,
  492. col = col + 1,
  493. })
  494. end
  495. vim.fn.setqflist({}, ' ', { title = 'LSP type hierarchy', items = items })
  496. vim.cmd('botright copen')
  497. end
  498. end
  499. --- @deprecated remove in 0.13
  500. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_incomingCalls
  501. RCS[ms.typeHierarchy_subtypes] = make_type_hierarchy_handler()
  502. --- @deprecated remove in 0.13
  503. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_outgoingCalls
  504. RCS[ms.typeHierarchy_supertypes] = make_type_hierarchy_handler()
  505. --- @see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
  506. --- @param params lsp.LogMessageParams
  507. NSC['window/logMessage'] = function(_, params, ctx)
  508. local message_type = params.type
  509. local message = params.message
  510. local client_id = ctx.client_id
  511. local client = vim.lsp.get_client_by_id(client_id)
  512. local client_name = client and client.name or string.format('id=%d', client_id)
  513. if not client then
  514. err_message('LSP[', client_name, '] client has shut down after sending ', message)
  515. end
  516. if message_type == protocol.MessageType.Error then
  517. log.error(message)
  518. elseif message_type == protocol.MessageType.Warning then
  519. log.warn(message)
  520. elseif message_type == protocol.MessageType.Info or message_type == protocol.MessageType.Log then
  521. log.info(message)
  522. else
  523. log.debug(message)
  524. end
  525. return params
  526. end
  527. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage
  528. --- @param params lsp.ShowMessageParams
  529. NSC['window/showMessage'] = function(_, params, ctx)
  530. local message_type = params.type
  531. local message = params.message
  532. local client_id = ctx.client_id
  533. local client = vim.lsp.get_client_by_id(client_id)
  534. local client_name = client and client.name or string.format('id=%d', client_id)
  535. if not client then
  536. err_message('LSP[', client_name, '] client has shut down after sending ', message)
  537. end
  538. if message_type == protocol.MessageType.Error then
  539. err_message('LSP[', client_name, '] ', message)
  540. else
  541. message = ('LSP[%s][%s] %s\n'):format(client_name, protocol.MessageType[message_type], message)
  542. api.nvim_echo({ { message } }, true, {})
  543. end
  544. return params
  545. end
  546. --- @private
  547. --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument
  548. --- @param params lsp.ShowDocumentParams
  549. RSC[ms.window_showDocument] = function(_, params, ctx)
  550. local uri = params.uri
  551. if params.external then
  552. -- TODO(lvimuser): ask the user for confirmation
  553. local cmd, err = vim.ui.open(uri)
  554. local ret = cmd and cmd:wait(2000) or nil
  555. if ret == nil or ret.code ~= 0 then
  556. return {
  557. success = false,
  558. error = {
  559. code = protocol.ErrorCodes.UnknownErrorCode,
  560. message = ret and ret.stderr or err,
  561. },
  562. }
  563. end
  564. return { success = true }
  565. end
  566. local client_id = ctx.client_id
  567. local client = vim.lsp.get_client_by_id(client_id)
  568. local client_name = client and client.name or string.format('id=%d', client_id)
  569. if not client then
  570. err_message('LSP[', client_name, '] client has shut down after sending ', ctx.method)
  571. return vim.NIL
  572. end
  573. local location = {
  574. uri = uri,
  575. range = params.selection,
  576. }
  577. local success = util.show_document(location, client.offset_encoding, {
  578. reuse_win = true,
  579. focus = params.takeFocus,
  580. })
  581. return { success = success or false }
  582. end
  583. ---@see https://microsoft.github.io/language-server-protocol/specification/#workspace_inlayHint_refresh
  584. RSC[ms.workspace_inlayHint_refresh] = function(err, result, ctx)
  585. return vim.lsp.inlay_hint.on_refresh(err, result, ctx)
  586. end
  587. ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#semanticTokens_refreshRequest
  588. RSC[ms.workspace_semanticTokens_refresh] = function(err, result, ctx)
  589. return vim.lsp.semantic_tokens._refresh(err, result, ctx)
  590. end
  591. --- @nodoc
  592. --- @type table<string, lsp.Handler>
  593. M = vim.tbl_extend('force', M, RSC, NSC, RCS)
  594. -- Add boilerplate error validation and logging for all of these.
  595. for k, fn in pairs(M) do
  596. --- @diagnostic disable-next-line:redundant-parameter
  597. M[k] = function(err, result, ctx, config)
  598. if log.trace() then
  599. log.trace('default_handler', ctx.method, {
  600. err = err,
  601. result = result,
  602. ctx = vim.inspect(ctx),
  603. })
  604. end
  605. -- ServerCancelled errors should be propagated to the request handler
  606. if err and err.code ~= protocol.ErrorCodes.ServerCancelled then
  607. -- LSP spec:
  608. -- interface ResponseError:
  609. -- code: integer;
  610. -- message: string;
  611. -- data?: string | number | boolean | array | object | null;
  612. -- Per LSP, don't show ContentModified error to the user.
  613. if err.code ~= protocol.ErrorCodes.ContentModified then
  614. local client = vim.lsp.get_client_by_id(ctx.client_id)
  615. local client_name = client and client.name or string.format('client_id=%d', ctx.client_id)
  616. err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message)
  617. end
  618. return
  619. end
  620. --- @diagnostic disable-next-line:redundant-parameter
  621. return fn(err, result, ctx, config)
  622. end
  623. end
  624. return M