aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp/handlers.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/lsp/handlers.lua')
-rw-r--r--runtime/lua/vim/lsp/handlers.lua310
1 files changed, 310 insertions, 0 deletions
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
new file mode 100644
index 0000000000..e034923afb
--- /dev/null
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -0,0 +1,310 @@
+local log = require 'vim.lsp.log'
+local protocol = require 'vim.lsp.protocol'
+local util = require 'vim.lsp.util'
+local vim = vim
+local api = vim.api
+local buf = require 'vim.lsp.buf'
+
+local M = {}
+
+-- FIXME: DOC: Expose in vimdocs
+
+--@private
+--- Writes to error buffer.
+--@param ... (table of strings) Will be concatenated before being written
+local function err_message(...)
+ api.nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
+ api.nvim_command("redraw")
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
+M['workspace/executeCommand'] = function(err, _)
+ if err then
+ error("Could not execute code action: "..err.message)
+ end
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
+M['textDocument/codeAction'] = function(_, _, actions)
+ if actions == nil or vim.tbl_isempty(actions) then
+ print("No code actions available")
+ return
+ end
+
+ local option_strings = {"Code Actions:"}
+ for i, action in ipairs(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 > #actions then
+ return
+ end
+ local action_chosen = actions[choice]
+ -- textDocument/codeAction can return either Command[] or CodeAction[].
+ -- If it is a CodeAction, it can have either an edit, a command or both.
+ -- Edits should be executed first
+ if action_chosen.edit or type(action_chosen.command) == "table" then
+ if action_chosen.edit then
+ util.apply_workspace_edit(action_chosen.edit)
+ end
+ if type(action_chosen.command) == "table" then
+ buf.execute_command(action_chosen.command)
+ end
+ else
+ buf.execute_command(action_chosen)
+ end
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
+M['workspace/applyEdit'] = function(_, _, workspace_edit)
+ if not workspace_edit then return end
+ -- TODO(ashkan) Do something more with label?
+ if workspace_edit.label then
+ print("Workspace edit", workspace_edit.label)
+ end
+ local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit)
+ return {
+ applied = status;
+ failureReason = result;
+ }
+end
+
+M['textDocument/publishDiagnostics'] = function(...)
+ return require('vim.lsp.diagnostic').on_publish_diagnostics(...)
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
+M['textDocument/references'] = function(_, _, result)
+ if not result then return end
+ util.set_qflist(util.locations_to_items(result))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+end
+
+--@private
+--- Prints given list of symbols to the quickfix list.
+--@param _ (not used)
+--@param _ (not used)
+--@param result (list of Symbols) LSP method name
+--@param result (table) result of LSP method; a location or a list of locations.
+---(`textDocument/definition` can return `Location` or `Location[]`
+local symbol_handler = function(_, _, result, _, bufnr)
+ if not result or vim.tbl_isempty(result) then return end
+
+ util.set_qflist(util.symbols_to_items(result, bufnr))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+end
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
+M['textDocument/documentSymbol'] = symbol_handler
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
+M['workspace/symbol'] = symbol_handler
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
+M['textDocument/rename'] = function(_, _, result)
+ if not result then return end
+ util.apply_workspace_edit(result)
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
+M['textDocument/rangeFormatting'] = function(_, _, result)
+ if not result then return end
+ util.apply_text_edits(result)
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
+M['textDocument/formatting'] = function(_, _, result)
+ if not result then return end
+ util.apply_text_edits(result)
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
+M['textDocument/completion'] = function(_, _, result)
+ if vim.tbl_isempty(result or {}) then return end
+ local row, col = unpack(api.nvim_win_get_cursor(0))
+ 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 = util.text_document_completion_list_to_complete_items(result, prefix)
+ vim.fn.complete(textMatch+1, matches)
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
+M['textDocument/hover'] = function(_, method, result)
+ util.focusable_float(method, function()
+ if not (result and result.contents) then
+ -- return { 'No information available' }
+ 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
+ -- return { 'No information available' }
+ return
+ end
+ local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, {
+ pad_left = 1; pad_right = 1;
+ })
+ util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr)
+ return bufnr, winnr
+ end)
+end
+
+--@private
+--- Jumps to a location. Used as a handler for multiple LSP methods.
+--@param _ (not used)
+--@param method (string) LSP method name
+--@param result (table) result of LSP method; a location or a list of locations.
+---(`textDocument/definition` can return `Location` or `Location[]`
+local function location_handler(_, method, result)
+ if result == nil or vim.tbl_isempty(result) then
+ local _ = log.info() and log.info(method, 'No location found')
+ return nil
+ end
+
+ -- textDocument/definition can return Location or Location[]
+ -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
+
+ if vim.tbl_islist(result) then
+ util.jump_to_location(result[1])
+
+ if #result > 1 then
+ util.set_qflist(util.locations_to_items(result))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+ end
+ else
+ util.jump_to_location(result)
+ end
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
+M['textDocument/declaration'] = location_handler
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
+M['textDocument/definition'] = location_handler
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
+M['textDocument/typeDefinition'] = location_handler
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
+M['textDocument/implementation'] = location_handler
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
+M['textDocument/signatureHelp'] = function(_, method, result)
+ -- 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
+ print('No signature help available')
+ return
+ end
+ local lines = util.convert_signature_help_to_markdown_lines(result)
+ lines = util.trim_empty_lines(lines)
+ if vim.tbl_isempty(lines) then
+ print('No signature help available')
+ return
+ end
+ util.focusable_preview(method, function()
+ return lines, util.try_trim_markdown_code_blocks(lines)
+ end)
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
+M['textDocument/documentHighlight'] = function(_, _, result, _)
+ if not result then return end
+ local bufnr = api.nvim_get_current_buf()
+ util.buf_highlight_references(bufnr, result)
+end
+
+--@private
+---
+--- 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"`,
+local make_call_hierarchy_handler = function(direction)
+ return function(_, _, result)
+ if not result then return end
+ local items = {}
+ for _, call_hierarchy_call in pairs(result) do
+ 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
+ util.set_qflist(items)
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+ end
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy/incomingCalls
+M['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')
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/logMessage
+M['window/logMessage'] = function(_, _, result, client_id)
+ local message_type = result.type
+ local message = result.message
+ 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 the 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 then
+ log.info(message)
+ else
+ log.debug(message)
+ end
+ return result
+end
+
+--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/showMessage
+M['window/showMessage'] = function(_, _, result, client_id)
+ local message_type = result.type
+ local message = result.message
+ 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 the message")
+ end
+ if message_type == protocol.MessageType.Error then
+ err_message("LSP[", client_name, "] ", message)
+ else
+ local message_type_name = protocol.MessageType[message_type]
+ api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
+ end
+ return result
+end
+
+-- Add boilerplate error validation and logging for all of these.
+for k, fn in pairs(M) do
+ M[k] = function(err, method, params, client_id, bufnr, config)
+ local _ = log.debug() and log.debug('default_handler', method, {
+ params = params, client_id = client_id, err = err, bufnr = bufnr, config = config
+ })
+
+ if err then
+ error(tostring(err))
+ end
+
+ return fn(err, method, params, client_id, bufnr, config)
+ end
+end
+
+return M
+-- vim:sw=2 ts=2 et