Files
neovim/runtime/lua/vim/lsp/handlers.lua
Tim Pope a1895f024a fix(lsp): support workspace/configuation with no section #27510
The [spec for `workspace/configuration`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration)
marks the `section` property of each item in `items` optional.
Therefore, I believe it violates the spec to skip over items without
a section, because then the length of the results won't match the length
of a valid `items` input. The spec does not elaborate on _what_ to
return in this case, but I don't think it would be controversial to say
that returning the full configuration, as done for an empty string, is
the most natural interpretation.

That empty string case, by the way, was initially [added in
response](5da124fc82)
to a real world implementation requesting it. I don't have a similar
real world implementation to point to for the omitted `section`, but
I would note that `getConfiguration()` from `vscode-languageserver-node`
[defaults to a request with no section](d859bb14d1/server/src/common/configuration.ts (L24-L26))
when called with no arguments. I surmise that this is intended as a way
to retrieve the full configuration.
2026-02-15 11:37:24 -05:00

715 lines
26 KiB
Lua

local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
local util = require('vim.lsp.util')
local api = vim.api
local completion = require('vim.lsp.completion')
--- @type table<string, lsp.Handler>
local M = {}
--- @deprecated
--- Client to server response handlers.
--- @type table<vim.lsp.protocol.Method.ClientToServer, lsp.Handler>
local RCS = {}
--- Server to client request handlers.
--- @type table<vim.lsp.protocol.Method.ServerToClient, lsp.Handler>
local RSC = {}
--- Server to client notification handlers.
--- @type table<vim.lsp.protocol.Method.ServerToClient, lsp.Handler>
local NSC = {}
--- Writes to error buffer.
---@param ... string Will be concatenated before being written
local function err_message(...)
vim.notify(table.concat(vim.iter({ ... }):flatten():totable()), vim.log.levels.ERROR)
api.nvim_command('redraw')
end
--- @param params lsp.ShowMessageParams|lsp.ShowMessageRequestParams
--- @param ctx lsp.HandlerContext
local function show_message_notification(params, ctx)
local message_type = params.type
local message = params.message
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
err_message('LSP[', client_name, '] client has shut down after sending ', message)
end
message = ('LSP[%s] %s'):format(client_name, message)
vim.notify(message, log._from_lsp_level(message_type))
return params
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
RCS['workspace/executeCommand'] = function(_, _, _)
-- Error handling is done implicitly by wrapping all handlers; see end of this file
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
---@param params lsp.ProgressParams
---@param ctx lsp.HandlerContext
---@diagnostic disable-next-line:no-unknown
RSC['$/progress'] = function(_, params, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
local kind = nil
local value = params.value
if type(value) == 'table' then
kind = value.kind --- @type string
-- Carry over title of `begin` messages to `report` and `end` messages
-- So that consumers always have it available, even if they consume a
-- subset of the full sequence
if kind == 'begin' then
client.progress.pending[params.token] = value.title
else
value.title = client.progress.pending[params.token]
if kind == 'end' then
client.progress.pending[params.token] = nil
end
end
end
client.progress:push(params)
api.nvim_exec_autocmds('LspProgress', {
pattern = kind,
modeline = false,
data = { client_id = ctx.client_id, params = params },
})
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
---@param params lsp.WorkDoneProgressCreateParams
---@param ctx lsp.HandlerContext
RSC['window/workDoneProgress/create'] = function(_, params, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
client.progress:push(params)
return vim.NIL
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
---@param params lsp.ShowMessageRequestParams
RSC['window/showMessageRequest'] = function(_, params, ctx)
if next(params.actions or {}) then
local co, is_main = coroutine.running()
if co and not is_main then
local opts = {
kind = 'lsp_message',
prompt = params.message .. ': ',
---@param action lsp.MessageActionItem
---@return string
format_item = function(action)
return (action.title:gsub('\r\n', '\\r\\n'):gsub('\n', '\\n'))
end,
}
vim.ui.select(params.actions, opts, function(choice)
-- schedule to ensure resume doesn't happen _before_ yield with
-- default synchronous vim.ui.select
vim.schedule(function()
coroutine.resume(co, choice or vim.NIL)
end)
end)
return coroutine.yield()
else
local option_strings = { params.message, '\nRequest Actions:' }
for i, action in ipairs(params.actions) do
local title = action.title:gsub('\r\n', '\\r\\n')
title = title:gsub('\n', '\\n')
table.insert(option_strings, string.format('%d. %s', i, title))
end
local choice = vim.fn.inputlist(option_strings)
if choice < 1 or choice > #params.actions then
return vim.NIL
else
return params.actions[choice]
end
end
else
-- No actions, just show the message.
show_message_notification(params, ctx)
return vim.NIL
end
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
--- @param params lsp.RegistrationParams
RSC['client/registerCapability'] = function(_, params, ctx)
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
client:_register(params.registrations)
for bufnr in pairs(client.attached_buffers) do
vim.lsp._set_defaults(client, bufnr)
end
for _, reg in ipairs(params.registrations) do
if reg.method == 'textDocument/documentColor' then
for bufnr in pairs(client.attached_buffers) do
if vim.lsp.document_color.is_enabled(bufnr) then
vim.lsp.document_color._buf_refresh(bufnr, client.id)
end
end
end
if reg.method == 'textDocument/diagnostic' then
for bufnr in pairs(client.attached_buffers) do
vim.lsp.diagnostic._refresh(bufnr, client.id)
end
end
end
return vim.NIL
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
--- @param params lsp.UnregistrationParams
RSC['client/unregisterCapability'] = function(_, params, ctx)
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
client:_unregister(params.unregisterations)
return vim.NIL
end
-- TODO(lewis6991): Do we need to notify other servers?
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
RSC['workspace/applyEdit'] = function(_, params, ctx)
assert(
params,
'workspace/applyEdit must be called with `ApplyWorkspaceEditParams`. Server is violating the specification'
)
-- TODO(ashkan) Do something more with label?
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
if params.label then
print('Workspace edit', params.label)
end
local status, result = pcall(util.apply_workspace_edit, params.edit, client.offset_encoding)
return {
applied = status,
failureReason = result,
}
end
---@param table table e.g., { foo = { bar = "z" } }
---@param section string indicating the field of the table, e.g., "foo.bar"
---@return any|nil setting value read from the table, or `nil` not found
local function lookup_section(table, section)
local keys = vim.split(section, '.', { plain = true }) --- @type string[]
return vim.tbl_get(table, unpack(keys))
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
--- @param params lsp.ConfigurationParams
RSC['workspace/configuration'] = function(_, params, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message(
'LSP[',
ctx.client_id,
'] client has shut down after sending a workspace/configuration request'
)
return
end
if not params.items then
return {}
end
local response = {}
for _, item in ipairs(params.items) do
if item.section then
local value = lookup_section(client.settings, item.section)
-- For empty sections with no explicit '' key, return settings as is
if value == nil and item.section == '' then
value = client.settings
end
if value == nil then
value = vim.NIL
end
table.insert(response, value)
else
-- If no section is provided, return settings as is
table.insert(response, client.settings)
end
end
return response
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders
RSC['workspace/workspaceFolders'] = function(_, _, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message('LSP[id=', ctx.client_id, '] client has shut down after sending the message')
return
end
return client.workspace_folders or vim.NIL
end
NSC['textDocument/publishDiagnostics'] = function(...)
return vim.lsp.diagnostic.on_publish_diagnostics(...)
end
--- @private
RCS['textDocument/diagnostic'] = function(...)
return vim.lsp.diagnostic.on_diagnostic(...)
end
--- @private
RCS['textDocument/inlayHint'] = function(...)
return vim.lsp.inlay_hint.on_inlayhint(...)
end
--- Return a function that converts LSP responses to list items and opens the list
---
--- The returned function has an optional {config} parameter that accepts |vim.lsp.ListOpts|
---
---@param map_result fun(resp: any, bufnr: integer, position_encoding: 'utf-8'|'utf-16'|'utf-32'): table to convert the response
---@param entity string name of the resource used in a `not found` error message
---@param title_fn fun(ctx: lsp.HandlerContext): string Function to call to generate list title
---@return lsp.Handler
local function response_to_list(map_result, entity, title_fn)
--- @diagnostic disable-next-line:redundant-parameter
return function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then
vim.notify('No ' .. entity .. ' found')
return
end
config = config or {}
local title = title_fn(ctx)
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
local items = map_result(result, ctx.bufnr, client.offset_encoding)
local list = { title = title, items = items, context = ctx }
if config.on_list then
assert(vim.is_callable(config.on_list), 'on_list is not a function')
config.on_list(list)
elseif config.loclist then
vim.fn.setloclist(0, {}, ' ', list)
vim.cmd.lopen()
else
vim.fn.setqflist({}, ' ', list)
vim.cmd('botright copen')
end
end
end
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
RCS['textDocument/documentSymbol'] = response_to_list(
util.symbols_to_items,
'document symbols',
function(ctx)
local fname = vim.fn.fnamemodify(vim.uri_to_fname(ctx.params.textDocument.uri), ':.')
return string.format('Symbols in %s', fname)
end
)
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
RCS['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
return string.format("Symbols matching '%s'", ctx.params.query)
end)
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
RCS['textDocument/rename'] = function(_, result, ctx)
if not result then
vim.notify("Language server couldn't provide rename result", vim.log.levels.INFO)
return
end
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
util.apply_workspace_edit(result, client.offset_encoding)
end
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
RCS['textDocument/rangeFormatting'] = function(_, result, ctx)
if not result then
return
end
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
RCS['textDocument/formatting'] = function(_, result, ctx)
if not result then
return
end
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
RCS['textDocument/completion'] = function(_, result, _)
if vim.tbl_isempty(result or {}) then
return
end
local cursor = api.nvim_win_get_cursor(0)
local row, col = cursor[1], cursor[2]
local line = assert(api.nvim_buf_get_lines(0, row - 1, row, false)[1])
local line_to_cursor = line:sub(col + 1)
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
local prefix = line_to_cursor:sub(textMatch + 1)
local matches = completion._lsp_to_complete_items(result, prefix)
vim.fn.complete(textMatch + 1, matches)
end
--- @deprecated
--- |lsp-handler| for the method "textDocument/hover"
---
--- ```lua
--- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
--- vim.lsp.handlers.hover, {
--- -- Use a sharp border with `FloatBorder` highlights
--- border = "single",
--- -- add the title in hover float window
--- title = "hover"
--- }
--- )
--- ```
---
---@param _ lsp.ResponseError?
---@param result lsp.Hover
---@param ctx lsp.HandlerContext
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
--- - See |vim.lsp.util.open_floating_preview()| for more options.
--- @diagnostic disable-next-line:redundant-parameter
function M.hover(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
if api.nvim_get_current_buf() ~= ctx.bufnr then
-- Ignore result since buffer changed. This happens for slow language servers.
return
end
if not (result and result.contents) then
if config.silent ~= true then
vim.notify('No information available')
end
return
end
local format = 'markdown'
local contents ---@type string[]
if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
format = 'plaintext'
contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
else
contents = util.convert_input_to_markdown_lines(result.contents)
end
if vim.tbl_isempty(contents) then
if config.silent ~= true then
vim.notify('No information available')
end
return
end
return util.open_floating_preview(contents, format, config)
end
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
--- @diagnostic disable-next-line: deprecated
RCS['textDocument/hover'] = M.hover
local sig_help_ns = api.nvim_create_namespace('nvim.lsp.signature_help')
--- @deprecated remove in 0.13
--- |lsp-handler| for the method "textDocument/signatureHelp".
---
--- The active parameter is highlighted with |hl-LspSignatureActiveParameter|.
---
--- ```lua
--- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
--- vim.lsp.handlers.signature_help, {
--- -- Use a sharp border with `FloatBorder` highlights
--- border = "single"
--- }
--- )
--- ```
---
---@param _ lsp.ResponseError?
---@param result lsp.SignatureHelp? Response from the language server
---@param ctx lsp.HandlerContext Client context
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
--- - See |vim.lsp.util.open_floating_preview()| for more options
--- @diagnostic disable-next-line:redundant-parameter
function M.signature_help(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
if api.nvim_get_current_buf() ~= ctx.bufnr then
-- Ignore result since buffer changed. This happens for slow language servers.
return
end
-- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler
-- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore
if not (result and result.signatures and result.signatures[1]) then
if config.silent ~= true then
print('No signature help available')
end
return
end
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
local triggers =
vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
local ft = vim.bo[ctx.bufnr].filetype
local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
if not lines or vim.tbl_isempty(lines) then
if config.silent ~= true then
print('No signature help available')
end
return
end
local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
-- Highlight the active parameter.
if hl then
vim.hl.range(
fbuf,
sig_help_ns,
'LspSignatureActiveParameter',
{ hl[1], hl[2] },
{ hl[3], hl[4] }
)
end
return fbuf, fwin
end
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
--- @diagnostic disable-next-line:deprecated
RCS['textDocument/signatureHelp'] = M.signature_help
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
RCS['textDocument/documentHighlight'] = function(_, result, ctx)
if not result then
return
end
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
if not client then
return
end
util.buf_highlight_references(ctx.bufnr, result, client.offset_encoding)
end
--- Displays call hierarchy in the quickfix window.
---
--- @param direction 'from'|'to' `"from"` for incoming calls and `"to"` for outgoing calls
--- @overload fun(direction:'from'): fun(_, result: lsp.CallHierarchyIncomingCall[]?)
--- @overload fun(direction:'to'): fun(_, result: lsp.CallHierarchyOutgoingCall[]?)
local function make_call_hierarchy_handler(direction)
--- @param result lsp.CallHierarchyIncomingCall[]|lsp.CallHierarchyOutgoingCall[]
return function(_, result)
if not result then
return
end
local items = {}
for _, call_hierarchy_call in pairs(result) do
--- @type lsp.CallHierarchyItem
local call_hierarchy_item = call_hierarchy_call[direction]
for _, range in pairs(call_hierarchy_call.fromRanges) do
table.insert(items, {
filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)),
text = call_hierarchy_item.name,
lnum = range.start.line + 1,
col = range.start.character + 1,
})
end
end
vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items })
vim.cmd('botright copen')
end
end
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_incomingCalls
RCS['callHierarchy/incomingCalls'] = make_call_hierarchy_handler('from')
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls
RCS['callHierarchy/outgoingCalls'] = make_call_hierarchy_handler('to')
--- Displays type hierarchy in the quickfix window.
local function make_type_hierarchy_handler()
--- @param result lsp.TypeHierarchyItem[]
return function(_, result, ctx, _)
if not result then
return
end
local function format_item(item)
if not item.detail or #item.detail == 0 then
return item.name
end
return string.format('%s %s', item.name, item.detail)
end
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
local items = {}
for _, type_hierarchy_item in pairs(result) do
local col = util._get_line_byte_from_position(
ctx.bufnr,
type_hierarchy_item.range.start,
client.offset_encoding
)
table.insert(items, {
filename = assert(vim.uri_to_fname(type_hierarchy_item.uri)),
text = format_item(type_hierarchy_item),
lnum = type_hierarchy_item.range.start.line + 1,
col = col + 1,
})
end
vim.fn.setqflist({}, ' ', { title = 'LSP type hierarchy', items = items })
vim.cmd('botright copen')
end
end
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_incomingCalls
RCS['typeHierarchy/subtypes'] = make_type_hierarchy_handler()
--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_outgoingCalls
RCS['typeHierarchy/supertypes'] = make_type_hierarchy_handler()
--- @see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
--- @param params lsp.LogMessageParams
NSC['window/logMessage'] = function(_, params, ctx)
local message_type = params.type
local message = params.message
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
err_message('LSP[', client_name, '] client has shut down after sending ', message)
end
if message_type == protocol.MessageType.Error then
log.error(message)
elseif message_type == protocol.MessageType.Warning then
log.warn(message)
elseif message_type == protocol.MessageType.Info or message_type == protocol.MessageType.Log then
log.info(message)
else
log.debug(message)
end
return params
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage
--- @param params lsp.ShowMessageParams
NSC['window/showMessage'] = function(_, params, ctx)
return show_message_notification(params, ctx)
end
--- @private
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument
--- @param params lsp.ShowDocumentParams
RSC['window/showDocument'] = function(_, params, ctx)
local uri = params.uri
if params.external then
-- TODO(lvimuser): ask the user for confirmation
local cmd, err = vim.ui.open(uri)
local ret = cmd and cmd:wait(2000) or nil
if ret == nil or ret.code ~= 0 then
return {
success = false,
error = {
code = protocol.ErrorCodes.UnknownErrorCode,
message = ret and ret.stderr or err,
},
}
end
return { success = true }
end
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
err_message('LSP[', client_name, '] client has shut down after sending ', ctx.method)
return vim.NIL
end
local location = {
uri = uri,
range = params.selection,
}
local success = util.show_document(location, client.offset_encoding, {
reuse_win = true,
focus = params.takeFocus,
})
return { success = success or false }
end
---@see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeLens_refresh
RSC['workspace/codeLens/refresh'] = function(err, result, ctx)
return vim.lsp.codelens.on_refresh(err, result, ctx)
end
---@see https://microsoft.github.io/language-server-protocol/specification/#diagnostic_refresh
RSC['workspace/diagnostic/refresh'] = function(err, result, ctx)
return vim.lsp.diagnostic.on_refresh(err, result, ctx)
end
---@see https://microsoft.github.io/language-server-protocol/specification/#workspace_inlayHint_refresh
RSC['workspace/inlayHint/refresh'] = function(err, result, ctx)
return vim.lsp.inlay_hint.on_refresh(err, result, ctx)
end
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#semanticTokens_refreshRequest
RSC['workspace/semanticTokens/refresh'] = function(err, result, ctx)
return vim.lsp.semantic_tokens._refresh(err, result, ctx)
end
--- @nodoc
--- @type table<string, lsp.Handler>
M = vim.tbl_extend('force', M, RSC, NSC, RCS)
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
--- @diagnostic disable-next-line:redundant-parameter
M[k] = function(err, result, ctx, config)
if log.trace() then
log.trace('default_handler', ctx.method, {
err = err,
result = result,
ctx = vim.inspect(ctx),
})
end
-- ServerCancelled errors should be propagated to the request handler
if err and err.code ~= protocol.ErrorCodes.ServerCancelled then
-- LSP spec:
-- interface ResponseError:
-- code: integer;
-- message: string;
-- data?: string | number | boolean | array | object | null;
-- Per LSP, don't show ContentModified error to the user.
if err.code ~= protocol.ErrorCodes.ContentModified then
local client = vim.lsp.get_client_by_id(ctx.client_id)
local client_name = client and client.name or string.format('client_id=%d', ctx.client_id)
err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message)
end
return
end
--- @diagnostic disable-next-line:redundant-parameter
return fn(err, result, ctx, config)
end
end
return M