neovim

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

handlers.lua (26128B)


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