neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

buf.lua (51759B)


      1 --- @brief
      2 --- The `vim.lsp.buf_…` functions perform operations for LSP clients attached to the current buffer.
      3 
      4 local api = vim.api
      5 local lsp = vim.lsp
      6 local validate = vim.validate
      7 local util = require('vim.lsp.util')
      8 local npcall = vim.F.npcall
      9 
     10 local M = {}
     11 
     12 --- @param params? table
     13 --- @return fun(client: vim.lsp.Client): lsp.TextDocumentPositionParams
     14 local function client_positional_params(params)
     15  local win = api.nvim_get_current_win()
     16  return function(client)
     17    local ret = util.make_position_params(win, client.offset_encoding)
     18    if params then
     19      ret = vim.tbl_extend('force', ret, params)
     20    end
     21    return ret
     22  end
     23 end
     24 
     25 local hover_ns = api.nvim_create_namespace('nvim.lsp.hover_range')
     26 local rename_ns = api.nvim_create_namespace('nvim.lsp.rename_range')
     27 
     28 --- @class vim.lsp.buf.hover.Opts : vim.lsp.util.open_floating_preview.Opts
     29 --- @field silent? boolean
     30 
     31 --- Displays hover information about the symbol under the cursor in a floating
     32 --- window. The window will be dismissed on cursor move.
     33 --- Calling the function twice will jump into the floating window
     34 --- (thus by default, "KK" will open the hover window and focus it).
     35 --- In the floating window, all commands and mappings are available as usual,
     36 --- except that "q" dismisses the window.
     37 --- You can scroll the contents the same as you would any other buffer.
     38 ---
     39 --- Note: to disable hover highlights, add the following to your config:
     40 ---
     41 --- ```lua
     42 --- vim.api.nvim_create_autocmd('ColorScheme', {
     43 ---   callback = function()
     44 ---     vim.api.nvim_set_hl(0, 'LspReferenceTarget', {})
     45 ---   end,
     46 --- })
     47 --- ```
     48 --- @param config? vim.lsp.buf.hover.Opts
     49 function M.hover(config)
     50  validate('config', config, 'table', true)
     51 
     52  config = config or {}
     53  config.focus_id = 'textDocument/hover'
     54 
     55  lsp.buf_request_all(0, 'textDocument/hover', client_positional_params(), function(results, ctx)
     56    local bufnr = assert(ctx.bufnr)
     57    if api.nvim_get_current_buf() ~= bufnr then
     58      -- Ignore result since buffer changed. This happens for slow language servers.
     59      return
     60    end
     61 
     62    -- Filter errors from results
     63    local results1 = {} --- @type table<integer,lsp.Hover>
     64    local empty_response = false
     65 
     66    for client_id, resp in pairs(results) do
     67      local err, result = resp.err, resp.result
     68      if err then
     69        lsp.log.error(err.code, err.message)
     70      elseif result and result.contents then
     71        -- Make sure the response is not empty
     72        -- Five response shapes:
     73        -- - MarkupContent: { kind="markdown", value="doc" }
     74        -- - MarkedString-string: "doc"
     75        -- - MarkedString-pair: { language="c", value="doc" }
     76        -- - MarkedString[]-string: { "doc1", ... }
     77        -- - MarkedString[]-pair: { { language="c", value="doc1" }, ... }
     78        if
     79          (
     80            type(result.contents) == 'table'
     81            and #(
     82                vim.tbl_get(result.contents, 'value') -- MarkupContent or MarkedString-pair
     83                or vim.tbl_get(result.contents, 1, 'value') -- MarkedString[]-pair
     84                or result.contents[1] -- MarkedString[]-string
     85                or ''
     86              )
     87              > 0
     88          )
     89          or (
     90            type(result.contents) == 'string' and #result.contents > 0 -- MarkedString-string
     91          )
     92        then
     93          results1[client_id] = result
     94        else
     95          empty_response = true
     96        end
     97      end
     98    end
     99 
    100    if vim.tbl_isempty(results1) then
    101      if config.silent ~= true then
    102        if empty_response then
    103          vim.notify('Empty hover response', vim.log.levels.INFO)
    104        else
    105          vim.notify('No information available', vim.log.levels.INFO)
    106        end
    107      end
    108      return
    109    end
    110 
    111    local contents = {} --- @type string[]
    112 
    113    local nresults = #vim.tbl_keys(results1)
    114 
    115    local format = 'markdown'
    116 
    117    for client_id, result in pairs(results1) do
    118      local client = assert(lsp.get_client_by_id(client_id))
    119      if nresults > 1 then
    120        -- Show client name if there are multiple clients
    121        contents[#contents + 1] = string.format('# %s', client.name)
    122      end
    123      if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
    124        if #results1 == 1 then
    125          format = 'plaintext'
    126          contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
    127        else
    128          -- Surround plaintext with ``` to get correct formatting
    129          contents[#contents + 1] = '```'
    130          vim.list_extend(
    131            contents,
    132            vim.split(result.contents.value or '', '\n', { trimempty = true })
    133          )
    134          contents[#contents + 1] = '```'
    135        end
    136      else
    137        vim.list_extend(contents, util.convert_input_to_markdown_lines(result.contents))
    138      end
    139      local range = result.range
    140      if range then
    141        local start = range.start
    142        local end_ = range['end']
    143        local start_idx = util._get_line_byte_from_position(bufnr, start, client.offset_encoding)
    144        local end_idx = util._get_line_byte_from_position(bufnr, end_, client.offset_encoding)
    145 
    146        vim.hl.range(
    147          bufnr,
    148          hover_ns,
    149          'LspReferenceTarget',
    150          { start.line, start_idx },
    151          { end_.line, end_idx },
    152          { priority = vim.hl.priorities.user }
    153        )
    154      end
    155      contents[#contents + 1] = '---'
    156    end
    157 
    158    -- Remove last linebreak ('---')
    159    contents[#contents] = nil
    160 
    161    local _, winid = lsp.util.open_floating_preview(contents, format, config)
    162 
    163    api.nvim_create_autocmd('WinClosed', {
    164      pattern = tostring(winid),
    165      once = true,
    166      callback = function()
    167        api.nvim_buf_clear_namespace(bufnr, hover_ns, 0, -1)
    168        return true
    169      end,
    170    })
    171  end)
    172 end
    173 
    174 local function request_with_opts(name, params, opts)
    175  local req_handler --- @type function?
    176  if opts then
    177    req_handler = function(err, result, ctx, config)
    178      local client = assert(lsp.get_client_by_id(ctx.client_id))
    179      local handler = client.handlers[name] or lsp.handlers[name]
    180      handler(err, result, ctx, vim.tbl_extend('force', config or {}, opts))
    181    end
    182  end
    183  lsp.buf_request(0, name, params, req_handler)
    184 end
    185 
    186 ---@param method vim.lsp.protocol.Method.ClientToServer.Request
    187 ---@param opts? vim.lsp.LocationOpts
    188 local function get_locations(method, opts)
    189  opts = opts or {}
    190  local bufnr = api.nvim_get_current_buf()
    191  local win = api.nvim_get_current_win()
    192 
    193  local clients = lsp.get_clients({ method = method, bufnr = bufnr })
    194  if not next(clients) then
    195    vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
    196    return
    197  end
    198 
    199  local from = vim.fn.getpos('.')
    200  from[1] = bufnr
    201  local tagname = vim.fn.expand('<cword>')
    202 
    203  lsp.buf_request_all(bufnr, method, function(client)
    204    return util.make_position_params(win, client.offset_encoding)
    205  end, function(results)
    206    ---@type vim.quickfix.entry[]
    207    local all_items = {}
    208 
    209    for client_id, res in pairs(results) do
    210      local client = assert(lsp.get_client_by_id(client_id))
    211      local locations = {}
    212      if res then
    213        locations = vim.islist(res.result) and res.result or { res.result }
    214      end
    215      local items = util.locations_to_items(locations, client.offset_encoding)
    216      vim.list_extend(all_items, items)
    217    end
    218 
    219    if vim.tbl_isempty(all_items) then
    220      vim.notify('No locations found', vim.log.levels.INFO)
    221      return
    222    end
    223 
    224    local title = 'LSP locations'
    225    if opts.on_list then
    226      assert(vim.is_callable(opts.on_list), 'on_list is not a function')
    227      opts.on_list({
    228        title = title,
    229        items = all_items,
    230        context = { bufnr = bufnr, method = method },
    231      })
    232      return
    233    end
    234 
    235    if #all_items == 1 then
    236      local item = all_items[1]
    237      local b = item.bufnr or vim.fn.bufadd(item.filename)
    238 
    239      -- Save position in jumplist
    240      vim.cmd("normal! m'")
    241      -- Push a new item into tagstack
    242      local tagstack = { { tagname = tagname, from = from } }
    243      vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't')
    244 
    245      vim.bo[b].buflisted = true
    246      local w = win
    247      if opts.reuse_win and api.nvim_win_get_buf(w) ~= b then
    248        w = vim.fn.bufwinid(b)
    249        w = w >= 0 and w or vim.fn.win_findbuf(b)[1] or win
    250        if w ~= win then
    251          api.nvim_set_current_win(w)
    252        end
    253      end
    254      api.nvim_win_set_buf(w, b)
    255      api.nvim_win_set_cursor(w, { item.lnum, item.col - 1 })
    256      vim._with({ win = w }, function()
    257        -- Open folds under the cursor
    258        vim.cmd('normal! zv')
    259      end)
    260      return
    261    end
    262    if opts.loclist then
    263      vim.fn.setloclist(0, {}, ' ', { title = title, items = all_items })
    264      vim.cmd.lopen()
    265    else
    266      vim.fn.setqflist({}, ' ', { title = title, items = all_items })
    267      vim.cmd('botright copen')
    268    end
    269  end)
    270 end
    271 
    272 --- @class vim.lsp.ListOpts
    273 ---
    274 --- list-handler replacing the default handler.
    275 --- Called for any non-empty result.
    276 --- This table can be used with |setqflist()| or |setloclist()|. E.g.:
    277 --- ```lua
    278 --- local function on_list(options)
    279 ---   vim.fn.setqflist({}, ' ', options)
    280 ---   vim.cmd.cfirst()
    281 --- end
    282 ---
    283 --- vim.lsp.buf.definition({ on_list = on_list })
    284 --- vim.lsp.buf.references(nil, { on_list = on_list })
    285 --- ```
    286 --- @field on_list? fun(t: vim.lsp.LocationOpts.OnList)
    287 ---
    288 --- Whether to use the |location-list| or the |quickfix| list in the default handler.
    289 --- ```lua
    290 --- vim.lsp.buf.definition({ loclist = true })
    291 --- vim.lsp.buf.references(nil, { loclist = false })
    292 --- ```
    293 --- @field loclist? boolean
    294 
    295 --- @class vim.lsp.LocationOpts.OnList
    296 --- @field items vim.quickfix.entry[] See |setqflist-what|
    297 --- @field title? string Title for the list.
    298 --- @field context? { bufnr: integer, method: string } Subset of `ctx` from |lsp-handler|.
    299 
    300 --- @class vim.lsp.LocationOpts: vim.lsp.ListOpts
    301 ---
    302 --- Jump to existing window if buffer is already open.
    303 --- @field reuse_win? boolean
    304 
    305 --- Jumps to the declaration of the symbol under the cursor.
    306 --- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
    307 --- @param opts? vim.lsp.LocationOpts
    308 function M.declaration(opts)
    309  validate('opts', opts, 'table', true)
    310  get_locations('textDocument/declaration', opts)
    311 end
    312 
    313 --- Jumps to the definition of the symbol under the cursor.
    314 --- @param opts? vim.lsp.LocationOpts
    315 function M.definition(opts)
    316  validate('opts', opts, 'table', true)
    317  get_locations('textDocument/definition', opts)
    318 end
    319 
    320 --- Jumps to the definition of the type of the symbol under the cursor.
    321 --- @param opts? vim.lsp.LocationOpts
    322 function M.type_definition(opts)
    323  validate('opts', opts, 'table', true)
    324  get_locations('textDocument/typeDefinition', opts)
    325 end
    326 
    327 --- Lists all the implementations for the symbol under the cursor in the
    328 --- quickfix window.
    329 --- @param opts? vim.lsp.LocationOpts
    330 function M.implementation(opts)
    331  validate('opts', opts, 'table', true)
    332  get_locations('textDocument/implementation', opts)
    333 end
    334 
    335 --- @param results table<integer,{err: lsp.ResponseError?, result: lsp.SignatureHelp?}>
    336 local function process_signature_help_results(results)
    337  local signatures = {} --- @type [vim.lsp.Client,lsp.SignatureInformation][]
    338  local active_signature = 1
    339 
    340  -- Pre-process results
    341  for client_id, r in pairs(results) do
    342    local err = r.err
    343    local client = assert(lsp.get_client_by_id(client_id))
    344    if err then
    345      vim.notify(
    346        client.name .. ': ' .. tostring(err.code) .. ': ' .. err.message,
    347        vim.log.levels.ERROR
    348      )
    349      api.nvim_command('redraw')
    350    else
    351      local result = r.result
    352      if result and result.signatures then
    353        for i, sig in ipairs(result.signatures) do
    354          sig.activeParameter = sig.activeParameter or result.activeParameter
    355          local idx = #signatures + 1
    356          if (result.activeSignature or 0) + 1 == i then
    357            active_signature = idx
    358          end
    359          signatures[idx] = { client, sig }
    360        end
    361      end
    362    end
    363  end
    364 
    365  return signatures, active_signature
    366 end
    367 
    368 local sig_help_ns = api.nvim_create_namespace('nvim.lsp.signature_help')
    369 
    370 --- @class vim.lsp.buf.signature_help.Opts : vim.lsp.util.open_floating_preview.Opts
    371 --- @field silent? boolean
    372 
    373 --- Displays signature information about the symbol under the cursor in a
    374 --- floating window. Allows cycling through signature overloads with `<C-s>`,
    375 --- which can be remapped via `<Plug>(nvim.lsp.ctrl-s)`
    376 ---
    377 --- Example:
    378 ---
    379 --- ```lua
    380 --- vim.keymap.set('n', '<C-b>', '<Plug>(nvim.lsp.ctrl-s)')
    381 --- ```
    382 ---
    383 --- @param config? vim.lsp.buf.signature_help.Opts
    384 function M.signature_help(config)
    385  validate('config', config, 'table', true)
    386 
    387  local method = 'textDocument/signatureHelp'
    388 
    389  config = config and vim.deepcopy(config) or {}
    390  config.focus_id = method
    391  local user_title = config.title
    392 
    393  lsp.buf_request_all(0, method, client_positional_params(), function(results, ctx)
    394    if api.nvim_get_current_buf() ~= ctx.bufnr then
    395      -- Ignore result since buffer changed. This happens for slow language servers.
    396      return
    397    end
    398 
    399    local signatures, active_signature = process_signature_help_results(results)
    400 
    401    if not next(signatures) then
    402      if config.silent ~= true then
    403        vim.notify('No signature help available', vim.log.levels.INFO)
    404      end
    405      return
    406    end
    407 
    408    local ft = vim.bo[ctx.bufnr].filetype
    409    local total = #signatures
    410    local can_cycle = total > 1 and config.focusable ~= false
    411    local idx = active_signature - 1
    412 
    413    --- @param update_win? integer
    414    local function show_signature(update_win)
    415      idx = (idx % total) + 1
    416      local client, result = signatures[idx][1], signatures[idx][2]
    417      --- @type string[]?
    418      local triggers =
    419        vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
    420      local lines, hl =
    421        util.convert_signature_help_to_markdown_lines({ signatures = { result } }, ft, triggers)
    422      if not lines then
    423        return
    424      end
    425 
    426      -- Show title only if there are multiple clients or multiple signatures.
    427      if total > 1 then
    428        local sfx = total > 1
    429            and string.format(' (%d/%d)%s', idx, total, can_cycle and ' (<C-s> to cycle)' or '')
    430          or ''
    431        config.title = user_title or string.format('Signature Help: %s%s', client.name, sfx)
    432        -- If no border is set, render title inside the window.
    433        if not (config.border or vim.o.winborder ~= '') then
    434          table.insert(lines, 1, '# ' .. config.title)
    435          if hl then
    436            hl[1] = hl[1] + 1
    437            hl[3] = hl[3] + 1
    438          end
    439        end
    440      end
    441 
    442      config._update_win = update_win
    443 
    444      local buf, win = util.open_floating_preview(lines, 'markdown', config)
    445 
    446      if hl then
    447        api.nvim_buf_clear_namespace(buf, sig_help_ns, 0, -1)
    448        vim.hl.range(
    449          buf,
    450          sig_help_ns,
    451          'LspSignatureActiveParameter',
    452          { hl[1], hl[2] },
    453          { hl[3], hl[4] }
    454        )
    455      end
    456      return buf, win
    457    end
    458 
    459    local fbuf, fwin = show_signature()
    460 
    461    if can_cycle then
    462      vim.keymap.set('n', '<Plug>(nvim.lsp.ctrl-s)', function()
    463        show_signature(fwin)
    464      end, {
    465        buffer = fbuf,
    466        desc = 'Cycle next signature',
    467      })
    468      if vim.fn.hasmapto('<Plug>(nvim.lsp.ctrl-s)', 'n') == 0 then
    469        vim.keymap.set('n', '<C-s>', '<Plug>(nvim.lsp.ctrl-s)', {
    470          buffer = fbuf,
    471          desc = 'Cycle next signature',
    472        })
    473      end
    474    end
    475  end)
    476 end
    477 
    478 --- @deprecated
    479 --- Retrieves the completion items at the current cursor position. Can only be
    480 --- called in Insert mode.
    481 ---
    482 ---@param context table (context support not yet implemented) Additional information
    483 --- about the context in which a completion was triggered (how it was triggered,
    484 --- and by which trigger character, if applicable)
    485 ---
    486 ---@see vim.lsp.protocol.CompletionTriggerKind
    487 function M.completion(context)
    488  validate('context', context, 'table', true)
    489  vim.deprecate('vim.lsp.buf.completion', 'vim.lsp.completion.trigger', '0.12')
    490  return lsp.buf_request(
    491    0,
    492    'textDocument/completion',
    493    client_positional_params({
    494      context = context,
    495    })
    496  )
    497 end
    498 
    499 ---@param bufnr integer
    500 ---@param mode "v"|"V"
    501 ---@return table {start={row,col}, end={row,col}} using (1, 0) indexing
    502 local function range_from_selection(bufnr, mode)
    503  -- TODO: Use `vim.fn.getregionpos()` instead.
    504 
    505  -- [bufnum, lnum, col, off]; both row and column 1-indexed
    506  local start = vim.fn.getpos('v')
    507  local end_ = vim.fn.getpos('.')
    508  local start_row = start[2]
    509  local start_col = start[3]
    510  local end_row = end_[2]
    511  local end_col = end_[3]
    512 
    513  -- A user can start visual selection at the end and move backwards
    514  -- Normalize the range to start < end
    515  if start_row == end_row and end_col < start_col then
    516    end_col, start_col = start_col, end_col --- @type integer, integer
    517  elseif end_row < start_row then
    518    start_row, end_row = end_row, start_row --- @type integer, integer
    519    start_col, end_col = end_col, start_col --- @type integer, integer
    520  end
    521  if mode == 'V' then
    522    start_col = 1
    523    local lines = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)
    524    end_col = #lines[1]
    525  end
    526  return {
    527    ['start'] = { start_row, start_col - 1 },
    528    ['end'] = { end_row, end_col - 1 },
    529  }
    530 end
    531 
    532 --- @class vim.lsp.buf.format.Opts
    533 --- @inlinedoc
    534 ---
    535 --- Can be used to specify FormattingOptions. Some unspecified options will be
    536 --- automatically derived from the current Nvim options.
    537 --- See https://microsoft.github.io/language-server-protocol/specification/#formattingOptions
    538 --- @field formatting_options? lsp.FormattingOptions
    539 ---
    540 --- Time in milliseconds to block for formatting requests. No effect if async=true.
    541 --- (default: `1000`)
    542 --- @field timeout_ms? integer
    543 ---
    544 --- Restrict formatting to the clients attached to the given buffer.
    545 --- (default: current buffer)
    546 --- @field bufnr? integer
    547 ---
    548 --- Predicate used to filter clients. Receives a client as argument and must
    549 --- return a boolean. Clients matching the predicate are included. Example:
    550 --- ```lua
    551 --- -- Never request typescript-language-server for formatting
    552 --- vim.lsp.buf.format {
    553 ---   filter = function(client) return client.name ~= "ts_ls" end
    554 --- }
    555 --- ```
    556 --- @field filter? fun(client: vim.lsp.Client): boolean?
    557 ---
    558 --- If true the method won't block.
    559 --- Editing the buffer while formatting asynchronous can lead to unexpected
    560 --- changes.
    561 --- (Default: false)
    562 --- @field async? boolean
    563 ---
    564 --- Restrict formatting to the client with ID (client.id) matching this field.
    565 --- @field id? integer
    566 ---
    567 --- Restrict formatting to the client with name (client.name) matching this field.
    568 --- @field name? string
    569 ---
    570 --- Range to format.
    571 --- Table must contain `start` and `end` keys with {row,col} tuples using
    572 --- (1,0) indexing.
    573 --- Can also be a list of tables that contain `start` and `end` keys as described above,
    574 --- in which case `textDocument/rangesFormatting` support is required.
    575 --- (Default: current selection in visual mode, `nil` in other modes,
    576 --- formatting the full buffer)
    577 --- @field range? {start:[integer,integer],end:[integer, integer]}|{start:[integer,integer],end:[integer,integer]}[]
    578 
    579 --- Formats a buffer using the attached (and optionally filtered) language
    580 --- server clients.
    581 ---
    582 --- @param opts? vim.lsp.buf.format.Opts
    583 function M.format(opts)
    584  validate('opts', opts, 'table', true)
    585 
    586  opts = opts or {}
    587  local bufnr = vim._resolve_bufnr(opts.bufnr)
    588  local mode = api.nvim_get_mode().mode
    589  local range = opts.range
    590  -- Try to use visual selection if no range is given
    591  if not range and mode == 'v' or mode == 'V' then
    592    range = range_from_selection(bufnr, mode)
    593  end
    594 
    595  local passed_multiple_ranges = (range and #range ~= 0 and type(range[1]) == 'table')
    596  local method ---@type vim.lsp.protocol.Method.ClientToServer.Request
    597  if passed_multiple_ranges then
    598    method = 'textDocument/rangesFormatting'
    599  elseif range then
    600    method = 'textDocument/rangeFormatting'
    601  else
    602    method = 'textDocument/formatting'
    603  end
    604 
    605  local clients = lsp.get_clients({
    606    id = opts.id,
    607    bufnr = bufnr,
    608    name = opts.name,
    609    method = method,
    610  })
    611  if opts.filter then
    612    clients = vim.tbl_filter(opts.filter, clients)
    613  end
    614 
    615  if #clients == 0 then
    616    vim.notify('[LSP] Format request failed, no matching language servers.')
    617  end
    618 
    619  --- @param client vim.lsp.Client
    620  --- @param params lsp.DocumentFormattingParams
    621  --- @return lsp.DocumentFormattingParams|lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams
    622  local function set_range(client, params)
    623    ---  @param r {start:[integer,integer],end:[integer, integer]}
    624    local function to_lsp_range(r)
    625      return util.make_given_range_params(r.start, r['end'], bufnr, client.offset_encoding).range
    626    end
    627 
    628    local ret = params --[[@as lsp.DocumentFormattingParams|lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams]]
    629    if passed_multiple_ranges then
    630      --- @cast range {start:[integer,integer],end:[integer, integer]}[]
    631      ret = params --[[@as lsp.DocumentRangesFormattingParams]]
    632      ret.ranges = vim.tbl_map(to_lsp_range, range)
    633    elseif range then
    634      --- @cast range {start:[integer,integer],end:[integer, integer]}
    635      ret = params --[[@as lsp.DocumentRangeFormattingParams]]
    636      ret.range = to_lsp_range(range)
    637    end
    638    return ret
    639  end
    640 
    641  if opts.async then
    642    --- @param idx? integer
    643    --- @param client? vim.lsp.Client
    644    local function do_format(idx, client)
    645      if not idx or not client then
    646        return
    647      end
    648      local params = set_range(client, util.make_formatting_params(opts.formatting_options))
    649      client:request(method, params, function(...)
    650        local handler = client.handlers[method] or lsp.handlers[method]
    651        handler(...)
    652        do_format(next(clients, idx))
    653      end, bufnr)
    654    end
    655    do_format(next(clients))
    656  else
    657    local timeout_ms = opts.timeout_ms or 1000
    658    for _, client in pairs(clients) do
    659      local params = set_range(client, util.make_formatting_params(opts.formatting_options))
    660      local result, err = client:request_sync(method, params, timeout_ms, bufnr)
    661      if result and result.result then
    662        util.apply_text_edits(result.result, bufnr, client.offset_encoding)
    663      elseif err then
    664        vim.notify(string.format('[LSP][%s] %s', client.name, err), vim.log.levels.WARN)
    665      end
    666    end
    667  end
    668 end
    669 
    670 --- @class vim.lsp.buf.rename.Opts
    671 --- @inlinedoc
    672 ---
    673 --- Predicate used to filter clients. Receives a client as argument and
    674 --- must return a boolean. Clients matching the predicate are included.
    675 --- @field filter? fun(client: vim.lsp.Client): boolean?
    676 ---
    677 --- Restrict clients used for rename to ones where client.name matches
    678 --- this field.
    679 --- @field name? string
    680 ---
    681 --- (default: current buffer)
    682 --- @field bufnr? integer
    683 
    684 --- Renames all references to the symbol under the cursor.
    685 ---
    686 ---@param new_name string|nil If not provided, the user will be prompted for a new
    687 ---                name using |vim.ui.input()|.
    688 ---@param opts? vim.lsp.buf.rename.Opts Additional options:
    689 function M.rename(new_name, opts)
    690  validate('new_name', new_name, 'string', true)
    691  validate('opts', opts, 'table', true)
    692 
    693  opts = opts or {}
    694  local bufnr = vim._resolve_bufnr(opts.bufnr)
    695  local clients = lsp.get_clients({
    696    bufnr = bufnr,
    697    name = opts.name,
    698    -- Clients must at least support rename, prepareRename is optional
    699    method = 'textDocument/rename',
    700  })
    701  if opts.filter then
    702    clients = vim.tbl_filter(opts.filter, clients)
    703  end
    704 
    705  if #clients == 0 then
    706    vim.notify('[LSP] Rename, no matching language servers with rename capability.')
    707  end
    708 
    709  local win = api.nvim_get_current_win()
    710 
    711  -- Compute early to account for cursor movements after going async
    712  local cword = vim.fn.expand('<cword>')
    713 
    714  --- @param range lsp.Range
    715  --- @param position_encoding 'utf-8'|'utf-16'|'utf-32'
    716  local function get_text_at_range(range, position_encoding)
    717    return api.nvim_buf_get_text(
    718      bufnr,
    719      range.start.line,
    720      util._get_line_byte_from_position(bufnr, range.start, position_encoding),
    721      range['end'].line,
    722      util._get_line_byte_from_position(bufnr, range['end'], position_encoding),
    723      {}
    724    )[1]
    725  end
    726 
    727  --- @param idx? integer
    728  --- @param client? vim.lsp.Client
    729  local function try_use_client(idx, client)
    730    if not idx or not client then
    731      return
    732    end
    733 
    734    --- @param name string
    735    local function rename(name)
    736      local params = util.make_position_params(win, client.offset_encoding) --[[@as lsp.RenameParams]]
    737      params.newName = name
    738      local handler = client.handlers['textDocument/rename'] or lsp.handlers['textDocument/rename']
    739      client:request('textDocument/rename', params, function(...)
    740        handler(...)
    741        try_use_client(next(clients, idx))
    742      end, bufnr)
    743    end
    744 
    745    if client:supports_method('textDocument/prepareRename') then
    746      local params = util.make_position_params(win, client.offset_encoding)
    747      ---@param result? lsp.Range|{ range: lsp.Range, placeholder: string }
    748      client:request('textDocument/prepareRename', params, function(err, result)
    749        if err or result == nil then
    750          if next(clients, idx) then
    751            try_use_client(next(clients, idx))
    752          else
    753            local msg = err and ('Error on prepareRename: ' .. (err.message or ''))
    754              or 'Nothing to rename'
    755            vim.notify(msg, vim.log.levels.INFO)
    756          end
    757          return
    758        end
    759 
    760        if new_name then
    761          rename(new_name)
    762          return
    763        end
    764 
    765        local range ---@type lsp.Range?
    766        if result.start then
    767          ---@cast result lsp.Range
    768          range = result
    769        elseif result.range then
    770          ---@cast result { range: lsp.Range, placeholder: string }
    771          range = result.range
    772        end
    773        if range then
    774          local start = range.start
    775          local end_ = range['end']
    776          local start_idx = util._get_line_byte_from_position(bufnr, start, client.offset_encoding)
    777          local end_idx = util._get_line_byte_from_position(bufnr, end_, client.offset_encoding)
    778 
    779          vim.hl.range(
    780            bufnr,
    781            rename_ns,
    782            'LspReferenceTarget',
    783            { start.line, start_idx },
    784            { end_.line, end_idx },
    785            { priority = vim.hl.priorities.user }
    786          )
    787        end
    788 
    789        local prompt_opts = {
    790          prompt = 'New Name: ',
    791        }
    792        if result.placeholder then
    793          prompt_opts.default = result.placeholder
    794        elseif result.start then
    795          prompt_opts.default = get_text_at_range(result, client.offset_encoding)
    796        elseif result.range then
    797          prompt_opts.default = get_text_at_range(result.range, client.offset_encoding)
    798        else
    799          prompt_opts.default = cword
    800        end
    801        vim.ui.input(prompt_opts, function(input)
    802          if input and #input ~= 0 then
    803            rename(input)
    804          end
    805          api.nvim_buf_clear_namespace(bufnr, rename_ns, 0, -1)
    806        end)
    807      end, bufnr)
    808    else
    809      assert(
    810        client:supports_method('textDocument/rename'),
    811        'Client must support textDocument/rename'
    812      )
    813      if new_name then
    814        rename(new_name)
    815        return
    816      end
    817 
    818      local prompt_opts = {
    819        prompt = 'New Name: ',
    820        default = cword,
    821      }
    822      vim.ui.input(prompt_opts, function(input)
    823        if not input or #input == 0 then
    824          return
    825        end
    826        rename(input)
    827      end)
    828    end
    829  end
    830 
    831  try_use_client(next(clients))
    832 end
    833 
    834 --- Lists all the references to the symbol under the cursor in the quickfix window.
    835 ---
    836 ---@param context lsp.ReferenceContext? Context for the request
    837 ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
    838 ---@param opts? vim.lsp.ListOpts
    839 function M.references(context, opts)
    840  validate('context', context, 'table', true)
    841  validate('opts', opts, 'table', true)
    842 
    843  local bufnr = api.nvim_get_current_buf()
    844  local win = api.nvim_get_current_win()
    845  opts = opts or {}
    846 
    847  lsp.buf_request_all(bufnr, 'textDocument/references', function(client)
    848    local params = util.make_position_params(win, client.offset_encoding)
    849    ---@diagnostic disable-next-line: inject-field
    850    params.context = context or { includeDeclaration = true }
    851    return params
    852  end, function(results)
    853    local all_items = {}
    854    local title = 'References'
    855 
    856    for client_id, res in pairs(results) do
    857      local client = assert(lsp.get_client_by_id(client_id))
    858      local items = util.locations_to_items(res.result or {}, client.offset_encoding)
    859      vim.list_extend(all_items, items)
    860    end
    861 
    862    if not next(all_items) then
    863      vim.notify('No references found')
    864    else
    865      local list = {
    866        title = title,
    867        items = all_items,
    868        context = {
    869          method = 'textDocument/references',
    870          bufnr = bufnr,
    871        },
    872      }
    873      if opts.on_list then
    874        assert(vim.is_callable(opts.on_list), 'on_list is not a function')
    875        opts.on_list(list)
    876      elseif opts.loclist then
    877        vim.fn.setloclist(0, {}, ' ', list)
    878        vim.cmd.lopen()
    879      else
    880        vim.fn.setqflist({}, ' ', list)
    881        vim.cmd('botright copen')
    882      end
    883    end
    884  end)
    885 end
    886 
    887 --- Lists all symbols in the current buffer in the |location-list|.
    888 --- @param opts? vim.lsp.ListOpts
    889 function M.document_symbol(opts)
    890  validate('opts', opts, 'table', true)
    891  opts = vim.tbl_deep_extend('keep', opts or {}, { loclist = true })
    892  local params = { textDocument = util.make_text_document_params() }
    893  request_with_opts('textDocument/documentSymbol', params, opts)
    894 end
    895 
    896 --- @param client_id integer
    897 --- @param method vim.lsp.protocol.Method.ClientToServer.Request
    898 --- @param params table
    899 --- @param handler? lsp.Handler
    900 --- @param bufnr? integer
    901 local function request_with_id(client_id, method, params, handler, bufnr)
    902  local client = lsp.get_client_by_id(client_id)
    903  if not client then
    904    vim.notify(
    905      string.format('Client with id=%d disappeared during hierarchy request', client_id),
    906      vim.log.levels.WARN
    907    )
    908    return
    909  end
    910  client:request(method, params, handler, bufnr)
    911 end
    912 
    913 --- @param item lsp.TypeHierarchyItem|lsp.CallHierarchyItem
    914 local function format_hierarchy_item(item)
    915  if not item.detail or #item.detail == 0 then
    916    return item.name
    917  end
    918  return string.format('%s %s', item.name, item.detail)
    919 end
    920 
    921 --- @alias vim.lsp.buf.HierarchyMethod
    922 --- | 'typeHierarchy/subtypes'
    923 --- | 'typeHierarchy/supertypes'
    924 --- | 'callHierarchy/incomingCalls'
    925 --- | 'callHierarchy/outgoingCalls'
    926 
    927 --- @type table<vim.lsp.buf.HierarchyMethod, 'type' | 'call'>
    928 local hierarchy_methods = {
    929  ['typeHierarchy/subtypes'] = 'type',
    930  ['typeHierarchy/supertypes'] = 'type',
    931  ['callHierarchy/incomingCalls'] = 'call',
    932  ['callHierarchy/outgoingCalls'] = 'call',
    933 }
    934 
    935 --- @param method vim.lsp.buf.HierarchyMethod
    936 local function hierarchy(method)
    937  local kind = hierarchy_methods[method]
    938 
    939  local prepare_method = kind == 'type' and 'textDocument/prepareTypeHierarchy'
    940    or 'textDocument/prepareCallHierarchy'
    941 
    942  local bufnr = api.nvim_get_current_buf()
    943  local clients = lsp.get_clients({ bufnr = bufnr, method = prepare_method })
    944  if not next(clients) then
    945    vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
    946    return
    947  end
    948 
    949  local win = api.nvim_get_current_win()
    950 
    951  lsp.buf_request_all(bufnr, prepare_method, function(client)
    952    return util.make_position_params(win, client.offset_encoding)
    953  end, function(req_results)
    954    local results = {} --- @type [integer, lsp.TypeHierarchyItem|lsp.CallHierarchyItem][]
    955    for client_id, res in pairs(req_results) do
    956      if res.err then
    957        vim.notify(res.err.message, vim.log.levels.WARN)
    958      elseif res.result then
    959        local result = res.result --- @type lsp.TypeHierarchyItem[]|lsp.CallHierarchyItem[]
    960        for _, item in ipairs(result) do
    961          results[#results + 1] = { client_id, item }
    962        end
    963      end
    964    end
    965 
    966    if #results == 0 then
    967      vim.notify('No item resolved', vim.log.levels.WARN)
    968    elseif #results == 1 then
    969      local client_id, item = results[1][1], results[1][2]
    970      request_with_id(client_id, method, { item = item }, nil, bufnr)
    971    else
    972      vim.ui.select(results, {
    973        prompt = string.format('Select a %s hierarchy item:', kind),
    974        kind = kind .. 'hierarchy',
    975        format_item = function(x)
    976          return format_hierarchy_item(x[2])
    977        end,
    978      }, function(x)
    979        if x then
    980          local client_id, item = x[1], x[2]
    981          request_with_id(client_id, method, { item = item }, nil, bufnr)
    982        end
    983      end)
    984    end
    985  end)
    986 end
    987 
    988 --- Lists all the call sites of the symbol under the cursor in the
    989 --- |quickfix| window. If the symbol can resolve to multiple
    990 --- items, the user can pick one in the |inputlist()|.
    991 function M.incoming_calls()
    992  hierarchy('callHierarchy/incomingCalls')
    993 end
    994 
    995 --- Lists all the items that are called by the symbol under the
    996 --- cursor in the |quickfix| window. If the symbol can resolve to
    997 --- multiple items, the user can pick one in the |inputlist()|.
    998 function M.outgoing_calls()
    999  hierarchy('callHierarchy/outgoingCalls')
   1000 end
   1001 
   1002 --- Lists all the subtypes or supertypes of the symbol under the
   1003 --- cursor in the |quickfix| window. If the symbol can resolve to
   1004 --- multiple items, the user can pick one using |vim.ui.select()|.
   1005 ---@param kind "subtypes"|"supertypes"
   1006 function M.typehierarchy(kind)
   1007  validate('kind', kind, function(v)
   1008    return v == 'subtypes' or v == 'supertypes'
   1009  end)
   1010 
   1011  local method = kind == 'subtypes' and 'typeHierarchy/subtypes' or 'typeHierarchy/supertypes'
   1012  hierarchy(method)
   1013 end
   1014 
   1015 --- List workspace folders.
   1016 ---
   1017 function M.list_workspace_folders()
   1018  local workspace_folders = {}
   1019  for _, client in pairs(lsp.get_clients({ bufnr = 0 })) do
   1020    for _, folder in pairs(client.workspace_folders or {}) do
   1021      table.insert(workspace_folders, folder.name)
   1022    end
   1023  end
   1024  return workspace_folders
   1025 end
   1026 
   1027 --- Add the folder at path to the workspace folders. If {path} is
   1028 --- not provided, the user will be prompted for a path using |input()|.
   1029 --- @param workspace_folder? string
   1030 function M.add_workspace_folder(workspace_folder)
   1031  validate('workspace_folder', workspace_folder, 'string', true)
   1032 
   1033  workspace_folder = workspace_folder
   1034    or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'), 'dir')
   1035  api.nvim_command('redraw')
   1036  if not (workspace_folder and #workspace_folder > 0) then
   1037    return
   1038  end
   1039  if vim.fn.isdirectory(workspace_folder) == 0 then
   1040    vim.notify(workspace_folder .. ' is not a valid directory')
   1041    return
   1042  end
   1043  local bufnr = api.nvim_get_current_buf()
   1044  for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
   1045    client:_add_workspace_folder(workspace_folder)
   1046  end
   1047 end
   1048 
   1049 --- Remove the folder at path from the workspace folders. If
   1050 --- {path} is not provided, the user will be prompted for
   1051 --- a path using |input()|.
   1052 --- @param workspace_folder? string
   1053 function M.remove_workspace_folder(workspace_folder)
   1054  validate('workspace_folder', workspace_folder, 'string', true)
   1055 
   1056  workspace_folder = workspace_folder
   1057    or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'))
   1058  api.nvim_command('redraw')
   1059  if not workspace_folder or #workspace_folder == 0 then
   1060    return
   1061  end
   1062  local bufnr = api.nvim_get_current_buf()
   1063  for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
   1064    client:_remove_workspace_folder(workspace_folder)
   1065  end
   1066  vim.notify(workspace_folder .. 'is not currently part of the workspace')
   1067 end
   1068 
   1069 --- Lists all symbols in the current workspace in the quickfix window.
   1070 ---
   1071 --- The list is filtered against {query}; if the argument is omitted from the
   1072 --- call, the user is prompted to enter a string on the command line. An empty
   1073 --- string means no filtering is done.
   1074 ---
   1075 --- @param query string? optional
   1076 --- @param opts? vim.lsp.ListOpts
   1077 function M.workspace_symbol(query, opts)
   1078  validate('query', query, 'string', true)
   1079  validate('opts', opts, 'table', true)
   1080 
   1081  query = query or npcall(vim.fn.input, 'Query: ')
   1082  if query == nil then
   1083    return
   1084  end
   1085  local params = { query = query }
   1086  request_with_opts('workspace/symbol', params, opts)
   1087 end
   1088 
   1089 --- @class vim.lsp.WorkspaceDiagnosticsOpts
   1090 --- @inlinedoc
   1091 ---
   1092 --- Only request diagnostics from the indicated client. If nil, the request is sent to all clients.
   1093 --- @field client_id? integer
   1094 
   1095 --- Request workspace-wide diagnostics.
   1096 --- @param opts? vim.lsp.WorkspaceDiagnosticsOpts
   1097 --- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_dagnostics
   1098 function M.workspace_diagnostics(opts)
   1099  validate('opts', opts, 'table', true)
   1100 
   1101  lsp.diagnostic._workspace_diagnostics(opts or {})
   1102 end
   1103 
   1104 --- Send request to the server to resolve document highlights for the current
   1105 --- text document position. This request can be triggered by a  key mapping or
   1106 --- by events such as `CursorHold`, e.g.:
   1107 ---
   1108 --- ```vim
   1109 --- autocmd CursorHold  <buffer> lua vim.lsp.buf.document_highlight()
   1110 --- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()
   1111 --- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
   1112 --- ```
   1113 ---
   1114 --- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups
   1115 ---       to be defined or you won't be able to see the actual highlights.
   1116 ---         |hl-LspReferenceText|
   1117 ---         |hl-LspReferenceRead|
   1118 ---         |hl-LspReferenceWrite|
   1119 function M.document_highlight()
   1120  lsp.buf_request(0, 'textDocument/documentHighlight', client_positional_params())
   1121 end
   1122 
   1123 --- Removes document highlights from current buffer.
   1124 function M.clear_references()
   1125  util.buf_clear_references()
   1126 end
   1127 
   1128 ---@nodoc
   1129 ---@class vim.lsp.CodeActionResultEntry
   1130 ---@field err? lsp.ResponseError
   1131 ---@field result? (lsp.Command|lsp.CodeAction)[]
   1132 ---@field context lsp.HandlerContext
   1133 
   1134 --- @class vim.lsp.buf.code_action.Opts
   1135 --- @inlinedoc
   1136 ---
   1137 --- Corresponds to `CodeActionContext` of the LSP specification:
   1138 ---   - {diagnostics}? (`table`) LSP `Diagnostic[]`. Inferred from the current
   1139 ---     position if not provided.
   1140 ---   - {only}? (`table`) List of LSP `CodeActionKind`s used to filter the code actions.
   1141 ---     Most language servers support values like `refactor`
   1142 ---     or `quickfix`.
   1143 ---   - {triggerKind}? (`integer`) The reason why code actions were requested.
   1144 --- @field context? lsp.CodeActionContext
   1145 ---
   1146 --- Predicate taking a code action or command and the provider's ID.
   1147 --- If it returns false, the action is filtered out.
   1148 --- @field filter? fun(x: lsp.CodeAction|lsp.Command, client_id: integer):boolean
   1149 ---
   1150 --- When set to `true`, and there is just one remaining action
   1151 --- (after filtering), the action is applied without user query.
   1152 --- @field apply? boolean
   1153 ---
   1154 --- Range for which code actions should be requested.
   1155 --- If in visual mode this defaults to the active selection.
   1156 --- Table must contain `start` and `end` keys with {row,col} tuples
   1157 --- using mark-like indexing. See |api-indexing|
   1158 --- @field range? {start: integer[], end: integer[]}
   1159 
   1160 --- This is not public because the main extension point is
   1161 --- vim.ui.select which can be overridden independently.
   1162 ---
   1163 --- Can't call/use vim.lsp.handlers['textDocument/codeAction'] because it expects
   1164 --- `(err, CodeAction[] | Command[], ctx)`, but we want to aggregate the results
   1165 --- from multiple clients to have 1 single UI prompt for the user, yet we still
   1166 --- need to be able to link a `CodeAction|Command` to the right client for
   1167 --- `codeAction/resolve`
   1168 ---@param results table<integer, vim.lsp.CodeActionResultEntry>
   1169 ---@param opts? vim.lsp.buf.code_action.Opts
   1170 local function on_code_action_results(results, opts)
   1171  ---@param a lsp.Command|lsp.CodeAction
   1172  ---@param client_id integer
   1173  local function action_filter(a, client_id)
   1174    -- filter by specified action kind
   1175    if opts and opts.context then
   1176      if opts.context.only then
   1177        if not a.kind then
   1178          return false
   1179        end
   1180        local found = false
   1181        for _, o in ipairs(opts.context.only) do
   1182          -- action kinds are hierarchical with . as a separator: when requesting only 'type-annotate'
   1183          -- this filter allows both 'type-annotate' and 'type-annotate.foo', for example
   1184          if a.kind == o or vim.startswith(a.kind, o .. '.') then
   1185            found = true
   1186            break
   1187          end
   1188        end
   1189        if not found then
   1190          return false
   1191        end
   1192      end
   1193      -- Only show disabled code actions when the trigger kind is "Invoked".
   1194      if a.disabled and opts.context.triggerKind ~= lsp.protocol.CodeActionTriggerKind.Invoked then
   1195        return false
   1196      end
   1197    end
   1198    -- filter by user function
   1199    if opts and opts.filter and not opts.filter(a, client_id) then
   1200      return false
   1201    end
   1202    -- no filter removed this action
   1203    return true
   1204  end
   1205 
   1206  ---@type {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}[]
   1207  local actions = {}
   1208  for _, result in pairs(results) do
   1209    for _, action in pairs(result.result or {}) do
   1210      if action_filter(action, result.context.client_id) then
   1211        table.insert(actions, { action = action, ctx = result.context })
   1212      end
   1213    end
   1214  end
   1215  if #actions == 0 then
   1216    vim.notify('No code actions available', vim.log.levels.INFO)
   1217    return
   1218  end
   1219 
   1220  ---@param action lsp.Command|lsp.CodeAction
   1221  ---@param client vim.lsp.Client
   1222  ---@param ctx lsp.HandlerContext
   1223  local function apply_action(action, client, ctx)
   1224    if action.edit then
   1225      util.apply_workspace_edit(action.edit, client.offset_encoding)
   1226    end
   1227    local a_cmd = action.command
   1228    if a_cmd then
   1229      local command = type(a_cmd) == 'table' and a_cmd or action
   1230      --- @cast command lsp.Command
   1231      client:exec_cmd(command, ctx)
   1232    end
   1233  end
   1234 
   1235  ---@param choice {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}
   1236  local function on_user_choice(choice)
   1237    if not choice then
   1238      return
   1239    end
   1240 
   1241    -- textDocument/codeAction can return either Command[] or CodeAction[]
   1242    --
   1243    -- CodeAction
   1244    --  ...
   1245    --  edit?: WorkspaceEdit    -- <- must be applied before command
   1246    --  command?: Command
   1247    --
   1248    -- Command:
   1249    --  title: string
   1250    --  command: string
   1251    --  arguments?: any[]
   1252 
   1253    local client = assert(lsp.get_client_by_id(choice.ctx.client_id))
   1254    local action = choice.action
   1255    local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number')
   1256 
   1257    -- Only code actions are resolved, so if we have a command, just apply it.
   1258    if type(action.title) == 'string' and type(action.command) == 'string' then
   1259      apply_action(action, client, choice.ctx)
   1260      return
   1261    end
   1262 
   1263    if action.disabled then
   1264      vim.notify(action.disabled.reason, vim.log.levels.ERROR)
   1265      return
   1266    end
   1267 
   1268    if not (action.edit and action.command) and client:supports_method('codeAction/resolve') then
   1269      client:request('codeAction/resolve', action, function(err, resolved_action)
   1270        if err then
   1271          -- If resolve fails, try to apply the edit/command from the original code action.
   1272          if action.edit or action.command then
   1273            apply_action(action, client, choice.ctx)
   1274          else
   1275            vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
   1276          end
   1277        else
   1278          apply_action(resolved_action, client, choice.ctx)
   1279        end
   1280      end, bufnr)
   1281    else
   1282      apply_action(action, client, choice.ctx)
   1283    end
   1284  end
   1285 
   1286  -- If options.apply is given, and there are just one remaining code action,
   1287  -- apply it directly without querying the user.
   1288  if opts and opts.apply and #actions == 1 then
   1289    on_user_choice(actions[1])
   1290    return
   1291  end
   1292 
   1293  ---@param item {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}
   1294  local function format_item(item)
   1295    local clients = lsp.get_clients({ bufnr = item.ctx.bufnr })
   1296    local title = item.action.title:gsub('\r\n', '\\r\\n'):gsub('\n', '\\n')
   1297 
   1298    if item.action.disabled then
   1299      title = title .. ' (disabled)'
   1300    end
   1301 
   1302    if #clients == 1 then
   1303      return title
   1304    end
   1305 
   1306    local source = assert(lsp.get_client_by_id(item.ctx.client_id)).name
   1307    return ('%s [%s]'):format(title, source)
   1308  end
   1309 
   1310  local select_opts = {
   1311    prompt = 'Code actions:',
   1312    kind = 'codeaction',
   1313    format_item = format_item,
   1314  }
   1315  vim.ui.select(actions, select_opts, on_user_choice)
   1316 end
   1317 
   1318 --- Selects a code action (LSP: "textDocument/codeAction" request) available at cursor position.
   1319 ---
   1320 ---@param opts? vim.lsp.buf.code_action.Opts
   1321 ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
   1322 ---@see vim.lsp.protocol.CodeActionTriggerKind
   1323 function M.code_action(opts)
   1324  validate('options', opts, 'table', true)
   1325  opts = opts or {}
   1326  -- Detect old API call code_action(context) which should now be
   1327  -- code_action({ context = context} )
   1328  --- @diagnostic disable-next-line:undefined-field
   1329  if opts.diagnostics or opts.only then
   1330    opts = { options = opts }
   1331  end
   1332  local context = opts.context and vim.deepcopy(opts.context) or {}
   1333  if not context.triggerKind then
   1334    context.triggerKind = lsp.protocol.CodeActionTriggerKind.Invoked
   1335  end
   1336  local mode = api.nvim_get_mode().mode
   1337  local bufnr = api.nvim_get_current_buf()
   1338  local win = api.nvim_get_current_win()
   1339  local clients = lsp.get_clients({ bufnr = bufnr, method = 'textDocument/codeAction' })
   1340  if not next(clients) then
   1341    vim.notify(lsp._unsupported_method('textDocument/codeAction'), vim.log.levels.WARN)
   1342    return
   1343  end
   1344 
   1345  lsp.buf_request_all(bufnr, 'textDocument/codeAction', function(client)
   1346    ---@type lsp.CodeActionParams
   1347    local params
   1348 
   1349    if opts.range then
   1350      assert(type(opts.range) == 'table', 'code_action range must be a table')
   1351      local start = assert(opts.range.start, 'range must have a `start` property')
   1352      local end_ = assert(opts.range['end'], 'range must have a `end` property')
   1353      params = util.make_given_range_params(start, end_, bufnr, client.offset_encoding)
   1354    elseif mode == 'v' or mode == 'V' then
   1355      local range = range_from_selection(bufnr, mode)
   1356      params =
   1357        util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding)
   1358    else
   1359      params = util.make_range_params(win, client.offset_encoding)
   1360    end
   1361 
   1362    --- @cast params lsp.CodeActionParams
   1363 
   1364    if context.diagnostics then
   1365      params.context = context
   1366    else
   1367      local ns_push = lsp.diagnostic.get_namespace(client.id, false)
   1368      local ns_pull = lsp.diagnostic.get_namespace(client.id, true)
   1369      local diagnostics = {}
   1370      local lnum = api.nvim_win_get_cursor(0)[1] - 1
   1371      vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_pull, lnum = lnum }))
   1372      vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_push, lnum = lnum }))
   1373      params.context = vim.tbl_extend('force', context, {
   1374        ---@diagnostic disable-next-line: no-unknown
   1375        diagnostics = vim.tbl_map(function(d)
   1376          return d.user_data.lsp
   1377        end, diagnostics),
   1378      })
   1379    end
   1380 
   1381    return params
   1382  end, function(results)
   1383    on_code_action_results(results, opts)
   1384  end)
   1385 end
   1386 
   1387 --- @deprecated
   1388 --- Executes an LSP server command.
   1389 --- @param command_params lsp.ExecuteCommandParams
   1390 --- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
   1391 function M.execute_command(command_params)
   1392  validate('command', command_params.command, 'string')
   1393  validate('arguments', command_params.arguments, 'table', true)
   1394  vim.deprecate('execute_command', 'client:exec_cmd', '0.12')
   1395  command_params = {
   1396    command = command_params.command,
   1397    arguments = command_params.arguments,
   1398    workDoneToken = command_params.workDoneToken,
   1399  }
   1400  lsp.buf_request(0, 'workspace/executeCommand', command_params)
   1401 end
   1402 
   1403 ---@type { index: integer, ranges: lsp.Range[] }?
   1404 local selection_ranges = nil
   1405 
   1406 ---@param range lsp.Range
   1407 local function select_range(range)
   1408  local start_line = range.start.line + 1
   1409  local end_line = range['end'].line + 1
   1410 
   1411  local start_col = range.start.character
   1412  local end_col = range['end'].character
   1413 
   1414  -- If the selection ends at column 0, adjust the position to the end of the previous line.
   1415  if end_col == 0 then
   1416    end_line = end_line - 1
   1417    local end_line_text = api.nvim_buf_get_lines(0, end_line - 1, end_line, true)[1]
   1418    end_col = #end_line_text
   1419  end
   1420 
   1421  vim.fn.setpos("'<", { 0, start_line, start_col + 1, 0 })
   1422  vim.fn.setpos("'>", { 0, end_line, end_col, 0 })
   1423  vim.cmd.normal({ 'gv', bang = true })
   1424 end
   1425 
   1426 ---@param range lsp.Range
   1427 local function is_empty(range)
   1428  return range.start.line == range['end'].line and range.start.character == range['end'].character
   1429 end
   1430 
   1431 --- Perform an incremental selection at the cursor position based on ranges given by the LSP. The
   1432 --- `direction` parameter specifies the number of times to expand the selection. Negative values
   1433 --- will shrink the selection.
   1434 ---
   1435 --- @param direction integer
   1436 --- @param timeout_ms integer? (default: `1000`) Maximum time (milliseconds) to wait for a result.
   1437 function M.selection_range(direction, timeout_ms)
   1438  validate('direction', direction, 'number')
   1439  validate('timeout_ms', timeout_ms, 'number', true)
   1440 
   1441  if selection_ranges then
   1442    local new_index = selection_ranges.index + direction
   1443    selection_ranges.index = math.min(#selection_ranges.ranges, math.max(1, new_index))
   1444 
   1445    select_range(selection_ranges.ranges[selection_ranges.index])
   1446    return
   1447  end
   1448 
   1449  local method = 'textDocument/selectionRange'
   1450  local client = lsp.get_clients({ method = method, bufnr = 0 })[1]
   1451  if not client then
   1452    vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
   1453    return
   1454  end
   1455 
   1456  local position_params = util.make_position_params(0, client.offset_encoding)
   1457 
   1458  ---@type lsp.SelectionRangeParams
   1459  local params = {
   1460    textDocument = position_params.textDocument,
   1461    positions = { position_params.position },
   1462  }
   1463 
   1464  timeout_ms = timeout_ms or 1000
   1465  local result, err = lsp.buf_request_sync(0, method, params, timeout_ms)
   1466  if err then
   1467    lsp.log.error('selectionRange request failed: ' .. err)
   1468    return
   1469  end
   1470  if not result or not result[client.id] or not result[client.id].result then
   1471    return
   1472  end
   1473  if result[client.id].error then
   1474    lsp.log.error(result[client.id].error.code, result[client.id].error.message)
   1475  end
   1476 
   1477  -- We only requested one range, thus we get the first and only response here.
   1478  local response = assert(result[client.id].result[1]) ---@type lsp.SelectionRange
   1479  local ranges = {} ---@type lsp.Range[]
   1480  local lines = api.nvim_buf_get_lines(0, 0, -1, false)
   1481 
   1482  -- Populate the list of ranges from the given request.
   1483  while response do
   1484    local range = response.range
   1485    if not is_empty(range) then
   1486      local start_line = range.start.line
   1487      local end_line = range['end'].line
   1488      range.start.character = vim.str_byteindex(
   1489        lines[start_line + 1] or '',
   1490        client.offset_encoding,
   1491        range.start.character,
   1492        false
   1493      )
   1494      range['end'].character = vim.str_byteindex(
   1495        lines[end_line + 1] or '',
   1496        client.offset_encoding,
   1497        range['end'].character,
   1498        false
   1499      )
   1500      ranges[#ranges + 1] = range
   1501    end
   1502    response = response.parent
   1503  end
   1504 
   1505  -- Clear selection ranges when leaving visual mode.
   1506  api.nvim_create_autocmd('ModeChanged', {
   1507    once = true,
   1508    pattern = 'v*:*',
   1509    callback = function()
   1510      selection_ranges = nil
   1511    end,
   1512  })
   1513 
   1514  if #ranges > 0 then
   1515    local index = math.min(#ranges, math.max(1, direction))
   1516    selection_ranges = { index = index, ranges = ranges }
   1517    select_range(ranges[index])
   1518  end
   1519 end
   1520 
   1521 return M