mirror of
https://github.com/neovim/neovim.git
synced 2026-02-17 18:01:16 +10:00
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.
715 lines
26 KiB
Lua
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
|