buf.lua 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292
  1. local api = vim.api
  2. local lsp = vim.lsp
  3. local validate = vim.validate
  4. local util = require('vim.lsp.util')
  5. local npcall = vim.F.npcall
  6. local ms = require('vim.lsp.protocol').Methods
  7. local M = {}
  8. --- @param params? table
  9. --- @return fun(client: vim.lsp.Client): lsp.TextDocumentPositionParams
  10. local function client_positional_params(params)
  11. local win = api.nvim_get_current_win()
  12. return function(client)
  13. local ret = util.make_position_params(win, client.offset_encoding)
  14. if params then
  15. ret = vim.tbl_extend('force', ret, params)
  16. end
  17. return ret
  18. end
  19. end
  20. local hover_ns = api.nvim_create_namespace('nvim.lsp.hover_range')
  21. --- @class vim.lsp.buf.hover.Opts : vim.lsp.util.open_floating_preview.Opts
  22. --- @field silent? boolean
  23. --- Displays hover information about the symbol under the cursor in a floating
  24. --- window. The window will be dismissed on cursor move.
  25. --- Calling the function twice will jump into the floating window
  26. --- (thus by default, "KK" will open the hover window and focus it).
  27. --- In the floating window, all commands and mappings are available as usual,
  28. --- except that "q" dismisses the window.
  29. --- You can scroll the contents the same as you would any other buffer.
  30. ---
  31. --- Note: to disable hover highlights, add the following to your config:
  32. ---
  33. --- ```lua
  34. --- vim.api.nvim_create_autocmd('ColorScheme', {
  35. --- callback = function()
  36. --- vim.api.nvim_set_hl(0, 'LspReferenceTarget', {})
  37. --- end,
  38. --- })
  39. --- ```
  40. --- @param config? vim.lsp.buf.hover.Opts
  41. function M.hover(config)
  42. config = config or {}
  43. config.focus_id = ms.textDocument_hover
  44. lsp.buf_request_all(0, ms.textDocument_hover, client_positional_params(), function(results, ctx)
  45. local bufnr = assert(ctx.bufnr)
  46. if api.nvim_get_current_buf() ~= bufnr then
  47. -- Ignore result since buffer changed. This happens for slow language servers.
  48. return
  49. end
  50. -- Filter errors from results
  51. local results1 = {} --- @type table<integer,lsp.Hover>
  52. for client_id, resp in pairs(results) do
  53. local err, result = resp.err, resp.result
  54. if err then
  55. lsp.log.error(err.code, err.message)
  56. elseif result then
  57. results1[client_id] = result
  58. end
  59. end
  60. if vim.tbl_isempty(results1) then
  61. if config.silent ~= true then
  62. vim.notify('No information available')
  63. end
  64. return
  65. end
  66. local contents = {} --- @type string[]
  67. local nresults = #vim.tbl_keys(results1)
  68. local format = 'markdown'
  69. for client_id, result in pairs(results1) do
  70. local client = assert(lsp.get_client_by_id(client_id))
  71. if nresults > 1 then
  72. -- Show client name if there are multiple clients
  73. contents[#contents + 1] = string.format('# %s', client.name)
  74. end
  75. if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
  76. if #results1 == 1 then
  77. format = 'plaintext'
  78. contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
  79. else
  80. -- Surround plaintext with ``` to get correct formatting
  81. contents[#contents + 1] = '```'
  82. vim.list_extend(
  83. contents,
  84. vim.split(result.contents.value or '', '\n', { trimempty = true })
  85. )
  86. contents[#contents + 1] = '```'
  87. end
  88. else
  89. vim.list_extend(contents, util.convert_input_to_markdown_lines(result.contents))
  90. end
  91. local range = result.range
  92. if range then
  93. local start = range.start
  94. local end_ = range['end']
  95. local start_idx = util._get_line_byte_from_position(bufnr, start, client.offset_encoding)
  96. local end_idx = util._get_line_byte_from_position(bufnr, end_, client.offset_encoding)
  97. vim.hl.range(
  98. bufnr,
  99. hover_ns,
  100. 'LspReferenceTarget',
  101. { start.line, start_idx },
  102. { end_.line, end_idx },
  103. { priority = vim.hl.priorities.user }
  104. )
  105. end
  106. contents[#contents + 1] = '---'
  107. end
  108. -- Remove last linebreak ('---')
  109. contents[#contents] = nil
  110. if vim.tbl_isempty(contents) then
  111. if config.silent ~= true then
  112. vim.notify('No information available')
  113. end
  114. return
  115. end
  116. local _, winid = lsp.util.open_floating_preview(contents, format, config)
  117. api.nvim_create_autocmd('WinClosed', {
  118. pattern = tostring(winid),
  119. once = true,
  120. callback = function()
  121. api.nvim_buf_clear_namespace(bufnr, hover_ns, 0, -1)
  122. return true
  123. end,
  124. })
  125. end)
  126. end
  127. local function request_with_opts(name, params, opts)
  128. local req_handler --- @type function?
  129. if opts then
  130. req_handler = function(err, result, ctx, config)
  131. local client = assert(lsp.get_client_by_id(ctx.client_id))
  132. local handler = client.handlers[name] or lsp.handlers[name]
  133. handler(err, result, ctx, vim.tbl_extend('force', config or {}, opts))
  134. end
  135. end
  136. lsp.buf_request(0, name, params, req_handler)
  137. end
  138. ---@param method string
  139. ---@param opts? vim.lsp.LocationOpts
  140. local function get_locations(method, opts)
  141. opts = opts or {}
  142. local bufnr = api.nvim_get_current_buf()
  143. local clients = lsp.get_clients({ method = method, bufnr = bufnr })
  144. if not next(clients) then
  145. vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
  146. return
  147. end
  148. local win = api.nvim_get_current_win()
  149. local from = vim.fn.getpos('.')
  150. from[1] = bufnr
  151. local tagname = vim.fn.expand('<cword>')
  152. local remaining = #clients
  153. ---@type vim.quickfix.entry[]
  154. local all_items = {}
  155. ---@param result nil|lsp.Location|lsp.Location[]
  156. ---@param client vim.lsp.Client
  157. local function on_response(_, result, client)
  158. local locations = {}
  159. if result then
  160. locations = vim.islist(result) and result or { result }
  161. end
  162. local items = util.locations_to_items(locations, client.offset_encoding)
  163. vim.list_extend(all_items, items)
  164. remaining = remaining - 1
  165. if remaining == 0 then
  166. if vim.tbl_isempty(all_items) then
  167. vim.notify('No locations found', vim.log.levels.INFO)
  168. return
  169. end
  170. local title = 'LSP locations'
  171. if opts.on_list then
  172. assert(vim.is_callable(opts.on_list), 'on_list is not a function')
  173. opts.on_list({
  174. title = title,
  175. items = all_items,
  176. context = { bufnr = bufnr, method = method },
  177. })
  178. return
  179. end
  180. if #all_items == 1 then
  181. local item = all_items[1]
  182. local b = item.bufnr or vim.fn.bufadd(item.filename)
  183. -- Save position in jumplist
  184. vim.cmd("normal! m'")
  185. -- Push a new item into tagstack
  186. local tagstack = { { tagname = tagname, from = from } }
  187. vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't')
  188. vim.bo[b].buflisted = true
  189. local w = opts.reuse_win and vim.fn.win_findbuf(b)[1] or win
  190. api.nvim_win_set_buf(w, b)
  191. api.nvim_win_set_cursor(w, { item.lnum, item.col - 1 })
  192. vim._with({ win = w }, function()
  193. -- Open folds under the cursor
  194. vim.cmd('normal! zv')
  195. end)
  196. return
  197. end
  198. if opts.loclist then
  199. vim.fn.setloclist(0, {}, ' ', { title = title, items = all_items })
  200. vim.cmd.lopen()
  201. else
  202. vim.fn.setqflist({}, ' ', { title = title, items = all_items })
  203. vim.cmd('botright copen')
  204. end
  205. end
  206. end
  207. for _, client in ipairs(clients) do
  208. local params = util.make_position_params(win, client.offset_encoding)
  209. client:request(method, params, function(_, result)
  210. on_response(_, result, client)
  211. end)
  212. end
  213. end
  214. --- @class vim.lsp.ListOpts
  215. ---
  216. --- list-handler replacing the default handler.
  217. --- Called for any non-empty result.
  218. --- This table can be used with |setqflist()| or |setloclist()|. E.g.:
  219. --- ```lua
  220. --- local function on_list(options)
  221. --- vim.fn.setqflist({}, ' ', options)
  222. --- vim.cmd.cfirst()
  223. --- end
  224. ---
  225. --- vim.lsp.buf.definition({ on_list = on_list })
  226. --- vim.lsp.buf.references(nil, { on_list = on_list })
  227. --- ```
  228. --- @field on_list? fun(t: vim.lsp.LocationOpts.OnList)
  229. ---
  230. --- Whether to use the |location-list| or the |quickfix| list in the default handler.
  231. --- ```lua
  232. --- vim.lsp.buf.definition({ loclist = true })
  233. --- vim.lsp.buf.references(nil, { loclist = false })
  234. --- ```
  235. --- @field loclist? boolean
  236. --- @class vim.lsp.LocationOpts.OnList
  237. --- @field items table[] Structured like |setqflist-what|
  238. --- @field title? string Title for the list.
  239. --- @field context? { bufnr: integer, method: string } Subset of `ctx` from |lsp-handler|.
  240. --- @class vim.lsp.LocationOpts: vim.lsp.ListOpts
  241. ---
  242. --- Jump to existing window if buffer is already open.
  243. --- @field reuse_win? boolean
  244. --- Jumps to the declaration of the symbol under the cursor.
  245. --- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
  246. --- @param opts? vim.lsp.LocationOpts
  247. function M.declaration(opts)
  248. get_locations(ms.textDocument_declaration, opts)
  249. end
  250. --- Jumps to the definition of the symbol under the cursor.
  251. --- @param opts? vim.lsp.LocationOpts
  252. function M.definition(opts)
  253. get_locations(ms.textDocument_definition, opts)
  254. end
  255. --- Jumps to the definition of the type of the symbol under the cursor.
  256. --- @param opts? vim.lsp.LocationOpts
  257. function M.type_definition(opts)
  258. get_locations(ms.textDocument_typeDefinition, opts)
  259. end
  260. --- Lists all the implementations for the symbol under the cursor in the
  261. --- quickfix window.
  262. --- @param opts? vim.lsp.LocationOpts
  263. function M.implementation(opts)
  264. get_locations(ms.textDocument_implementation, opts)
  265. end
  266. --- @param results table<integer,{err: lsp.ResponseError?, result: lsp.SignatureHelp?}>
  267. local function process_signature_help_results(results)
  268. local signatures = {} --- @type [vim.lsp.Client,lsp.SignatureInformation][]
  269. -- Pre-process results
  270. for client_id, r in pairs(results) do
  271. local err = r.err
  272. local client = assert(lsp.get_client_by_id(client_id))
  273. if err then
  274. vim.notify(
  275. client.name .. ': ' .. tostring(err.code) .. ': ' .. err.message,
  276. vim.log.levels.ERROR
  277. )
  278. api.nvim_command('redraw')
  279. else
  280. local result = r.result --- @type lsp.SignatureHelp
  281. if result and result.signatures and result.signatures[1] then
  282. for _, sig in ipairs(result.signatures) do
  283. sig.activeParameter = sig.activeParameter or result.activeParameter
  284. signatures[#signatures + 1] = { client, sig }
  285. end
  286. end
  287. end
  288. end
  289. return signatures
  290. end
  291. local sig_help_ns = api.nvim_create_namespace('nvim.lsp.signature_help')
  292. --- @class vim.lsp.buf.signature_help.Opts : vim.lsp.util.open_floating_preview.Opts
  293. --- @field silent? boolean
  294. --- Displays signature information about the symbol under the cursor in a
  295. --- floating window.
  296. --- @param config? vim.lsp.buf.signature_help.Opts
  297. function M.signature_help(config)
  298. local method = ms.textDocument_signatureHelp
  299. config = config and vim.deepcopy(config) or {}
  300. config.focus_id = method
  301. lsp.buf_request_all(0, method, client_positional_params(), function(results, ctx)
  302. if api.nvim_get_current_buf() ~= ctx.bufnr then
  303. -- Ignore result since buffer changed. This happens for slow language servers.
  304. return
  305. end
  306. local signatures = process_signature_help_results(results)
  307. if not next(signatures) then
  308. if config.silent ~= true then
  309. print('No signature help available')
  310. end
  311. return
  312. end
  313. local ft = vim.bo[ctx.bufnr].filetype
  314. local total = #signatures
  315. local can_cycle = total > 1 and config.focusable
  316. local idx = 0
  317. --- @param update_win? integer
  318. local function show_signature(update_win)
  319. idx = (idx % total) + 1
  320. local client, result = signatures[idx][1], signatures[idx][2]
  321. --- @type string[]?
  322. local triggers =
  323. vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
  324. local lines, hl =
  325. util.convert_signature_help_to_markdown_lines({ signatures = { result } }, ft, triggers)
  326. if not lines then
  327. return
  328. end
  329. local sfx = can_cycle and string.format(' (%d/%d) (<C-s> to cycle)', idx, total) or ''
  330. local title = string.format('Signature Help: %s%s', client.name, sfx)
  331. if config.border then
  332. config.title = title
  333. else
  334. table.insert(lines, 1, '# ' .. title)
  335. if hl then
  336. hl[1] = hl[1] + 1
  337. hl[3] = hl[3] + 1
  338. end
  339. end
  340. config._update_win = update_win
  341. local buf, win = util.open_floating_preview(lines, 'markdown', config)
  342. if hl then
  343. vim.api.nvim_buf_clear_namespace(buf, sig_help_ns, 0, -1)
  344. vim.hl.range(
  345. buf,
  346. sig_help_ns,
  347. 'LspSignatureActiveParameter',
  348. { hl[1], hl[2] },
  349. { hl[3], hl[4] }
  350. )
  351. end
  352. return buf, win
  353. end
  354. local fbuf, fwin = show_signature()
  355. if can_cycle then
  356. vim.keymap.set('n', '<C-s>', function()
  357. show_signature(fwin)
  358. end, {
  359. buffer = fbuf,
  360. desc = 'Cycle next signature',
  361. })
  362. end
  363. end)
  364. end
  365. --- @deprecated
  366. --- Retrieves the completion items at the current cursor position. Can only be
  367. --- called in Insert mode.
  368. ---
  369. ---@param context table (context support not yet implemented) Additional information
  370. --- about the context in which a completion was triggered (how it was triggered,
  371. --- and by which trigger character, if applicable)
  372. ---
  373. ---@see vim.lsp.protocol.CompletionTriggerKind
  374. function M.completion(context)
  375. vim.depends('vim.lsp.buf.completion', 'vim.lsp.completion.trigger', '0.12')
  376. return lsp.buf_request(
  377. 0,
  378. ms.textDocument_completion,
  379. client_positional_params({
  380. context = context,
  381. })
  382. )
  383. end
  384. ---@param bufnr integer
  385. ---@param mode "v"|"V"
  386. ---@return table {start={row,col}, end={row,col}} using (1, 0) indexing
  387. local function range_from_selection(bufnr, mode)
  388. -- TODO: Use `vim.fn.getregionpos()` instead.
  389. -- [bufnum, lnum, col, off]; both row and column 1-indexed
  390. local start = vim.fn.getpos('v')
  391. local end_ = vim.fn.getpos('.')
  392. local start_row = start[2]
  393. local start_col = start[3]
  394. local end_row = end_[2]
  395. local end_col = end_[3]
  396. -- A user can start visual selection at the end and move backwards
  397. -- Normalize the range to start < end
  398. if start_row == end_row and end_col < start_col then
  399. end_col, start_col = start_col, end_col --- @type integer, integer
  400. elseif end_row < start_row then
  401. start_row, end_row = end_row, start_row --- @type integer, integer
  402. start_col, end_col = end_col, start_col --- @type integer, integer
  403. end
  404. if mode == 'V' then
  405. start_col = 1
  406. local lines = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)
  407. end_col = #lines[1]
  408. end
  409. return {
  410. ['start'] = { start_row, start_col - 1 },
  411. ['end'] = { end_row, end_col - 1 },
  412. }
  413. end
  414. --- @class vim.lsp.buf.format.Opts
  415. --- @inlinedoc
  416. ---
  417. --- Can be used to specify FormattingOptions. Some unspecified options will be
  418. --- automatically derived from the current Nvim options.
  419. --- See https://microsoft.github.io/language-server-protocol/specification/#formattingOptions
  420. --- @field formatting_options? table
  421. ---
  422. --- Time in milliseconds to block for formatting requests. No effect if async=true.
  423. --- (default: `1000`)
  424. --- @field timeout_ms? integer
  425. ---
  426. --- Restrict formatting to the clients attached to the given buffer.
  427. --- (default: current buffer)
  428. --- @field bufnr? integer
  429. ---
  430. --- Predicate used to filter clients. Receives a client as argument and must
  431. --- return a boolean. Clients matching the predicate are included. Example:
  432. --- ```lua
  433. --- -- Never request typescript-language-server for formatting
  434. --- vim.lsp.buf.format {
  435. --- filter = function(client) return client.name ~= "ts_ls" end
  436. --- }
  437. --- ```
  438. --- @field filter? fun(client: vim.lsp.Client): boolean?
  439. ---
  440. --- If true the method won't block.
  441. --- Editing the buffer while formatting asynchronous can lead to unexpected
  442. --- changes.
  443. --- (Default: false)
  444. --- @field async? boolean
  445. ---
  446. --- Restrict formatting to the client with ID (client.id) matching this field.
  447. --- @field id? integer
  448. ---
  449. --- Restrict formatting to the client with name (client.name) matching this field.
  450. --- @field name? string
  451. ---
  452. --- Range to format.
  453. --- Table must contain `start` and `end` keys with {row,col} tuples using
  454. --- (1,0) indexing.
  455. --- Can also be a list of tables that contain `start` and `end` keys as described above,
  456. --- in which case `textDocument/rangesFormatting` support is required.
  457. --- (Default: current selection in visual mode, `nil` in other modes,
  458. --- formatting the full buffer)
  459. --- @field range? {start:[integer,integer],end:[integer, integer]}|{start:[integer,integer],end:[integer,integer]}[]
  460. --- Formats a buffer using the attached (and optionally filtered) language
  461. --- server clients.
  462. ---
  463. --- @param opts? vim.lsp.buf.format.Opts
  464. function M.format(opts)
  465. opts = opts or {}
  466. local bufnr = vim._resolve_bufnr(opts.bufnr)
  467. local mode = api.nvim_get_mode().mode
  468. local range = opts.range
  469. -- Try to use visual selection if no range is given
  470. if not range and mode == 'v' or mode == 'V' then
  471. range = range_from_selection(bufnr, mode)
  472. end
  473. local passed_multiple_ranges = (range and #range ~= 0 and type(range[1]) == 'table')
  474. local method ---@type string
  475. if passed_multiple_ranges then
  476. method = ms.textDocument_rangesFormatting
  477. elseif range then
  478. method = ms.textDocument_rangeFormatting
  479. else
  480. method = ms.textDocument_formatting
  481. end
  482. local clients = lsp.get_clients({
  483. id = opts.id,
  484. bufnr = bufnr,
  485. name = opts.name,
  486. method = method,
  487. })
  488. if opts.filter then
  489. clients = vim.tbl_filter(opts.filter, clients)
  490. end
  491. if #clients == 0 then
  492. vim.notify('[LSP] Format request failed, no matching language servers.')
  493. end
  494. --- @param client vim.lsp.Client
  495. --- @param params lsp.DocumentFormattingParams
  496. --- @return lsp.DocumentFormattingParams|lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams
  497. local function set_range(client, params)
  498. --- @param r {start:[integer,integer],end:[integer, integer]}
  499. local function to_lsp_range(r)
  500. return util.make_given_range_params(r.start, r['end'], bufnr, client.offset_encoding).range
  501. end
  502. local ret = params --[[@as lsp.DocumentFormattingParams|lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams]]
  503. if passed_multiple_ranges then
  504. ret = params --[[@as lsp.DocumentRangesFormattingParams]]
  505. --- @cast range {start:[integer,integer],end:[integer, integer]}
  506. ret.ranges = vim.tbl_map(to_lsp_range, range)
  507. elseif range then
  508. ret = params --[[@as lsp.DocumentRangeFormattingParams]]
  509. ret.range = to_lsp_range(range)
  510. end
  511. return ret
  512. end
  513. if opts.async then
  514. --- @param idx? integer
  515. --- @param client? vim.lsp.Client
  516. local function do_format(idx, client)
  517. if not idx or not client then
  518. return
  519. end
  520. local params = set_range(client, util.make_formatting_params(opts.formatting_options))
  521. client:request(method, params, function(...)
  522. local handler = client.handlers[method] or lsp.handlers[method]
  523. handler(...)
  524. do_format(next(clients, idx))
  525. end, bufnr)
  526. end
  527. do_format(next(clients))
  528. else
  529. local timeout_ms = opts.timeout_ms or 1000
  530. for _, client in pairs(clients) do
  531. local params = set_range(client, util.make_formatting_params(opts.formatting_options))
  532. local result, err = client:request_sync(method, params, timeout_ms, bufnr)
  533. if result and result.result then
  534. util.apply_text_edits(result.result, bufnr, client.offset_encoding)
  535. elseif err then
  536. vim.notify(string.format('[LSP][%s] %s', client.name, err), vim.log.levels.WARN)
  537. end
  538. end
  539. end
  540. end
  541. --- @class vim.lsp.buf.rename.Opts
  542. --- @inlinedoc
  543. ---
  544. --- Predicate used to filter clients. Receives a client as argument and
  545. --- must return a boolean. Clients matching the predicate are included.
  546. --- @field filter? fun(client: vim.lsp.Client): boolean?
  547. ---
  548. --- Restrict clients used for rename to ones where client.name matches
  549. --- this field.
  550. --- @field name? string
  551. ---
  552. --- (default: current buffer)
  553. --- @field bufnr? integer
  554. --- Renames all references to the symbol under the cursor.
  555. ---
  556. ---@param new_name string|nil If not provided, the user will be prompted for a new
  557. --- name using |vim.ui.input()|.
  558. ---@param opts? vim.lsp.buf.rename.Opts Additional options:
  559. function M.rename(new_name, opts)
  560. opts = opts or {}
  561. local bufnr = vim._resolve_bufnr(opts.bufnr)
  562. local clients = lsp.get_clients({
  563. bufnr = bufnr,
  564. name = opts.name,
  565. -- Clients must at least support rename, prepareRename is optional
  566. method = ms.textDocument_rename,
  567. })
  568. if opts.filter then
  569. clients = vim.tbl_filter(opts.filter, clients)
  570. end
  571. if #clients == 0 then
  572. vim.notify('[LSP] Rename, no matching language servers with rename capability.')
  573. end
  574. local win = api.nvim_get_current_win()
  575. -- Compute early to account for cursor movements after going async
  576. local cword = vim.fn.expand('<cword>')
  577. --- @param range lsp.Range
  578. --- @param position_encoding string
  579. local function get_text_at_range(range, position_encoding)
  580. return api.nvim_buf_get_text(
  581. bufnr,
  582. range.start.line,
  583. util._get_line_byte_from_position(bufnr, range.start, position_encoding),
  584. range['end'].line,
  585. util._get_line_byte_from_position(bufnr, range['end'], position_encoding),
  586. {}
  587. )[1]
  588. end
  589. --- @param idx? integer
  590. --- @param client? vim.lsp.Client
  591. local function try_use_client(idx, client)
  592. if not idx or not client then
  593. return
  594. end
  595. --- @param name string
  596. local function rename(name)
  597. local params = util.make_position_params(win, client.offset_encoding) --[[@as lsp.RenameParams]]
  598. params.newName = name
  599. local handler = client.handlers[ms.textDocument_rename]
  600. or lsp.handlers[ms.textDocument_rename]
  601. client:request(ms.textDocument_rename, params, function(...)
  602. handler(...)
  603. try_use_client(next(clients, idx))
  604. end, bufnr)
  605. end
  606. if client:supports_method(ms.textDocument_prepareRename) then
  607. local params = util.make_position_params(win, client.offset_encoding)
  608. client:request(ms.textDocument_prepareRename, params, function(err, result)
  609. if err or result == nil then
  610. if next(clients, idx) then
  611. try_use_client(next(clients, idx))
  612. else
  613. local msg = err and ('Error on prepareRename: ' .. (err.message or ''))
  614. or 'Nothing to rename'
  615. vim.notify(msg, vim.log.levels.INFO)
  616. end
  617. return
  618. end
  619. if new_name then
  620. rename(new_name)
  621. return
  622. end
  623. local prompt_opts = {
  624. prompt = 'New Name: ',
  625. }
  626. -- result: Range | { range: Range, placeholder: string }
  627. if result.placeholder then
  628. prompt_opts.default = result.placeholder
  629. elseif result.start then
  630. prompt_opts.default = get_text_at_range(result, client.offset_encoding)
  631. elseif result.range then
  632. prompt_opts.default = get_text_at_range(result.range, client.offset_encoding)
  633. else
  634. prompt_opts.default = cword
  635. end
  636. vim.ui.input(prompt_opts, function(input)
  637. if not input or #input == 0 then
  638. return
  639. end
  640. rename(input)
  641. end)
  642. end, bufnr)
  643. else
  644. assert(
  645. client:supports_method(ms.textDocument_rename),
  646. 'Client must support textDocument/rename'
  647. )
  648. if new_name then
  649. rename(new_name)
  650. return
  651. end
  652. local prompt_opts = {
  653. prompt = 'New Name: ',
  654. default = cword,
  655. }
  656. vim.ui.input(prompt_opts, function(input)
  657. if not input or #input == 0 then
  658. return
  659. end
  660. rename(input)
  661. end)
  662. end
  663. end
  664. try_use_client(next(clients))
  665. end
  666. --- Lists all the references to the symbol under the cursor in the quickfix window.
  667. ---
  668. ---@param context lsp.ReferenceContext? Context for the request
  669. ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
  670. ---@param opts? vim.lsp.ListOpts
  671. function M.references(context, opts)
  672. validate('context', context, 'table', true)
  673. local bufnr = api.nvim_get_current_buf()
  674. local clients = lsp.get_clients({ method = ms.textDocument_references, bufnr = bufnr })
  675. if not next(clients) then
  676. return
  677. end
  678. local win = api.nvim_get_current_win()
  679. opts = opts or {}
  680. local all_items = {}
  681. local title = 'References'
  682. local function on_done()
  683. if not next(all_items) then
  684. vim.notify('No references found')
  685. else
  686. local list = {
  687. title = title,
  688. items = all_items,
  689. context = {
  690. method = ms.textDocument_references,
  691. bufnr = bufnr,
  692. },
  693. }
  694. if opts.loclist then
  695. vim.fn.setloclist(0, {}, ' ', list)
  696. vim.cmd.lopen()
  697. elseif opts.on_list then
  698. assert(vim.is_callable(opts.on_list), 'on_list is not a function')
  699. opts.on_list(list)
  700. else
  701. vim.fn.setqflist({}, ' ', list)
  702. vim.cmd('botright copen')
  703. end
  704. end
  705. end
  706. local remaining = #clients
  707. for _, client in ipairs(clients) do
  708. local params = util.make_position_params(win, client.offset_encoding)
  709. ---@diagnostic disable-next-line: inject-field
  710. params.context = context or {
  711. includeDeclaration = true,
  712. }
  713. client:request(ms.textDocument_references, params, function(_, result)
  714. local items = util.locations_to_items(result or {}, client.offset_encoding)
  715. vim.list_extend(all_items, items)
  716. remaining = remaining - 1
  717. if remaining == 0 then
  718. on_done()
  719. end
  720. end)
  721. end
  722. end
  723. --- Lists all symbols in the current buffer in the |location-list|.
  724. --- @param opts? vim.lsp.ListOpts
  725. function M.document_symbol(opts)
  726. opts = vim.tbl_deep_extend('keep', opts or {}, { loclist = true })
  727. local params = { textDocument = util.make_text_document_params() }
  728. request_with_opts(ms.textDocument_documentSymbol, params, opts)
  729. end
  730. --- @param client_id integer
  731. --- @param method string
  732. --- @param params table
  733. --- @param handler? lsp.Handler
  734. --- @param bufnr? integer
  735. local function request_with_id(client_id, method, params, handler, bufnr)
  736. local client = lsp.get_client_by_id(client_id)
  737. if not client then
  738. vim.notify(
  739. string.format('Client with id=%d disappeared during hierarchy request', client_id),
  740. vim.log.levels.WARN
  741. )
  742. return
  743. end
  744. client:request(method, params, handler, bufnr)
  745. end
  746. --- @param item lsp.TypeHierarchyItem|lsp.CallHierarchyItem
  747. local function format_hierarchy_item(item)
  748. if not item.detail or #item.detail == 0 then
  749. return item.name
  750. end
  751. return string.format('%s %s', item.name, item.detail)
  752. end
  753. local hierarchy_methods = {
  754. [ms.typeHierarchy_subtypes] = 'type',
  755. [ms.typeHierarchy_supertypes] = 'type',
  756. [ms.callHierarchy_incomingCalls] = 'call',
  757. [ms.callHierarchy_outgoingCalls] = 'call',
  758. }
  759. --- @param method string
  760. local function hierarchy(method)
  761. local kind = hierarchy_methods[method]
  762. if not kind then
  763. error('unsupported method ' .. method)
  764. end
  765. local prepare_method = kind == 'type' and ms.textDocument_prepareTypeHierarchy
  766. or ms.textDocument_prepareCallHierarchy
  767. local bufnr = api.nvim_get_current_buf()
  768. local clients = lsp.get_clients({ bufnr = bufnr, method = prepare_method })
  769. if not next(clients) then
  770. vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
  771. return
  772. end
  773. local win = api.nvim_get_current_win()
  774. --- @param results [integer, lsp.TypeHierarchyItem|lsp.CallHierarchyItem][]
  775. local function on_response(results)
  776. if #results == 0 then
  777. vim.notify('No item resolved', vim.log.levels.WARN)
  778. elseif #results == 1 then
  779. local client_id, item = results[1][1], results[1][2]
  780. request_with_id(client_id, method, { item = item }, nil, bufnr)
  781. else
  782. vim.ui.select(results, {
  783. prompt = string.format('Select a %s hierarchy item:', kind),
  784. kind = kind .. 'hierarchy',
  785. format_item = function(x)
  786. return format_hierarchy_item(x[2])
  787. end,
  788. }, function(x)
  789. if x then
  790. local client_id, item = x[1], x[2]
  791. request_with_id(client_id, method, { item = item }, nil, bufnr)
  792. end
  793. end)
  794. end
  795. end
  796. local results = {} --- @type [integer, lsp.TypeHierarchyItem|lsp.CallHierarchyItem][]
  797. local remaining = #clients
  798. for _, client in ipairs(clients) do
  799. local params = util.make_position_params(win, client.offset_encoding)
  800. --- @param result lsp.CallHierarchyItem[]|lsp.TypeHierarchyItem[]?
  801. client:request(prepare_method, params, function(err, result, ctx)
  802. if err then
  803. vim.notify(err.message, vim.log.levels.WARN)
  804. elseif result then
  805. for _, item in ipairs(result) do
  806. results[#results + 1] = { ctx.client_id, item }
  807. end
  808. end
  809. remaining = remaining - 1
  810. if remaining == 0 then
  811. on_response(results)
  812. end
  813. end, bufnr)
  814. end
  815. end
  816. --- Lists all the call sites of the symbol under the cursor in the
  817. --- |quickfix| window. If the symbol can resolve to multiple
  818. --- items, the user can pick one in the |inputlist()|.
  819. function M.incoming_calls()
  820. hierarchy(ms.callHierarchy_incomingCalls)
  821. end
  822. --- Lists all the items that are called by the symbol under the
  823. --- cursor in the |quickfix| window. If the symbol can resolve to
  824. --- multiple items, the user can pick one in the |inputlist()|.
  825. function M.outgoing_calls()
  826. hierarchy(ms.callHierarchy_outgoingCalls)
  827. end
  828. --- Lists all the subtypes or supertypes of the symbol under the
  829. --- cursor in the |quickfix| window. If the symbol can resolve to
  830. --- multiple items, the user can pick one using |vim.ui.select()|.
  831. ---@param kind "subtypes"|"supertypes"
  832. function M.typehierarchy(kind)
  833. local method = kind == 'subtypes' and ms.typeHierarchy_subtypes or ms.typeHierarchy_supertypes
  834. hierarchy(method)
  835. end
  836. --- List workspace folders.
  837. ---
  838. function M.list_workspace_folders()
  839. local workspace_folders = {}
  840. for _, client in pairs(lsp.get_clients({ bufnr = 0 })) do
  841. for _, folder in pairs(client.workspace_folders or {}) do
  842. table.insert(workspace_folders, folder.name)
  843. end
  844. end
  845. return workspace_folders
  846. end
  847. --- Add the folder at path to the workspace folders. If {path} is
  848. --- not provided, the user will be prompted for a path using |input()|.
  849. --- @param workspace_folder? string
  850. function M.add_workspace_folder(workspace_folder)
  851. workspace_folder = workspace_folder
  852. or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'), 'dir')
  853. api.nvim_command('redraw')
  854. if not (workspace_folder and #workspace_folder > 0) then
  855. return
  856. end
  857. if vim.fn.isdirectory(workspace_folder) == 0 then
  858. print(workspace_folder, ' is not a valid directory')
  859. return
  860. end
  861. local bufnr = api.nvim_get_current_buf()
  862. for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
  863. client:_add_workspace_folder(workspace_folder)
  864. end
  865. end
  866. --- Remove the folder at path from the workspace folders. If
  867. --- {path} is not provided, the user will be prompted for
  868. --- a path using |input()|.
  869. --- @param workspace_folder? string
  870. function M.remove_workspace_folder(workspace_folder)
  871. workspace_folder = workspace_folder
  872. or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'))
  873. api.nvim_command('redraw')
  874. if not workspace_folder or #workspace_folder == 0 then
  875. return
  876. end
  877. local bufnr = api.nvim_get_current_buf()
  878. for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
  879. client:_remove_workspace_folder(workspace_folder)
  880. end
  881. print(workspace_folder, 'is not currently part of the workspace')
  882. end
  883. --- Lists all symbols in the current workspace in the quickfix window.
  884. ---
  885. --- The list is filtered against {query}; if the argument is omitted from the
  886. --- call, the user is prompted to enter a string on the command line. An empty
  887. --- string means no filtering is done.
  888. ---
  889. --- @param query string? optional
  890. --- @param opts? vim.lsp.ListOpts
  891. function M.workspace_symbol(query, opts)
  892. query = query or npcall(vim.fn.input, 'Query: ')
  893. if query == nil then
  894. return
  895. end
  896. local params = { query = query }
  897. request_with_opts(ms.workspace_symbol, params, opts)
  898. end
  899. --- Send request to the server to resolve document highlights for the current
  900. --- text document position. This request can be triggered by a key mapping or
  901. --- by events such as `CursorHold`, e.g.:
  902. ---
  903. --- ```vim
  904. --- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
  905. --- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()
  906. --- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
  907. --- ```
  908. ---
  909. --- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups
  910. --- to be defined or you won't be able to see the actual highlights.
  911. --- |hl-LspReferenceText|
  912. --- |hl-LspReferenceRead|
  913. --- |hl-LspReferenceWrite|
  914. function M.document_highlight()
  915. lsp.buf_request(0, ms.textDocument_documentHighlight, client_positional_params())
  916. end
  917. --- Removes document highlights from current buffer.
  918. function M.clear_references()
  919. util.buf_clear_references()
  920. end
  921. ---@nodoc
  922. ---@class vim.lsp.CodeActionResultEntry
  923. ---@field error? lsp.ResponseError
  924. ---@field result? (lsp.Command|lsp.CodeAction)[]
  925. ---@field ctx lsp.HandlerContext
  926. --- @class vim.lsp.buf.code_action.Opts
  927. --- @inlinedoc
  928. ---
  929. --- Corresponds to `CodeActionContext` of the LSP specification:
  930. --- - {diagnostics}? (`table`) LSP `Diagnostic[]`. Inferred from the current
  931. --- position if not provided.
  932. --- - {only}? (`table`) List of LSP `CodeActionKind`s used to filter the code actions.
  933. --- Most language servers support values like `refactor`
  934. --- or `quickfix`.
  935. --- - {triggerKind}? (`integer`) The reason why code actions were requested.
  936. --- @field context? lsp.CodeActionContext
  937. ---
  938. --- Predicate taking an `CodeAction` and returning a boolean.
  939. --- @field filter? fun(x: lsp.CodeAction|lsp.Command):boolean
  940. ---
  941. --- When set to `true`, and there is just one remaining action
  942. --- (after filtering), the action is applied without user query.
  943. --- @field apply? boolean
  944. ---
  945. --- Range for which code actions should be requested.
  946. --- If in visual mode this defaults to the active selection.
  947. --- Table must contain `start` and `end` keys with {row,col} tuples
  948. --- using mark-like indexing. See |api-indexing|
  949. --- @field range? {start: integer[], end: integer[]}
  950. --- This is not public because the main extension point is
  951. --- vim.ui.select which can be overridden independently.
  952. ---
  953. --- Can't call/use vim.lsp.handlers['textDocument/codeAction'] because it expects
  954. --- `(err, CodeAction[] | Command[], ctx)`, but we want to aggregate the results
  955. --- from multiple clients to have 1 single UI prompt for the user, yet we still
  956. --- need to be able to link a `CodeAction|Command` to the right client for
  957. --- `codeAction/resolve`
  958. ---@param results table<integer, vim.lsp.CodeActionResultEntry>
  959. ---@param opts? vim.lsp.buf.code_action.Opts
  960. local function on_code_action_results(results, opts)
  961. ---@param a lsp.Command|lsp.CodeAction
  962. local function action_filter(a)
  963. -- filter by specified action kind
  964. if opts and opts.context and opts.context.only then
  965. if not a.kind then
  966. return false
  967. end
  968. local found = false
  969. for _, o in ipairs(opts.context.only) do
  970. -- action kinds are hierarchical with . as a separator: when requesting only 'type-annotate'
  971. -- this filter allows both 'type-annotate' and 'type-annotate.foo', for example
  972. if a.kind == o or vim.startswith(a.kind, o .. '.') then
  973. found = true
  974. break
  975. end
  976. end
  977. if not found then
  978. return false
  979. end
  980. end
  981. -- filter by user function
  982. if opts and opts.filter and not opts.filter(a) then
  983. return false
  984. end
  985. -- no filter removed this action
  986. return true
  987. end
  988. ---@type {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}[]
  989. local actions = {}
  990. for _, result in pairs(results) do
  991. for _, action in pairs(result.result or {}) do
  992. if action_filter(action) then
  993. table.insert(actions, { action = action, ctx = result.ctx })
  994. end
  995. end
  996. end
  997. if #actions == 0 then
  998. vim.notify('No code actions available', vim.log.levels.INFO)
  999. return
  1000. end
  1001. ---@param action lsp.Command|lsp.CodeAction
  1002. ---@param client vim.lsp.Client
  1003. ---@param ctx lsp.HandlerContext
  1004. local function apply_action(action, client, ctx)
  1005. if action.edit then
  1006. util.apply_workspace_edit(action.edit, client.offset_encoding)
  1007. end
  1008. local a_cmd = action.command
  1009. if a_cmd then
  1010. local command = type(a_cmd) == 'table' and a_cmd or action
  1011. --- @cast command lsp.Command
  1012. client:exec_cmd(command, ctx)
  1013. end
  1014. end
  1015. ---@param choice {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}
  1016. local function on_user_choice(choice)
  1017. if not choice then
  1018. return
  1019. end
  1020. -- textDocument/codeAction can return either Command[] or CodeAction[]
  1021. --
  1022. -- CodeAction
  1023. -- ...
  1024. -- edit?: WorkspaceEdit -- <- must be applied before command
  1025. -- command?: Command
  1026. --
  1027. -- Command:
  1028. -- title: string
  1029. -- command: string
  1030. -- arguments?: any[]
  1031. --
  1032. local client = assert(lsp.get_client_by_id(choice.ctx.client_id))
  1033. local action = choice.action
  1034. local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number')
  1035. if not action.edit and client:supports_method(ms.codeAction_resolve) then
  1036. client:request(ms.codeAction_resolve, action, function(err, resolved_action)
  1037. if err then
  1038. if action.command then
  1039. apply_action(action, client, choice.ctx)
  1040. else
  1041. vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
  1042. end
  1043. else
  1044. apply_action(resolved_action, client, choice.ctx)
  1045. end
  1046. end, bufnr)
  1047. else
  1048. apply_action(action, client, choice.ctx)
  1049. end
  1050. end
  1051. -- If options.apply is given, and there are just one remaining code action,
  1052. -- apply it directly without querying the user.
  1053. if opts and opts.apply and #actions == 1 then
  1054. on_user_choice(actions[1])
  1055. return
  1056. end
  1057. ---@param item {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}
  1058. local function format_item(item)
  1059. local clients = lsp.get_clients({ bufnr = item.ctx.bufnr })
  1060. local title = item.action.title:gsub('\r\n', '\\r\\n'):gsub('\n', '\\n')
  1061. if #clients == 1 then
  1062. return title
  1063. end
  1064. local source = lsp.get_client_by_id(item.ctx.client_id).name
  1065. return ('%s [%s]'):format(title, source)
  1066. end
  1067. local select_opts = {
  1068. prompt = 'Code actions:',
  1069. kind = 'codeaction',
  1070. format_item = format_item,
  1071. }
  1072. vim.ui.select(actions, select_opts, on_user_choice)
  1073. end
  1074. --- Selects a code action available at the current
  1075. --- cursor position.
  1076. ---
  1077. ---@param opts? vim.lsp.buf.code_action.Opts
  1078. ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
  1079. ---@see vim.lsp.protocol.CodeActionTriggerKind
  1080. function M.code_action(opts)
  1081. validate('options', opts, 'table', true)
  1082. opts = opts or {}
  1083. -- Detect old API call code_action(context) which should now be
  1084. -- code_action({ context = context} )
  1085. --- @diagnostic disable-next-line:undefined-field
  1086. if opts.diagnostics or opts.only then
  1087. opts = { options = opts }
  1088. end
  1089. local context = opts.context and vim.deepcopy(opts.context) or {}
  1090. if not context.triggerKind then
  1091. context.triggerKind = lsp.protocol.CodeActionTriggerKind.Invoked
  1092. end
  1093. local mode = api.nvim_get_mode().mode
  1094. local bufnr = api.nvim_get_current_buf()
  1095. local win = api.nvim_get_current_win()
  1096. local clients = lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_codeAction })
  1097. local remaining = #clients
  1098. if remaining == 0 then
  1099. if next(lsp.get_clients({ bufnr = bufnr })) then
  1100. vim.notify(lsp._unsupported_method(ms.textDocument_codeAction), vim.log.levels.WARN)
  1101. end
  1102. return
  1103. end
  1104. ---@type table<integer, vim.lsp.CodeActionResultEntry>
  1105. local results = {}
  1106. ---@param err? lsp.ResponseError
  1107. ---@param result? (lsp.Command|lsp.CodeAction)[]
  1108. ---@param ctx lsp.HandlerContext
  1109. local function on_result(err, result, ctx)
  1110. results[ctx.client_id] = { error = err, result = result, ctx = ctx }
  1111. remaining = remaining - 1
  1112. if remaining == 0 then
  1113. on_code_action_results(results, opts)
  1114. end
  1115. end
  1116. for _, client in ipairs(clients) do
  1117. ---@type lsp.CodeActionParams
  1118. local params
  1119. if opts.range then
  1120. assert(type(opts.range) == 'table', 'code_action range must be a table')
  1121. local start = assert(opts.range.start, 'range must have a `start` property')
  1122. local end_ = assert(opts.range['end'], 'range must have a `end` property')
  1123. params = util.make_given_range_params(start, end_, bufnr, client.offset_encoding)
  1124. elseif mode == 'v' or mode == 'V' then
  1125. local range = range_from_selection(bufnr, mode)
  1126. params =
  1127. util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding)
  1128. else
  1129. params = util.make_range_params(win, client.offset_encoding)
  1130. end
  1131. --- @cast params lsp.CodeActionParams
  1132. if context.diagnostics then
  1133. params.context = context
  1134. else
  1135. local ns_push = lsp.diagnostic.get_namespace(client.id, false)
  1136. local ns_pull = lsp.diagnostic.get_namespace(client.id, true)
  1137. local diagnostics = {}
  1138. local lnum = api.nvim_win_get_cursor(0)[1] - 1
  1139. vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_pull, lnum = lnum }))
  1140. vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_push, lnum = lnum }))
  1141. params.context = vim.tbl_extend('force', context, {
  1142. ---@diagnostic disable-next-line: no-unknown
  1143. diagnostics = vim.tbl_map(function(d)
  1144. return d.user_data.lsp
  1145. end, diagnostics),
  1146. })
  1147. end
  1148. client:request(ms.textDocument_codeAction, params, on_result, bufnr)
  1149. end
  1150. end
  1151. --- @deprecated
  1152. --- Executes an LSP server command.
  1153. --- @param command_params lsp.ExecuteCommandParams
  1154. --- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
  1155. function M.execute_command(command_params)
  1156. validate('command', command_params.command, 'string')
  1157. validate('arguments', command_params.arguments, 'table', true)
  1158. vim.deprecate('execute_command', 'client:exec_cmd', '0.12')
  1159. command_params = {
  1160. command = command_params.command,
  1161. arguments = command_params.arguments,
  1162. workDoneToken = command_params.workDoneToken,
  1163. }
  1164. lsp.buf_request(0, ms.workspace_executeCommand, command_params)
  1165. end
  1166. return M