buf.lua 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281
  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. ---
  229. --- If you prefer loclist instead of qflist:
  230. --- ```lua
  231. --- vim.lsp.buf.definition({ loclist = true })
  232. --- vim.lsp.buf.references(nil, { loclist = true })
  233. --- ```
  234. --- @field on_list? fun(t: vim.lsp.LocationOpts.OnList)
  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? table `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. signatures[#signatures + 1] = { client, sig }
  284. end
  285. end
  286. end
  287. end
  288. return signatures
  289. end
  290. local sig_help_ns = api.nvim_create_namespace('nvim.lsp.signature_help')
  291. --- @class vim.lsp.buf.signature_help.Opts : vim.lsp.util.open_floating_preview.Opts
  292. --- @field silent? boolean
  293. --- Displays signature information about the symbol under the cursor in a
  294. --- floating window.
  295. --- @param config? vim.lsp.buf.signature_help.Opts
  296. function M.signature_help(config)
  297. local method = ms.textDocument_signatureHelp
  298. config = config and vim.deepcopy(config) or {}
  299. config.focus_id = method
  300. lsp.buf_request_all(0, method, client_positional_params(), function(results, ctx)
  301. if api.nvim_get_current_buf() ~= ctx.bufnr then
  302. -- Ignore result since buffer changed. This happens for slow language servers.
  303. return
  304. end
  305. local signatures = process_signature_help_results(results)
  306. if not next(signatures) then
  307. if config.silent ~= true then
  308. print('No signature help available')
  309. end
  310. return
  311. end
  312. local ft = vim.bo[ctx.bufnr].filetype
  313. local total = #signatures
  314. local can_cycle = total > 1 and config.focusable
  315. local idx = 0
  316. --- @param update_win? integer
  317. local function show_signature(update_win)
  318. idx = (idx % total) + 1
  319. local client, result = signatures[idx][1], signatures[idx][2]
  320. --- @type string[]?
  321. local triggers =
  322. vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
  323. local lines, hl =
  324. util.convert_signature_help_to_markdown_lines({ signatures = { result } }, ft, triggers)
  325. if not lines then
  326. return
  327. end
  328. local sfx = can_cycle and string.format(' (%d/%d) (<C-s> to cycle)', idx, total) or ''
  329. local title = string.format('Signature Help: %s%s', client.name, sfx)
  330. if config.border then
  331. config.title = title
  332. else
  333. table.insert(lines, 1, '# ' .. title)
  334. if hl then
  335. hl[1] = hl[1] + 1
  336. hl[3] = hl[3] + 1
  337. end
  338. end
  339. config._update_win = update_win
  340. local buf, win = util.open_floating_preview(lines, 'markdown', config)
  341. if hl then
  342. vim.api.nvim_buf_clear_namespace(buf, sig_help_ns, 0, -1)
  343. vim.hl.range(
  344. buf,
  345. sig_help_ns,
  346. 'LspSignatureActiveParameter',
  347. { hl[1], hl[2] },
  348. { hl[3], hl[4] }
  349. )
  350. end
  351. return buf, win
  352. end
  353. local fbuf, fwin = show_signature()
  354. if can_cycle then
  355. vim.keymap.set('n', '<C-s>', function()
  356. show_signature(fwin)
  357. end, {
  358. buffer = fbuf,
  359. desc = 'Cycle next signature',
  360. })
  361. end
  362. end)
  363. end
  364. --- @deprecated
  365. --- Retrieves the completion items at the current cursor position. Can only be
  366. --- called in Insert mode.
  367. ---
  368. ---@param context table (context support not yet implemented) Additional information
  369. --- about the context in which a completion was triggered (how it was triggered,
  370. --- and by which trigger character, if applicable)
  371. ---
  372. ---@see vim.lsp.protocol.CompletionTriggerKind
  373. function M.completion(context)
  374. vim.depends('vim.lsp.buf.completion', 'vim.lsp.commpletion.trigger', '0.12')
  375. return lsp.buf_request(
  376. 0,
  377. ms.textDocument_completion,
  378. client_positional_params({
  379. context = context,
  380. })
  381. )
  382. end
  383. ---@param bufnr integer
  384. ---@param mode "v"|"V"
  385. ---@return table {start={row,col}, end={row,col}} using (1, 0) indexing
  386. local function range_from_selection(bufnr, mode)
  387. -- TODO: Use `vim.fn.getregionpos()` instead.
  388. -- [bufnum, lnum, col, off]; both row and column 1-indexed
  389. local start = vim.fn.getpos('v')
  390. local end_ = vim.fn.getpos('.')
  391. local start_row = start[2]
  392. local start_col = start[3]
  393. local end_row = end_[2]
  394. local end_col = end_[3]
  395. -- A user can start visual selection at the end and move backwards
  396. -- Normalize the range to start < end
  397. if start_row == end_row and end_col < start_col then
  398. end_col, start_col = start_col, end_col
  399. elseif end_row < start_row then
  400. start_row, end_row = end_row, start_row
  401. start_col, end_col = end_col, start_col
  402. end
  403. if mode == 'V' then
  404. start_col = 1
  405. local lines = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)
  406. end_col = #lines[1]
  407. end
  408. return {
  409. ['start'] = { start_row, start_col - 1 },
  410. ['end'] = { end_row, end_col - 1 },
  411. }
  412. end
  413. --- @class vim.lsp.buf.format.Opts
  414. --- @inlinedoc
  415. ---
  416. --- Can be used to specify FormattingOptions. Some unspecified options will be
  417. --- automatically derived from the current Nvim options.
  418. --- See https://microsoft.github.io/language-server-protocol/specification/#formattingOptions
  419. --- @field formatting_options? table
  420. ---
  421. --- Time in milliseconds to block for formatting requests. No effect if async=true.
  422. --- (default: `1000`)
  423. --- @field timeout_ms? integer
  424. ---
  425. --- Restrict formatting to the clients attached to the given buffer.
  426. --- (default: current buffer)
  427. --- @field bufnr? integer
  428. ---
  429. --- Predicate used to filter clients. Receives a client as argument and must
  430. --- return a boolean. Clients matching the predicate are included. Example:
  431. --- ```lua
  432. --- -- Never request typescript-language-server for formatting
  433. --- vim.lsp.buf.format {
  434. --- filter = function(client) return client.name ~= "ts_ls" end
  435. --- }
  436. --- ```
  437. --- @field filter? fun(client: vim.lsp.Client): boolean?
  438. ---
  439. --- If true the method won't block.
  440. --- Editing the buffer while formatting asynchronous can lead to unexpected
  441. --- changes.
  442. --- (Default: false)
  443. --- @field async? boolean
  444. ---
  445. --- Restrict formatting to the client with ID (client.id) matching this field.
  446. --- @field id? integer
  447. ---
  448. --- Restrict formatting to the client with name (client.name) matching this field.
  449. --- @field name? string
  450. ---
  451. --- Range to format.
  452. --- Table must contain `start` and `end` keys with {row,col} tuples using
  453. --- (1,0) indexing.
  454. --- Can also be a list of tables that contain `start` and `end` keys as described above,
  455. --- in which case `textDocument/rangesFormatting` support is required.
  456. --- (Default: current selection in visual mode, `nil` in other modes,
  457. --- formatting the full buffer)
  458. --- @field range? {start:[integer,integer],end:[integer, integer]}|{start:[integer,integer],end:[integer,integer]}[]
  459. --- Formats a buffer using the attached (and optionally filtered) language
  460. --- server clients.
  461. ---
  462. --- @param opts? vim.lsp.buf.format.Opts
  463. function M.format(opts)
  464. opts = opts or {}
  465. local bufnr = vim._resolve_bufnr(opts.bufnr)
  466. local mode = api.nvim_get_mode().mode
  467. local range = opts.range
  468. -- Try to use visual selection if no range is given
  469. if not range and mode == 'v' or mode == 'V' then
  470. range = range_from_selection(bufnr, mode)
  471. end
  472. local passed_multiple_ranges = (range and #range ~= 0 and type(range[1]) == 'table')
  473. local method ---@type string
  474. if passed_multiple_ranges then
  475. method = ms.textDocument_rangesFormatting
  476. elseif range then
  477. method = ms.textDocument_rangeFormatting
  478. else
  479. method = ms.textDocument_formatting
  480. end
  481. local clients = lsp.get_clients({
  482. id = opts.id,
  483. bufnr = bufnr,
  484. name = opts.name,
  485. method = method,
  486. })
  487. if opts.filter then
  488. clients = vim.tbl_filter(opts.filter, clients)
  489. end
  490. if #clients == 0 then
  491. vim.notify('[LSP] Format request failed, no matching language servers.')
  492. end
  493. --- @param client vim.lsp.Client
  494. --- @param params lsp.DocumentFormattingParams
  495. --- @return lsp.DocumentFormattingParams
  496. local function set_range(client, params)
  497. local to_lsp_range = function(r) ---@return lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams
  498. return util.make_given_range_params(r.start, r['end'], bufnr, client.offset_encoding).range
  499. end
  500. if passed_multiple_ranges then
  501. params.ranges = vim.tbl_map(to_lsp_range, range)
  502. elseif range then
  503. params.range = to_lsp_range(range)
  504. end
  505. return params
  506. end
  507. if opts.async then
  508. --- @param idx integer
  509. --- @param client vim.lsp.Client
  510. local function do_format(idx, client)
  511. if not client then
  512. return
  513. end
  514. local params = set_range(client, util.make_formatting_params(opts.formatting_options))
  515. client:request(method, params, function(...)
  516. local handler = client.handlers[method] or lsp.handlers[method]
  517. handler(...)
  518. do_format(next(clients, idx))
  519. end, bufnr)
  520. end
  521. do_format(next(clients))
  522. else
  523. local timeout_ms = opts.timeout_ms or 1000
  524. for _, client in pairs(clients) do
  525. local params = set_range(client, util.make_formatting_params(opts.formatting_options))
  526. local result, err = client:request_sync(method, params, timeout_ms, bufnr)
  527. if result and result.result then
  528. util.apply_text_edits(result.result, bufnr, client.offset_encoding)
  529. elseif err then
  530. vim.notify(string.format('[LSP][%s] %s', client.name, err), vim.log.levels.WARN)
  531. end
  532. end
  533. end
  534. end
  535. --- @class vim.lsp.buf.rename.Opts
  536. --- @inlinedoc
  537. ---
  538. --- Predicate used to filter clients. Receives a client as argument and
  539. --- must return a boolean. Clients matching the predicate are included.
  540. --- @field filter? fun(client: vim.lsp.Client): boolean?
  541. ---
  542. --- Restrict clients used for rename to ones where client.name matches
  543. --- this field.
  544. --- @field name? string
  545. ---
  546. --- (default: current buffer)
  547. --- @field bufnr? integer
  548. --- Renames all references to the symbol under the cursor.
  549. ---
  550. ---@param new_name string|nil If not provided, the user will be prompted for a new
  551. --- name using |vim.ui.input()|.
  552. ---@param opts? vim.lsp.buf.rename.Opts Additional options:
  553. function M.rename(new_name, opts)
  554. opts = opts or {}
  555. local bufnr = vim._resolve_bufnr(opts.bufnr)
  556. local clients = lsp.get_clients({
  557. bufnr = bufnr,
  558. name = opts.name,
  559. -- Clients must at least support rename, prepareRename is optional
  560. method = ms.textDocument_rename,
  561. })
  562. if opts.filter then
  563. clients = vim.tbl_filter(opts.filter, clients)
  564. end
  565. if #clients == 0 then
  566. vim.notify('[LSP] Rename, no matching language servers with rename capability.')
  567. end
  568. local win = api.nvim_get_current_win()
  569. -- Compute early to account for cursor movements after going async
  570. local cword = vim.fn.expand('<cword>')
  571. --- @param range lsp.Range
  572. --- @param position_encoding string
  573. local function get_text_at_range(range, position_encoding)
  574. return api.nvim_buf_get_text(
  575. bufnr,
  576. range.start.line,
  577. util._get_line_byte_from_position(bufnr, range.start, position_encoding),
  578. range['end'].line,
  579. util._get_line_byte_from_position(bufnr, range['end'], position_encoding),
  580. {}
  581. )[1]
  582. end
  583. --- @param idx integer
  584. --- @param client? vim.lsp.Client
  585. local function try_use_client(idx, client)
  586. if not client then
  587. return
  588. end
  589. --- @param name string
  590. local function rename(name)
  591. local params = util.make_position_params(win, client.offset_encoding)
  592. params.newName = name
  593. local handler = client.handlers[ms.textDocument_rename]
  594. or lsp.handlers[ms.textDocument_rename]
  595. client:request(ms.textDocument_rename, params, function(...)
  596. handler(...)
  597. try_use_client(next(clients, idx))
  598. end, bufnr)
  599. end
  600. if client:supports_method(ms.textDocument_prepareRename) then
  601. local params = util.make_position_params(win, client.offset_encoding)
  602. client:request(ms.textDocument_prepareRename, params, function(err, result)
  603. if err or result == nil then
  604. if next(clients, idx) then
  605. try_use_client(next(clients, idx))
  606. else
  607. local msg = err and ('Error on prepareRename: ' .. (err.message or ''))
  608. or 'Nothing to rename'
  609. vim.notify(msg, vim.log.levels.INFO)
  610. end
  611. return
  612. end
  613. if new_name then
  614. rename(new_name)
  615. return
  616. end
  617. local prompt_opts = {
  618. prompt = 'New Name: ',
  619. }
  620. -- result: Range | { range: Range, placeholder: string }
  621. if result.placeholder then
  622. prompt_opts.default = result.placeholder
  623. elseif result.start then
  624. prompt_opts.default = get_text_at_range(result, client.offset_encoding)
  625. elseif result.range then
  626. prompt_opts.default = get_text_at_range(result.range, client.offset_encoding)
  627. else
  628. prompt_opts.default = cword
  629. end
  630. vim.ui.input(prompt_opts, function(input)
  631. if not input or #input == 0 then
  632. return
  633. end
  634. rename(input)
  635. end)
  636. end, bufnr)
  637. else
  638. assert(
  639. client:supports_method(ms.textDocument_rename),
  640. 'Client must support textDocument/rename'
  641. )
  642. if new_name then
  643. rename(new_name)
  644. return
  645. end
  646. local prompt_opts = {
  647. prompt = 'New Name: ',
  648. default = cword,
  649. }
  650. vim.ui.input(prompt_opts, function(input)
  651. if not input or #input == 0 then
  652. return
  653. end
  654. rename(input)
  655. end)
  656. end
  657. end
  658. try_use_client(next(clients))
  659. end
  660. --- Lists all the references to the symbol under the cursor in the quickfix window.
  661. ---
  662. ---@param context lsp.ReferenceContext? Context for the request
  663. ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
  664. ---@param opts? vim.lsp.ListOpts
  665. function M.references(context, opts)
  666. validate('context', context, 'table', true)
  667. local bufnr = api.nvim_get_current_buf()
  668. local clients = lsp.get_clients({ method = ms.textDocument_references, bufnr = bufnr })
  669. if not next(clients) then
  670. return
  671. end
  672. local win = api.nvim_get_current_win()
  673. opts = opts or {}
  674. local all_items = {}
  675. local title = 'References'
  676. local function on_done()
  677. if not next(all_items) then
  678. vim.notify('No references found')
  679. else
  680. local list = {
  681. title = title,
  682. items = all_items,
  683. context = {
  684. method = ms.textDocument_references,
  685. bufnr = bufnr,
  686. },
  687. }
  688. if opts.loclist then
  689. vim.fn.setloclist(0, {}, ' ', list)
  690. vim.cmd.lopen()
  691. elseif opts.on_list then
  692. assert(vim.is_callable(opts.on_list), 'on_list is not a function')
  693. opts.on_list(list)
  694. else
  695. vim.fn.setqflist({}, ' ', list)
  696. vim.cmd('botright copen')
  697. end
  698. end
  699. end
  700. local remaining = #clients
  701. for _, client in ipairs(clients) do
  702. local params = util.make_position_params(win, client.offset_encoding)
  703. ---@diagnostic disable-next-line: inject-field
  704. params.context = context or {
  705. includeDeclaration = true,
  706. }
  707. client:request(ms.textDocument_references, params, function(_, result)
  708. local items = util.locations_to_items(result or {}, client.offset_encoding)
  709. vim.list_extend(all_items, items)
  710. remaining = remaining - 1
  711. if remaining == 0 then
  712. on_done()
  713. end
  714. end)
  715. end
  716. end
  717. --- Lists all symbols in the current buffer in the quickfix window.
  718. --- @param opts? vim.lsp.ListOpts
  719. function M.document_symbol(opts)
  720. local params = { textDocument = util.make_text_document_params() }
  721. request_with_opts(ms.textDocument_documentSymbol, params, opts)
  722. end
  723. --- @param client_id integer
  724. --- @param method string
  725. --- @param params table
  726. --- @param handler? lsp.Handler
  727. --- @param bufnr? integer
  728. local function request_with_id(client_id, method, params, handler, bufnr)
  729. local client = lsp.get_client_by_id(client_id)
  730. if not client then
  731. vim.notify(
  732. string.format('Client with id=%d disappeared during hierarchy request', client_id),
  733. vim.log.levels.WARN
  734. )
  735. return
  736. end
  737. client:request(method, params, handler, bufnr)
  738. end
  739. --- @param item lsp.TypeHierarchyItem|lsp.CallHierarchyItem
  740. local function format_hierarchy_item(item)
  741. if not item.detail or #item.detail == 0 then
  742. return item.name
  743. end
  744. return string.format('%s %s', item.name, item.detail)
  745. end
  746. local hierarchy_methods = {
  747. [ms.typeHierarchy_subtypes] = 'type',
  748. [ms.typeHierarchy_supertypes] = 'type',
  749. [ms.callHierarchy_incomingCalls] = 'call',
  750. [ms.callHierarchy_outgoingCalls] = 'call',
  751. }
  752. --- @param method string
  753. local function hierarchy(method)
  754. local kind = hierarchy_methods[method]
  755. if not kind then
  756. error('unsupported method ' .. method)
  757. end
  758. local prepare_method = kind == 'type' and ms.textDocument_prepareTypeHierarchy
  759. or ms.textDocument_prepareCallHierarchy
  760. local bufnr = api.nvim_get_current_buf()
  761. local clients = lsp.get_clients({ bufnr = bufnr, method = prepare_method })
  762. if not next(clients) then
  763. vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
  764. return
  765. end
  766. local win = api.nvim_get_current_win()
  767. --- @param results [integer, lsp.TypeHierarchyItem|lsp.CallHierarchyItem][]
  768. local function on_response(results)
  769. if #results == 0 then
  770. vim.notify('No item resolved', vim.log.levels.WARN)
  771. elseif #results == 1 then
  772. local client_id, item = results[1][1], results[1][2]
  773. request_with_id(client_id, method, { item = item }, nil, bufnr)
  774. else
  775. vim.ui.select(results, {
  776. prompt = string.format('Select a %s hierarchy item:', kind),
  777. kind = kind .. 'hierarchy',
  778. format_item = function(x)
  779. return format_hierarchy_item(x[2])
  780. end,
  781. }, function(x)
  782. if x then
  783. local client_id, item = x[1], x[2]
  784. request_with_id(client_id, method, { item = item }, nil, bufnr)
  785. end
  786. end)
  787. end
  788. end
  789. local results = {} --- @type [integer, lsp.TypeHierarchyItem|lsp.CallHierarchyItem][]
  790. local remaining = #clients
  791. for _, client in ipairs(clients) do
  792. local params = util.make_position_params(win, client.offset_encoding)
  793. --- @param result lsp.CallHierarchyItem[]|lsp.TypeHierarchyItem[]?
  794. client:request(prepare_method, params, function(err, result, ctx)
  795. if err then
  796. vim.notify(err.message, vim.log.levels.WARN)
  797. elseif result then
  798. for _, item in ipairs(result) do
  799. results[#results + 1] = { ctx.client_id, item }
  800. end
  801. end
  802. remaining = remaining - 1
  803. if remaining == 0 then
  804. on_response(results)
  805. end
  806. end, bufnr)
  807. end
  808. end
  809. --- Lists all the call sites of the symbol under the cursor in the
  810. --- |quickfix| window. If the symbol can resolve to multiple
  811. --- items, the user can pick one in the |inputlist()|.
  812. function M.incoming_calls()
  813. hierarchy(ms.callHierarchy_incomingCalls)
  814. end
  815. --- Lists all the items that are called by the symbol under the
  816. --- cursor in the |quickfix| window. If the symbol can resolve to
  817. --- multiple items, the user can pick one in the |inputlist()|.
  818. function M.outgoing_calls()
  819. hierarchy(ms.callHierarchy_outgoingCalls)
  820. end
  821. --- Lists all the subtypes or supertypes of the symbol under the
  822. --- cursor in the |quickfix| window. If the symbol can resolve to
  823. --- multiple items, the user can pick one using |vim.ui.select()|.
  824. ---@param kind "subtypes"|"supertypes"
  825. function M.typehierarchy(kind)
  826. local method = kind == 'subtypes' and ms.typeHierarchy_subtypes or ms.typeHierarchy_supertypes
  827. hierarchy(method)
  828. end
  829. --- List workspace folders.
  830. ---
  831. function M.list_workspace_folders()
  832. local workspace_folders = {}
  833. for _, client in pairs(lsp.get_clients({ bufnr = 0 })) do
  834. for _, folder in pairs(client.workspace_folders or {}) do
  835. table.insert(workspace_folders, folder.name)
  836. end
  837. end
  838. return workspace_folders
  839. end
  840. --- Add the folder at path to the workspace folders. If {path} is
  841. --- not provided, the user will be prompted for a path using |input()|.
  842. --- @param workspace_folder? string
  843. function M.add_workspace_folder(workspace_folder)
  844. workspace_folder = workspace_folder
  845. or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'), 'dir')
  846. api.nvim_command('redraw')
  847. if not (workspace_folder and #workspace_folder > 0) then
  848. return
  849. end
  850. if vim.fn.isdirectory(workspace_folder) == 0 then
  851. print(workspace_folder, ' is not a valid directory')
  852. return
  853. end
  854. local bufnr = api.nvim_get_current_buf()
  855. for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
  856. client:_add_workspace_folder(workspace_folder)
  857. end
  858. end
  859. --- Remove the folder at path from the workspace folders. If
  860. --- {path} is not provided, the user will be prompted for
  861. --- a path using |input()|.
  862. --- @param workspace_folder? string
  863. function M.remove_workspace_folder(workspace_folder)
  864. workspace_folder = workspace_folder
  865. or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'))
  866. api.nvim_command('redraw')
  867. if not workspace_folder or #workspace_folder == 0 then
  868. return
  869. end
  870. local bufnr = api.nvim_get_current_buf()
  871. for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
  872. client:_remove_workspace_folder(workspace_folder)
  873. end
  874. print(workspace_folder, 'is not currently part of the workspace')
  875. end
  876. --- Lists all symbols in the current workspace in the quickfix window.
  877. ---
  878. --- The list is filtered against {query}; if the argument is omitted from the
  879. --- call, the user is prompted to enter a string on the command line. An empty
  880. --- string means no filtering is done.
  881. ---
  882. --- @param query string? optional
  883. --- @param opts? vim.lsp.ListOpts
  884. function M.workspace_symbol(query, opts)
  885. query = query or npcall(vim.fn.input, 'Query: ')
  886. if query == nil then
  887. return
  888. end
  889. local params = { query = query }
  890. request_with_opts(ms.workspace_symbol, params, opts)
  891. end
  892. --- Send request to the server to resolve document highlights for the current
  893. --- text document position. This request can be triggered by a key mapping or
  894. --- by events such as `CursorHold`, e.g.:
  895. ---
  896. --- ```vim
  897. --- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
  898. --- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()
  899. --- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
  900. --- ```
  901. ---
  902. --- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups
  903. --- to be defined or you won't be able to see the actual highlights.
  904. --- |hl-LspReferenceText|
  905. --- |hl-LspReferenceRead|
  906. --- |hl-LspReferenceWrite|
  907. function M.document_highlight()
  908. lsp.buf_request(0, ms.textDocument_documentHighlight, client_positional_params())
  909. end
  910. --- Removes document highlights from current buffer.
  911. function M.clear_references()
  912. util.buf_clear_references()
  913. end
  914. ---@nodoc
  915. ---@class vim.lsp.CodeActionResultEntry
  916. ---@field error? lsp.ResponseError
  917. ---@field result? (lsp.Command|lsp.CodeAction)[]
  918. ---@field ctx lsp.HandlerContext
  919. --- @class vim.lsp.buf.code_action.Opts
  920. --- @inlinedoc
  921. ---
  922. --- Corresponds to `CodeActionContext` of the LSP specification:
  923. --- - {diagnostics}? (`table`) LSP `Diagnostic[]`. Inferred from the current
  924. --- position if not provided.
  925. --- - {only}? (`table`) List of LSP `CodeActionKind`s used to filter the code actions.
  926. --- Most language servers support values like `refactor`
  927. --- or `quickfix`.
  928. --- - {triggerKind}? (`integer`) The reason why code actions were requested.
  929. --- @field context? lsp.CodeActionContext
  930. ---
  931. --- Predicate taking an `CodeAction` and returning a boolean.
  932. --- @field filter? fun(x: lsp.CodeAction|lsp.Command):boolean
  933. ---
  934. --- When set to `true`, and there is just one remaining action
  935. --- (after filtering), the action is applied without user query.
  936. --- @field apply? boolean
  937. ---
  938. --- Range for which code actions should be requested.
  939. --- If in visual mode this defaults to the active selection.
  940. --- Table must contain `start` and `end` keys with {row,col} tuples
  941. --- using mark-like indexing. See |api-indexing|
  942. --- @field range? {start: integer[], end: integer[]}
  943. --- This is not public because the main extension point is
  944. --- vim.ui.select which can be overridden independently.
  945. ---
  946. --- Can't call/use vim.lsp.handlers['textDocument/codeAction'] because it expects
  947. --- `(err, CodeAction[] | Command[], ctx)`, but we want to aggregate the results
  948. --- from multiple clients to have 1 single UI prompt for the user, yet we still
  949. --- need to be able to link a `CodeAction|Command` to the right client for
  950. --- `codeAction/resolve`
  951. ---@param results table<integer, vim.lsp.CodeActionResultEntry>
  952. ---@param opts? vim.lsp.buf.code_action.Opts
  953. local function on_code_action_results(results, opts)
  954. ---@param a lsp.Command|lsp.CodeAction
  955. local function action_filter(a)
  956. -- filter by specified action kind
  957. if opts and opts.context and opts.context.only then
  958. if not a.kind then
  959. return false
  960. end
  961. local found = false
  962. for _, o in ipairs(opts.context.only) do
  963. -- action kinds are hierarchical with . as a separator: when requesting only 'type-annotate'
  964. -- this filter allows both 'type-annotate' and 'type-annotate.foo', for example
  965. if a.kind == o or vim.startswith(a.kind, o .. '.') then
  966. found = true
  967. break
  968. end
  969. end
  970. if not found then
  971. return false
  972. end
  973. end
  974. -- filter by user function
  975. if opts and opts.filter and not opts.filter(a) then
  976. return false
  977. end
  978. -- no filter removed this action
  979. return true
  980. end
  981. ---@type {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}[]
  982. local actions = {}
  983. for _, result in pairs(results) do
  984. for _, action in pairs(result.result or {}) do
  985. if action_filter(action) then
  986. table.insert(actions, { action = action, ctx = result.ctx })
  987. end
  988. end
  989. end
  990. if #actions == 0 then
  991. vim.notify('No code actions available', vim.log.levels.INFO)
  992. return
  993. end
  994. ---@param action lsp.Command|lsp.CodeAction
  995. ---@param client vim.lsp.Client
  996. ---@param ctx lsp.HandlerContext
  997. local function apply_action(action, client, ctx)
  998. if action.edit then
  999. util.apply_workspace_edit(action.edit, client.offset_encoding)
  1000. end
  1001. local a_cmd = action.command
  1002. if a_cmd then
  1003. local command = type(a_cmd) == 'table' and a_cmd or action
  1004. --- @cast command lsp.Command
  1005. client:exec_cmd(command, ctx)
  1006. end
  1007. end
  1008. ---@param choice {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}
  1009. local function on_user_choice(choice)
  1010. if not choice then
  1011. return
  1012. end
  1013. -- textDocument/codeAction can return either Command[] or CodeAction[]
  1014. --
  1015. -- CodeAction
  1016. -- ...
  1017. -- edit?: WorkspaceEdit -- <- must be applied before command
  1018. -- command?: Command
  1019. --
  1020. -- Command:
  1021. -- title: string
  1022. -- command: string
  1023. -- arguments?: any[]
  1024. --
  1025. local client = assert(lsp.get_client_by_id(choice.ctx.client_id))
  1026. local action = choice.action
  1027. local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number')
  1028. if not action.edit and client:supports_method(ms.codeAction_resolve) then
  1029. client:request(ms.codeAction_resolve, action, function(err, resolved_action)
  1030. if err then
  1031. if action.command then
  1032. apply_action(action, client, choice.ctx)
  1033. else
  1034. vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
  1035. end
  1036. else
  1037. apply_action(resolved_action, client, choice.ctx)
  1038. end
  1039. end, bufnr)
  1040. else
  1041. apply_action(action, client, choice.ctx)
  1042. end
  1043. end
  1044. -- If options.apply is given, and there are just one remaining code action,
  1045. -- apply it directly without querying the user.
  1046. if opts and opts.apply and #actions == 1 then
  1047. on_user_choice(actions[1])
  1048. return
  1049. end
  1050. ---@param item {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}
  1051. local function format_item(item)
  1052. local clients = lsp.get_clients({ bufnr = item.ctx.bufnr })
  1053. local title = item.action.title:gsub('\r\n', '\\r\\n'):gsub('\n', '\\n')
  1054. if #clients == 1 then
  1055. return title
  1056. end
  1057. local source = lsp.get_client_by_id(item.ctx.client_id).name
  1058. return ('%s [%s]'):format(title, source)
  1059. end
  1060. local select_opts = {
  1061. prompt = 'Code actions:',
  1062. kind = 'codeaction',
  1063. format_item = format_item,
  1064. }
  1065. vim.ui.select(actions, select_opts, on_user_choice)
  1066. end
  1067. --- Selects a code action available at the current
  1068. --- cursor position.
  1069. ---
  1070. ---@param opts? vim.lsp.buf.code_action.Opts
  1071. ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
  1072. ---@see vim.lsp.protocol.CodeActionTriggerKind
  1073. function M.code_action(opts)
  1074. validate('options', opts, 'table', true)
  1075. opts = opts or {}
  1076. -- Detect old API call code_action(context) which should now be
  1077. -- code_action({ context = context} )
  1078. --- @diagnostic disable-next-line:undefined-field
  1079. if opts.diagnostics or opts.only then
  1080. opts = { options = opts }
  1081. end
  1082. local context = opts.context and vim.deepcopy(opts.context) or {}
  1083. if not context.triggerKind then
  1084. context.triggerKind = lsp.protocol.CodeActionTriggerKind.Invoked
  1085. end
  1086. local mode = api.nvim_get_mode().mode
  1087. local bufnr = api.nvim_get_current_buf()
  1088. local win = api.nvim_get_current_win()
  1089. local clients = lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_codeAction })
  1090. local remaining = #clients
  1091. if remaining == 0 then
  1092. if next(lsp.get_clients({ bufnr = bufnr })) then
  1093. vim.notify(lsp._unsupported_method(ms.textDocument_codeAction), vim.log.levels.WARN)
  1094. end
  1095. return
  1096. end
  1097. ---@type table<integer, vim.lsp.CodeActionResultEntry>
  1098. local results = {}
  1099. ---@param err? lsp.ResponseError
  1100. ---@param result? (lsp.Command|lsp.CodeAction)[]
  1101. ---@param ctx lsp.HandlerContext
  1102. local function on_result(err, result, ctx)
  1103. results[ctx.client_id] = { error = err, result = result, ctx = ctx }
  1104. remaining = remaining - 1
  1105. if remaining == 0 then
  1106. on_code_action_results(results, opts)
  1107. end
  1108. end
  1109. for _, client in ipairs(clients) do
  1110. ---@type lsp.CodeActionParams
  1111. local params
  1112. if opts.range then
  1113. assert(type(opts.range) == 'table', 'code_action range must be a table')
  1114. local start = assert(opts.range.start, 'range must have a `start` property')
  1115. local end_ = assert(opts.range['end'], 'range must have a `end` property')
  1116. params = util.make_given_range_params(start, end_, bufnr, client.offset_encoding)
  1117. elseif mode == 'v' or mode == 'V' then
  1118. local range = range_from_selection(bufnr, mode)
  1119. params =
  1120. util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding)
  1121. else
  1122. params = util.make_range_params(win, client.offset_encoding)
  1123. end
  1124. if context.diagnostics then
  1125. params.context = context
  1126. else
  1127. local ns_push = lsp.diagnostic.get_namespace(client.id, false)
  1128. local ns_pull = lsp.diagnostic.get_namespace(client.id, true)
  1129. local diagnostics = {}
  1130. local lnum = api.nvim_win_get_cursor(0)[1] - 1
  1131. vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_pull, lnum = lnum }))
  1132. vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_push, lnum = lnum }))
  1133. params.context = vim.tbl_extend('force', context, {
  1134. ---@diagnostic disable-next-line: no-unknown
  1135. diagnostics = vim.tbl_map(function(d)
  1136. return d.user_data.lsp
  1137. end, diagnostics),
  1138. })
  1139. end
  1140. client:request(ms.textDocument_codeAction, params, on_result, bufnr)
  1141. end
  1142. end
  1143. --- @deprecated
  1144. --- Executes an LSP server command.
  1145. --- @param command_params lsp.ExecuteCommandParams
  1146. --- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
  1147. function M.execute_command(command_params)
  1148. validate('command', command_params.command, 'string')
  1149. validate('arguments', command_params.arguments, 'table', true)
  1150. vim.deprecate('execute_command', 'client:exec_cmd', '0.12')
  1151. command_params = {
  1152. command = command_params.command,
  1153. arguments = command_params.arguments,
  1154. workDoneToken = command_params.workDoneToken,
  1155. }
  1156. lsp.buf_request(0, ms.workspace_executeCommand, command_params)
  1157. end
  1158. return M