diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-30 20:35:25 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-30 20:35:25 +0000 |
commit | 1b7b916b7631ddf73c38e3a0070d64e4636cb2f3 (patch) | |
tree | cd08258054db80bb9a11b1061bb091c70b76926a /runtime/lua/vim/lsp/handlers.lua | |
parent | eaa89c11d0f8aefbb512de769c6c82f61a8baca3 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.gz rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.bz2 rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.zip |
Merge remote-tracking branch 'upstream/master' into aucmd_textputpostaucmd_textputpost
Diffstat (limited to 'runtime/lua/vim/lsp/handlers.lua')
-rw-r--r-- | runtime/lua/vim/lsp/handlers.lua | 321 |
1 files changed, 180 insertions, 141 deletions
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 5096100a60..6fde55cf04 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -1,5 +1,6 @@ local log = require('vim.lsp.log') local protocol = require('vim.lsp.protocol') +local ms = protocol.Methods local util = require('vim.lsp.util') local api = vim.api @@ -7,82 +8,70 @@ local M = {} -- FIXME: DOC: Expose in vimdocs ----@private --- Writes to error buffer. ----@param ... (table of strings) Will be concatenated before being written +---@param ... string Will be concatenated before being written local function err_message(...) vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR) api.nvim_command('redraw') end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand -M['workspace/executeCommand'] = function(_, _, _, _) +M[ms.workspace_executeCommand] = function(_, _, _, _) -- Error handling is done implicitly by wrapping all handlers; see end of this file end ----@private -local function progress_handler(_, result, ctx, _) - 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) +--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress +---@param result lsp.ProgressParams +---@param ctx lsp.HandlerContext +M[ms.dollar_progress] = function(_, result, ctx) + local client = vim.lsp.get_client_by_id(ctx.client_id) if not client then - err_message('LSP[', client_name, '] client has shut down during progress update') + err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update') return vim.NIL end - local val = result.value -- unspecified yet - local token = result.token -- string or number + local kind = nil + local value = result.value - if type(val) ~= 'table' then - val = { content = val } - end - if val.kind then - if val.kind == 'begin' then - client.messages.progress[token] = { - title = val.title, - cancellable = val.cancellable, - message = val.message, - percentage = val.percentage, - } - elseif val.kind == 'report' then - client.messages.progress[token].cancellable = val.cancellable - client.messages.progress[token].message = val.message - client.messages.progress[token].percentage = val.percentage - elseif val.kind == 'end' then - if client.messages.progress[token] == nil then - err_message('LSP[', client_name, '] received `end` message with no corresponding `begin`') - else - client.messages.progress[token].message = val.message - client.messages.progress[token].done = true + if type(value) == 'table' then + kind = value.kind + -- 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[result.token] = value.title + else + value.title = client.progress.pending[result.token] + if kind == 'end' then + client.progress.pending[result.token] = nil end end - else - client.messages.progress[token] = val - client.messages.progress[token].done = true end - api.nvim_exec_autocmds('User', { pattern = 'LspProgressUpdate', modeline = false }) -end + client.progress:push(result) ---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress -M['$/progress'] = progress_handler + api.nvim_exec_autocmds('LspProgress', { + pattern = kind, + modeline = false, + data = { client_id = ctx.client_id, result = result }, + }) +end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create -M['window/workDoneProgress/create'] = function(_, result, ctx) - local client_id = ctx.client_id - local client = vim.lsp.get_client_by_id(client_id) - local token = result.token -- string or number - local client_name = client and client.name or string.format('id=%d', client_id) +---@param result lsp.WorkDoneProgressCreateParams +---@param ctx lsp.HandlerContext +M[ms.window_workDoneProgress_create] = function(_, result, ctx) + local client = vim.lsp.get_client_by_id(ctx.client_id) if not client then - err_message('LSP[', client_name, '] client has shut down while creating progress report') + err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update') return vim.NIL end - client.messages.progress[token] = {} + client.progress:push(result) return vim.NIL end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest ---@param result lsp.ShowMessageRequestParams -M['window/showMessageRequest'] = function(_, result) +M[ms.window_showMessageRequest] = function(_, result) local actions = result.actions or {} local co, is_main = coroutine.running() if co and not is_main then @@ -117,20 +106,52 @@ M['window/showMessageRequest'] = function(_, result) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability -M['client/registerCapability'] = function(_, _, ctx) +M[ms.client_registerCapability] = function(_, result, ctx) local client_id = ctx.client_id - local warning_tpl = 'The language server %s triggers a registerCapability ' - .. 'handler despite dynamicRegistration set to false. ' - .. 'Report upstream, this warning is harmless' + ---@type lsp.Client local client = vim.lsp.get_client_by_id(client_id) - local client_name = client and client.name or string.format('id=%d', client_id) - local warning = string.format(warning_tpl, client_name) - log.warn(warning) + + client.dynamic_capabilities:register(result.registrations) + for bufnr, _ in pairs(client.attached_buffers) do + vim.lsp._set_defaults(client, bufnr) + end + + ---@type string[] + local unsupported = {} + for _, reg in ipairs(result.registrations) do + if reg.method == ms.workspace_didChangeWatchedFiles then + require('vim.lsp._watchfiles').register(reg, ctx) + elseif not client.dynamic_capabilities:supports_registration(reg.method) then + unsupported[#unsupported + 1] = reg.method + end + end + if #unsupported > 0 then + local warning_tpl = 'The language server %s triggers a registerCapability ' + .. 'handler for %s despite dynamicRegistration set to false. ' + .. 'Report upstream, this warning is harmless' + local client_name = client and client.name or string.format('id=%d', client_id) + local warning = string.format(warning_tpl, client_name, table.concat(unsupported, ', ')) + log.warn(warning) + end + return vim.NIL +end + +--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability +M[ms.client_unregisterCapability] = function(_, result, ctx) + local client_id = ctx.client_id + local client = vim.lsp.get_client_by_id(client_id) + client.dynamic_capabilities:unregister(result.unregisterations) + + for _, unreg in ipairs(result.unregisterations) do + if unreg.method == ms.workspace_didChangeWatchedFiles then + require('vim.lsp._watchfiles').unregister(unreg, ctx) + end + end return vim.NIL end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit -M['workspace/applyEdit'] = function(_, workspace_edit, ctx) +M[ms.workspace_applyEdit] = function(_, workspace_edit, ctx) assert( workspace_edit, 'workspace/applyEdit must be called with `ApplyWorkspaceEditParams`. Server is violating the specification' @@ -150,7 +171,7 @@ M['workspace/applyEdit'] = function(_, workspace_edit, ctx) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration -M['workspace/configuration'] = function(_, result, ctx) +M[ms.workspace_configuration] = function(_, result, ctx) local client_id = ctx.client_id local client = vim.lsp.get_client_by_id(client_id) if not client then @@ -180,7 +201,7 @@ M['workspace/configuration'] = function(_, result, ctx) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders -M['workspace/workspaceFolders'] = function(_, _, ctx) +M[ms.workspace_workspaceFolders] = function(_, _, ctx) local client_id = ctx.client_id local client = vim.lsp.get_client_by_id(client_id) if not client then @@ -190,16 +211,24 @@ M['workspace/workspaceFolders'] = function(_, _, ctx) return client.workspace_folders or vim.NIL end -M['textDocument/publishDiagnostics'] = function(...) +M[ms.textDocument_publishDiagnostics] = function(...) return require('vim.lsp.diagnostic').on_publish_diagnostics(...) end -M['textDocument/codeLens'] = function(...) +M[ms.textDocument_diagnostic] = function(...) + return require('vim.lsp.diagnostic').on_diagnostic(...) +end + +M[ms.textDocument_codeLens] = function(...) return require('vim.lsp.codelens').on_codelens(...) end +M[ms.textDocument_inlayHint] = function(...) + return require('vim.lsp.inlay_hint').on_inlayhint(...) +end + --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references -M['textDocument/references'] = function(_, result, ctx, config) +M[ms.textDocument_references] = function(_, result, ctx, config) if not result or vim.tbl_isempty(result) then vim.notify('No references found') else @@ -221,7 +250,6 @@ M['textDocument/references'] = function(_, result, ctx, config) end end ----@private --- Return a function that converts LSP responses to list items and opens the list --- --- The returned function has an optional {config} parameter that accepts a table @@ -256,7 +284,7 @@ local function response_to_list(map_result, entity, title_fn) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol -M['textDocument/documentSymbol'] = response_to_list( +M[ms.textDocument_documentSymbol] = response_to_list( util.symbols_to_items, 'document symbols', function(ctx) @@ -266,12 +294,12 @@ M['textDocument/documentSymbol'] = response_to_list( ) --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol -M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols', function(ctx) +M[ms.workspace_symbol] = response_to_list(util.symbols_to_items, 'symbols', function(ctx) return string.format("Symbols matching '%s'", ctx.params.query) end) --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename -M['textDocument/rename'] = function(_, result, ctx, _) +M[ms.textDocument_rename] = function(_, result, ctx, _) if not result then vim.notify("Language server couldn't provide rename result", vim.log.levels.INFO) return @@ -281,7 +309,7 @@ M['textDocument/rename'] = function(_, result, ctx, _) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting -M['textDocument/rangeFormatting'] = function(_, result, ctx, _) +M[ms.textDocument_rangeFormatting] = function(_, result, ctx, _) if not result then return end @@ -290,7 +318,7 @@ M['textDocument/rangeFormatting'] = function(_, result, ctx, _) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting -M['textDocument/formatting'] = function(_, result, ctx, _) +M[ms.textDocument_formatting] = function(_, result, ctx, _) if not result then return end @@ -299,7 +327,7 @@ M['textDocument/formatting'] = function(_, result, ctx, _) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion -M['textDocument/completion'] = function(_, result, _, _) +M[ms.textDocument_completion] = function(_, result, _, _) if vim.tbl_isempty(result or {}) then return end @@ -314,20 +342,22 @@ M['textDocument/completion'] = function(_, result, _, _) end --- |lsp-handler| for the method "textDocument/hover" ---- <pre>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" ---- } ---- ) ---- </pre> +--- +--- ```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 config table Configuration table. --- - border: (default=nil) --- - Add borders to the floating window ---- - See |nvim_open_win()| +--- - See |vim.lsp.util.open_floating_preview()| for more options. function M.hover(_, result, ctx, config) config = config or {} config.focus_id = ctx.method @@ -341,21 +371,28 @@ function M.hover(_, result, ctx, config) end return end - local markdown_lines = util.convert_input_to_markdown_lines(result.contents) - markdown_lines = util.trim_empty_lines(markdown_lines) - if vim.tbl_isempty(markdown_lines) then - vim.notify('No information available') + 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(markdown_lines, 'markdown', config) + return util.open_floating_preview(contents, format, config) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover -M['textDocument/hover'] = M.hover +M[ms.textDocument_hover] = M.hover ----@private --- Jumps to a location. Used as a handler for multiple LSP methods. ----@param _ (not used) +---@param _ nil not used ---@param result (table) result of LSP method; a location or a list of locations. ---@param ctx (table) table containing the context of the request, including the method ---(`textDocument/definition` can return `Location` or `Location[]` @@ -370,50 +407,54 @@ local function location_handler(_, result, ctx, config) -- textDocument/definition can return Location or Location[] -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition + if not vim.tbl_islist(result) then + result = { result } + end - if vim.tbl_islist(result) then - local title = 'LSP locations' - local items = util.locations_to_items(result, client.offset_encoding) + local title = 'LSP locations' + local items = util.locations_to_items(result, client.offset_encoding) - if config.on_list then - assert(type(config.on_list) == 'function', 'on_list is not a function') - config.on_list({ title = title, items = items }) - else - if #result == 1 then - util.jump_to_location(result[1], client.offset_encoding, config.reuse_win) - return - end - vim.fn.setqflist({}, ' ', { title = title, items = items }) - api.nvim_command('botright copen') - end - else - util.jump_to_location(result, client.offset_encoding, config.reuse_win) + if config.on_list then + assert(type(config.on_list) == 'function', 'on_list is not a function') + config.on_list({ title = title, items = items }) + return + end + if #result == 1 then + util.jump_to_location(result[1], client.offset_encoding, config.reuse_win) + return end + vim.fn.setqflist({}, ' ', { title = title, items = items }) + api.nvim_command('botright copen') end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration -M['textDocument/declaration'] = location_handler +M[ms.textDocument_declaration] = location_handler --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition -M['textDocument/definition'] = location_handler +M[ms.textDocument_definition] = location_handler --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition -M['textDocument/typeDefinition'] = location_handler +M[ms.textDocument_typeDefinition] = location_handler --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation -M['textDocument/implementation'] = location_handler +M[ms.textDocument_implementation] = location_handler --- |lsp-handler| for the method "textDocument/signatureHelp". +--- --- The active parameter is highlighted with |hl-LspSignatureActiveParameter|. ---- <pre>lua ---- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( ---- vim.lsp.handlers.signature_help, { ---- -- Use a sharp border with `FloatBorder` highlights ---- border = "single" ---- } ---- ) ---- </pre> +--- +--- ```lua +--- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( +--- vim.lsp.handlers.signature_help, { +--- -- Use a sharp border with `FloatBorder` highlights +--- border = "single" +--- } +--- ) +--- ``` +--- +---@param result table Response from the language server +---@param ctx table Client context ---@param config table Configuration table. --- - border: (default=nil) --- - Add borders to the floating window ---- - See |nvim_open_win()| +--- - See |vim.lsp.util.open_floating_preview()| for more options function M.signature_help(_, result, ctx, config) config = config or {} config.focus_id = ctx.method @@ -432,10 +473,9 @@ function M.signature_help(_, result, ctx, config) local client = vim.lsp.get_client_by_id(ctx.client_id) local triggers = vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters') - local ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype') + local ft = vim.bo[ctx.bufnr].filetype local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers) - lines = util.trim_empty_lines(lines) - if vim.tbl_isempty(lines) then + if not lines or vim.tbl_isempty(lines) then if config.silent ~= true then print('No signature help available') end @@ -443,16 +483,18 @@ function M.signature_help(_, result, ctx, config) end local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config) if hl then - api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', 0, unpack(hl)) + -- Highlight the second line if the signature is wrapped in a Markdown code block. + local line = vim.startswith(lines[1], '```') and 1 or 0 + api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', line, unpack(hl)) end return fbuf, fwin end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp -M['textDocument/signatureHelp'] = M.signature_help +M[ms.textDocument_signatureHelp] = M.signature_help --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight -M['textDocument/documentHighlight'] = function(_, result, ctx, _) +M[ms.textDocument_documentHighlight] = function(_, result, ctx, _) if not result then return end @@ -469,8 +511,9 @@ end --- Displays call hierarchy in the quickfix window. --- ---@param direction `"from"` for incoming calls and `"to"` for outgoing calls ----@returns `CallHierarchyIncomingCall[]` if {direction} is `"from"`, ----@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`, +---@return function +--- `CallHierarchyIncomingCall[]` if {direction} is `"from"`, +--- `CallHierarchyOutgoingCall[]` if {direction} is `"to"`, local make_call_hierarchy_handler = function(direction) return function(_, result) if not result then @@ -494,13 +537,13 @@ local make_call_hierarchy_handler = function(direction) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_incomingCalls -M['callHierarchy/incomingCalls'] = make_call_hierarchy_handler('from') +M[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from') --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls -M['callHierarchy/outgoingCalls'] = make_call_hierarchy_handler('to') +M[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to') --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage -M['window/logMessage'] = function(_, result, ctx, _) +M[ms.window_logMessage] = function(_, result, ctx, _) local message_type = result.type local message = result.message local client_id = ctx.client_id @@ -522,7 +565,7 @@ M['window/logMessage'] = function(_, result, ctx, _) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage -M['window/showMessage'] = function(_, result, ctx, _) +M[ms.window_showMessage] = function(_, result, ctx, _) local message_type = result.type local message = result.message local client_id = ctx.client_id @@ -541,27 +584,19 @@ M['window/showMessage'] = function(_, result, ctx, _) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument -M['window/showDocument'] = function(_, result, ctx, _) +M[ms.window_showDocument] = function(_, result, ctx, _) local uri = result.uri if result.external then -- TODO(lvimuser): ask the user for confirmation - local cmd - if vim.fn.has('win32') == 1 then - cmd = { 'cmd.exe', '/c', 'start', '""', uri } - elseif vim.fn.has('macunix') == 1 then - cmd = { 'open', uri } - else - cmd = { 'xdg-open', uri } - end + local ret, err = vim.ui.open(uri) - local ret = vim.fn.system(cmd) - if vim.v.shell_error ~= 0 then + if ret == nil or ret.code ~= 0 then return { success = false, error = { code = protocol.ErrorCodes.UnknownErrorCode, - message = ret, + message = ret and ret.stderr or err, }, } end @@ -573,7 +608,7 @@ M['window/showDocument'] = function(_, result, ctx, _) 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 }) + err_message('LSP[', client_name, '] client has shut down after sending ', ctx.method) return vim.NIL end @@ -589,6 +624,11 @@ M['window/showDocument'] = function(_, result, ctx, _) return { success = success or false } end +---@see https://microsoft.github.io/language-server-protocol/specification/#workspace_inlayHint_refresh +M[ms.workspace_inlayHint_refresh] = function(err, result, ctx, config) + return require('vim.lsp.inlay_hint').on_refresh(err, result, ctx, config) +end + -- Add boilerplate error validation and logging for all of these. for k, fn in pairs(M) do M[k] = function(err, result, ctx, config) @@ -622,4 +662,3 @@ for k, fn in pairs(M) do end return M --- vim:sw=2 ts=2 et |