aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua')
-rw-r--r--runtime/lua/vim/F.lua24
-rw-r--r--runtime/lua/vim/highlight.lua16
-rw-r--r--runtime/lua/vim/lsp.lua216
-rw-r--r--runtime/lua/vim/lsp/buf.lua15
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua345
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua1195
-rw-r--r--runtime/lua/vim/lsp/handlers.lua310
-rw-r--r--runtime/lua/vim/lsp/protocol.lua25
-rw-r--r--runtime/lua/vim/lsp/rpc.lua68
-rw-r--r--runtime/lua/vim/lsp/util.lua402
-rw-r--r--runtime/lua/vim/shared.lua4
11 files changed, 1824 insertions, 796 deletions
diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua
new file mode 100644
index 0000000000..5887e978b9
--- /dev/null
+++ b/runtime/lua/vim/F.lua
@@ -0,0 +1,24 @@
+local F = {}
+
+--- Returns {a} if it is not nil, otherwise returns {b}.
+---
+--@param a
+--@param b
+function F.if_nil(a, b)
+ if a == nil then return b end
+ return a
+end
+
+-- Use in combination with pcall
+function F.ok_or_nil(status, ...)
+ if not status then return end
+ return ...
+end
+
+-- Nil pcall.
+function F.npcall(fn, ...)
+ return F.ok_or_nil(pcall(fn, ...))
+end
+
+
+return F
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 705b34dc99..0012dce081 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -2,6 +2,22 @@ local api = vim.api
local highlight = {}
+--@private
+function highlight.create(higroup, hi_info, default)
+ local options = {}
+ -- TODO: Add validation
+ for k, v in pairs(hi_info) do
+ table.insert(options, string.format("%s=%s", k, v))
+ end
+ vim.cmd(string.format([[highlight %s %s %s]], default and "default" or "", higroup, table.concat(options, " ")))
+end
+
+--@private
+function highlight.link(higroup, link_to, force)
+ vim.cmd(string.format([[highlight%s link %s %s]], force and "!" or " default", higroup, link_to))
+end
+
+
--- Highlight range between two positions
---
--@param bufnr number of buffer to apply highlighting to
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 1a0015e2db..dacdbcfa17 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,4 +1,4 @@
-local default_callbacks = require 'vim.lsp.callbacks'
+local default_handlers = require 'vim.lsp.handlers'
local log = require 'vim.lsp.log'
local lsp_rpc = require 'vim.lsp.rpc'
local protocol = require 'vim.lsp.protocol'
@@ -13,16 +13,21 @@ local validate = vim.validate
local lsp = {
protocol = protocol;
- callbacks = default_callbacks;
+
+ -- TODO(tjdevries): Add in the warning that `callbacks` is no longer supported.
+ -- util.warn_once("vim.lsp.callbacks is deprecated. Use vim.lsp.handlers instead.")
+ handlers = default_handlers;
+ callbacks = default_handlers;
+
buf = require'vim.lsp.buf';
+ diagnostic = require'vim.lsp.diagnostic';
util = util;
+
-- Allow raw RPC access.
rpc = lsp_rpc;
+
-- Export these directly from rpc.
rpc_response_error = lsp_rpc.rpc_response_error;
- -- You probably won't need this directly, since __tostring is set for errors
- -- by the RPC.
- -- format_rpc_error = lsp_rpc.format_rpc_error;
}
-- maps request name to the required resolved_capability in the client.
@@ -72,7 +77,7 @@ local function resolve_bufnr(bufnr)
end
--@private
---- callback called by the client when trying to call a method that's not
+--- Called by the client when trying to call a method that's not
--- supported in any of the servers registered for the current buffer.
--@param method (string) name of the method
function lsp._unsupported_method(method)
@@ -115,14 +120,14 @@ local all_buffer_active_clients = {}
local uninitialized_clients = {}
--@private
---- Invokes a callback for each LSP client attached to the buffer {bufnr}.
+--- Invokes a function for each LSP client attached to the buffer {bufnr}.
---
--@param bufnr (Number) of buffer
---@param callback (function({client}, {client_id}, {bufnr}) Function to run on
+--@param fn (function({client}, {client_id}, {bufnr}) Function to run on
---each client attached to that buffer.
-local function for_each_buffer_client(bufnr, callback)
+local function for_each_buffer_client(bufnr, fn)
validate {
- callback = { callback, 'f' };
+ fn = { fn, 'f' };
}
bufnr = resolve_bufnr(bufnr)
local client_ids = all_buffer_active_clients[bufnr]
@@ -132,7 +137,7 @@ local function for_each_buffer_client(bufnr, callback)
for client_id in pairs(client_ids) do
local client = active_clients[client_id]
if client then
- callback(client, client_id, bufnr)
+ fn(client, client_id, bufnr)
end
end
end
@@ -209,7 +214,9 @@ local function validate_client_config(config)
}
validate {
root_dir = { config.root_dir, is_dir, "directory" };
+ -- TODO(remove-callbacks)
callbacks = { config.callbacks, "t", true };
+ handlers = { config.handlers, "t", true };
capabilities = { config.capabilities, "t", true };
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
cmd_env = { config.cmd_env, "t", true };
@@ -220,13 +227,23 @@ local function validate_client_config(config)
before_init = { config.before_init, "f", true };
offset_encoding = { config.offset_encoding, "s", true };
}
+
+ -- TODO(remove-callbacks)
+ if config.handlers and config.callbacks then
+ error(debug.traceback(
+ "Unable to configure LSP with both 'config.handlers' and 'config.callbacks'. Use 'config.handlers' exclusively."
+ ))
+ end
+
local cmd, cmd_args = lsp._cmd_parts(config.cmd)
local offset_encoding = valid_encodings.UTF16
if config.offset_encoding then
offset_encoding = validate_encoding(config.offset_encoding)
end
+
return {
- cmd = cmd; cmd_args = cmd_args;
+ cmd = cmd;
+ cmd_args = cmd_args;
offset_encoding = offset_encoding;
}
end
@@ -276,12 +293,11 @@ end
---
--- - Methods:
---
---- - request(method, params, [callback], bufnr)
+--- - request(method, params, [handler], bufnr)
--- Sends a request to the server.
--- This is a thin wrapper around {client.rpc.request} with some additional
--- checking.
---- If {callback} is not specified, it will use {client.callbacks} to try to
---- find a callback. If one is not found there, then an error will occur.
+--- If {handler} is not specified, If one is not found there, then an error will occur.
--- Returns: {status}, {[client_id]}. {status} is a boolean indicating if
--- the notification was successful. If it is `false`, then it will always
--- be `false` (the client has shutdown).
@@ -325,8 +341,7 @@ end
--- with the server. You can modify this in the `config`'s `on_init` method
--- before text is sent to the server.
---
---- - {callbacks} (table): The callbacks used by the client as
---- described in |lsp-callbacks|.
+--- - {handlers} (table): The handlers used by the client as described in |lsp-handler|.
---
--- - {config} (table): copy of the table that was passed by the user
--- to |vim.lsp.start_client()|.
@@ -378,15 +393,7 @@ end
--- `{[vim.type_idx]=vim.types.dictionary}`, else it will be encoded as an
--- array.
---
---@param callbacks Map of language server method names to
---- `function(err, method, params, client_id)` handler. Invoked for:
---- - Notifications to the server, where `err` will always be `nil`.
---- - Requests by the server. For these you can respond by returning
---- two values: `result, err` where err must be shaped like a RPC error,
---- i.e. `{ code, message, data? }`. Use |vim.lsp.rpc_response_error()| to
---- help with this.
---- - Default callback for client requests not explicitly specifying
---- a callback.
+--@param handlers Map of language server method names to |lsp-handler|
---
--@param init_options Values to pass in the initialization request
--- as `initializationOptions`. See `initialize` in the LSP spec.
@@ -437,52 +444,51 @@ function lsp.start_client(config)
local client_id = next_client_id()
- local callbacks = config.callbacks or {}
+ -- TODO(remove-callbacks)
+ local handlers = config.handlers or config.callbacks or {}
local name = config.name or tostring(client_id)
local log_prefix = string.format("LSP[%s]", name)
- local handlers = {}
+ local dispatch = {}
--@private
- --- Returns the callback associated with an LSP method. Returns the default
- --- callback if the user hasn't set a custom one.
+ --- Returns the handler associated with an LSP method.
+ --- Returns the default handler if the user hasn't set a custom one.
---
--@param method (string) LSP method name
- --@returns (fn) The callback for the given method, if defined, or the default
- ---from |lsp-callbacks|
- local function resolve_callback(method)
- return callbacks[method] or default_callbacks[method]
+ --@returns (fn) The handler for the given method, if defined, or the default from |vim.lsp.handlers|
+ local function resolve_handler(method)
+ return handlers[method] or default_handlers[method]
end
--@private
--- Handles a notification sent by an LSP server by invoking the
- --- corresponding callback.
+ --- corresponding handler.
---
--@param method (string) LSP method name
--@param params (table) The parameters for that method.
- function handlers.notification(method, params)
+ function dispatch.notification(method, params)
local _ = log.debug() and log.debug('notification', method, params)
- local callback = resolve_callback(method)
- if callback then
+ local handler = resolve_handler(method)
+ if handler then
-- Method name is provided here for convenience.
- callback(nil, method, params, client_id)
+ handler(nil, method, params, client_id)
end
end
--@private
- --- Handles a request from an LSP server by invoking the corresponding
- --- callback.
+ --- Handles a request from an LSP server by invoking the corresponding handler.
---
--@param method (string) LSP method name
--@param params (table) The parameters for that method
- function handlers.server_request(method, params)
+ function dispatch.server_request(method, params)
local _ = log.debug() and log.debug('server_request', method, params)
- local callback = resolve_callback(method)
- if callback then
- local _ = log.debug() and log.debug("server_request: found callback for", method)
- return callback(nil, method, params, client_id)
+ local handler = resolve_handler(method)
+ if handler then
+ local _ = log.debug() and log.debug("server_request: found handler for", method)
+ return handler(nil, method, params, client_id)
end
- local _ = log.debug() and log.debug("server_request: no callback found for", method)
+ local _ = log.debug() and log.debug("server_request: no handler found for", method)
return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
end
@@ -493,7 +499,7 @@ function lsp.start_client(config)
--@param err (...) Other arguments may be passed depending on the error kind
--@see |vim.lsp.client_errors| for possible errors. Use
---`vim.lsp.client_errors[code]` to get a human-friendly name.
- function handlers.on_error(code, err)
+ function dispatch.on_error(code, err)
local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = err })
err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
if config.on_error then
@@ -510,7 +516,7 @@ function lsp.start_client(config)
---
--@param code (number) exit code of the process
--@param signal (number) the signal used to terminate (if any)
- function handlers.on_exit(code, signal)
+ function dispatch.on_exit(code, signal)
active_clients[client_id] = nil
uninitialized_clients[client_id] = nil
local active_buffers = {}
@@ -523,7 +529,7 @@ function lsp.start_client(config)
-- Buffer level cleanup
vim.schedule(function()
for _, bufnr in ipairs(active_buffers) do
- util.buf_clear_diagnostics(bufnr)
+ lsp.diagnostic.clear(bufnr)
end
end)
if config.on_exit then
@@ -532,7 +538,7 @@ function lsp.start_client(config)
end
-- Start the RPC client.
- local rpc = lsp_rpc.start(cmd, cmd_args, handlers, {
+ local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, {
cwd = config.cmd_cwd;
env = config.cmd_env;
})
@@ -542,12 +548,14 @@ function lsp.start_client(config)
name = name;
rpc = rpc;
offset_encoding = offset_encoding;
- callbacks = callbacks;
config = config;
+
+ -- TODO(remove-callbacks)
+ callbacks = handlers;
+ handlers = handlers;
}
- -- Store the uninitialized_clients for cleanup in case we exit before
- -- initialize finishes.
+ -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
uninitialized_clients[client_id] = client;
--@private
@@ -641,13 +649,11 @@ function lsp.start_client(config)
--- Sends a request to the server.
---
--- This is a thin wrapper around {client.rpc.request} with some additional
- --- checks for capabilities and callback availability.
+ --- checks for capabilities and handler availability.
---
--@param method (string) LSP method name.
--@param params (table) LSP request params.
- --@param callback (function, optional) Response handler for this method.
- ---If {callback} is not specified, it will use {client.callbacks} to try to
- ---find a callback. If one is not found there, then an error will occur.
+ --@param handler (function, optional) Response |lsp-handler| for this method.
--@param bufnr (number) Buffer handle (0 for current).
--@returns ({status}, [request_id]): {status} is a bool indicating
---whether the request was successful. If it is `false`, then it will
@@ -656,16 +662,14 @@ function lsp.start_client(config)
---second result. You can use this with `client.cancel_request(request_id)`
---to cancel the-request.
--@see |vim.lsp.buf_request()|
- function client.request(method, params, callback, bufnr)
- -- FIXME: callback is optional, but bufnr is apparently not? Shouldn't that
- -- require a `select('#', ...)` call?
- if not callback then
- callback = resolve_callback(method)
- or error(string.format("not found: %q request callback for client %q.", method, client.name))
+ function client.request(method, params, handler, bufnr)
+ if not handler then
+ handler = resolve_handler(method)
+ or error(string.format("not found: %q request handler for client %q.", method, client.name))
end
- local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr)
+ local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr)
return rpc.request(method, params, function(err, result)
- callback(err, method, result, client_id, bufnr)
+ handler(err, method, result, client_id, bufnr)
end)
end
@@ -995,19 +999,18 @@ nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()")
--@param bufnr (number) Buffer handle, or 0 for current.
--@param method (string) LSP method name
--@param params (optional, table) Parameters to send to the server
---@param callback (optional, functionnil) Handler
--- `function(err, method, params, client_id)` for this request. Defaults
--- to the client callback in `client.callbacks`. See |lsp-callbacks|.
+--@param handler (optional, function) See |lsp-handler|
+-- If nil, follows resolution strategy defined in |lsp-handler-configuration|
--
--@returns 2-tuple:
--- - Map of client-id:request-id pairs for all successful requests.
--- - Function which can be used to cancel all the requests. You could instead
--- iterate all clients and call their `cancel_request()` methods.
-function lsp.buf_request(bufnr, method, params, callback)
+function lsp.buf_request(bufnr, method, params, handler)
validate {
bufnr = { bufnr, 'n', true };
method = { method, 's' };
- callback = { callback, 'f', true };
+ handler = { handler, 'f', true };
}
local client_request_ids = {}
@@ -1015,7 +1018,7 @@ function lsp.buf_request(bufnr, method, params, callback)
for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
if client.supports_method(method) then
method_supported = true
- local request_success, request_id = client.request(method, params, callback, resolved_bufnr)
+ local request_success, request_id = client.request(method, params, handler, resolved_bufnr)
-- This could only fail if the client shut down in the time since we looked
-- it up and we did the request, which should be rare.
@@ -1025,13 +1028,13 @@ function lsp.buf_request(bufnr, method, params, callback)
end
end)
- -- if no clients support the given method, call the callback with the proper
+ -- if no clients support the given method, call the handler with the proper
-- error message.
if not method_supported then
local unsupported_err = lsp._unsupported_method(method)
- local cb = callback or lsp.callbacks[method]
- if cb then
- cb(unsupported_err, method, bufnr)
+ handler = handler or lsp.handlers[method]
+ if handler then
+ handler(unsupported_err, method, bufnr)
end
return
end
@@ -1064,11 +1067,11 @@ end
function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
local request_results = {}
local result_count = 0
- local function _callback(err, _method, result, client_id)
+ local function _sync_handler(err, _, result, client_id)
request_results[client_id] = { error = err, result = result }
result_count = result_count + 1
end
- local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _callback)
+ local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler)
local expected_result_count = 0
for _ in pairs(client_request_ids) do
expected_result_count = expected_result_count + 1
@@ -1209,22 +1212,53 @@ function lsp.get_log_path()
return log.get_filename()
end
--- Defines the LspDiagnostics signs if they're not defined already.
-do
- --@private
- --- Defines a sign if it isn't already defined.
- --@param name (String) Name of the sign
- --@param properties (table) Properties to attach to the sign
- local function define_default_sign(name, properties)
- if vim.tbl_isempty(vim.fn.sign_getdefined(name)) then
- vim.fn.sign_define(name, properties)
+--- Call {fn} for every client attached to {bufnr}
+function lsp.for_each_buffer_client(bufnr, fn)
+ return for_each_buffer_client(bufnr, fn)
+end
+
+--- Function to manage overriding defaults for LSP handlers.
+--@param handler (function) See |lsp-handler|
+--@param override_config (table) Table containing the keys to override behavior of the {handler}
+function lsp.with(handler, override_config)
+ return function(err, method, params, client_id, bufnr, config)
+ return handler(err, method, params, client_id, bufnr, vim.tbl_deep_extend("force", config or {}, override_config))
+ end
+end
+
+--- Helper function to use when implementing a handler.
+--- This will check that all of the keys in the user configuration
+--- are valid keys and make sense to include for this handler.
+---
+--- Will error on invalid keys (i.e. keys that do not exist in the options)
+function lsp._with_extend(name, options, user_config)
+ user_config = user_config or {}
+
+ local resulting_config = {}
+ for k, v in pairs(user_config) do
+ if options[k] == nil then
+ error(debug.traceback(string.format(
+ "Invalid option for `%s`: %s. Valid options are:\n%s",
+ name,
+ k,
+ vim.inspect(vim.tbl_keys(options))
+ )))
end
+
+ resulting_config[k] = v
end
- define_default_sign('LspDiagnosticsErrorSign', {text='E', texthl='LspDiagnosticsErrorSign', linehl='', numhl=''})
- define_default_sign('LspDiagnosticsWarningSign', {text='W', texthl='LspDiagnosticsWarningSign', linehl='', numhl=''})
- define_default_sign('LspDiagnosticsInformationSign', {text='I', texthl='LspDiagnosticsInformationSign', linehl='', numhl=''})
- define_default_sign('LspDiagnosticsHintSign', {text='H', texthl='LspDiagnosticsHintSign', linehl='', numhl=''})
+
+ for k, v in pairs(options) do
+ if resulting_config[k] == nil then
+ resulting_config[k] = v
+ end
+ end
+
+ return resulting_config
end
+-- Define the LspDiagnostics signs if they're not defined already.
+require('vim.lsp.diagnostic')._define_default_signs_and_highlights()
+
return lsp
-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 0b8e08f36c..fa62905c0a 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -30,9 +30,7 @@ end
---
--@param method (string) LSP method name
--@param params (optional, table) Parameters to send to the server
---@param callback (optional, functionnil) Handler
--- `function(err, method, params, client_id)` for this request. Defaults
--- to the client callback in `client.callbacks`. See |lsp-callbacks|.
+--@param handler (optional, functionnil) See |lsp-handler|. Follows |lsp-handler-resolution|
--
--@returns 2-tuple:
--- - Map of client-id:request-id pairs for all successful requests.
@@ -40,12 +38,12 @@ end
--- iterate all clients and call their `cancel_request()` methods.
---
--@see |vim.lsp.buf_request()|
-local function request(method, params, callback)
+local function request(method, params, handler)
validate {
method = {method, 's'};
- callback = {callback, 'f', true};
+ handler = {handler, 'f', true};
}
- return vim.lsp.buf_request(0, method, params, callback)
+ return vim.lsp.buf_request(0, method, params, handler)
end
--- Checks whether the language servers attached to the current buffer are
@@ -64,6 +62,7 @@ function M.hover()
end
--- Jumps to the declaration of the symbol under the cursor.
+--@note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
---
function M.declaration()
local params = util.make_position_params()
@@ -279,7 +278,7 @@ end
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
function M.code_action(context)
validate { context = { context, 't', true } }
- context = context or { diagnostics = util.get_line_diagnostics() }
+ context = context or { diagnostics = vim.lsp.diagnostic.get_line_diagnostics() }
local params = util.make_range_params()
params.context = context
request('textDocument/codeAction', params)
@@ -294,7 +293,7 @@ end
---Defaults to the end of the last visual selection.
function M.range_code_action(context, start_pos, end_pos)
validate { context = { context, 't', true } }
- context = context or { diagnostics = util.get_line_diagnostics() }
+ context = context or { diagnostics = vim.lsp.diagnostic.get_line_diagnostics() }
local params = util.make_given_range_params(start_pos, end_pos)
params.context = context
request('textDocument/codeAction', params)
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua
index 3270d1d2a9..1da92b900d 100644
--- a/runtime/lua/vim/lsp/callbacks.lua
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -1,345 +1,4 @@
-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
-
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_publishDiagnostics
-M['textDocument/publishDiagnostics'] = function(_, _, result)
- if not result then return end
- local uri = result.uri
- local bufnr = vim.uri_to_bufnr(uri)
- if not bufnr then
- err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri)
- return
- end
-
- -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
- -- The diagnostic's severity. Can be omitted. If omitted it is up to the
- -- client to interpret diagnostics as error, warning, info or hint.
- -- TODO: Replace this with server-specific heuristics to infer severity.
- for _, diagnostic in ipairs(result.diagnostics) do
- if diagnostic.severity == nil then
- diagnostic.severity = protocol.DiagnosticSeverity.Error
- end
- end
-
- util.buf_clear_diagnostics(bufnr)
-
- -- Always save the diagnostics, even if the buf is not loaded.
- -- Language servers may report compile or build errors via diagnostics
- -- Users should be able to find these, even if they're in files which
- -- are not loaded.
- util.buf_diagnostics_save_positions(bufnr, result.diagnostics)
-
- -- Unloaded buffers should not handle diagnostics.
- -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen.
- -- This should trigger another publish of the diagnostics.
- --
- -- In particular, this stops a ton of spam when first starting a server for current
- -- unloaded buffers.
- if not api.nvim_buf_is_loaded(bufnr) then
- return
- end
- util.buf_diagnostics_underline(bufnr, result.diagnostics)
- util.buf_diagnostics_virtual_text(bufnr, result.diagnostics)
- util.buf_diagnostics_signs(bufnr, result.diagnostics)
- vim.api.nvim_command("doautocmd User LspDiagnosticsChanged")
-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_callback = 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_callback
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
-M['workspace/symbol'] = symbol_callback
-
---@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 callback 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_callback(_, 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_callback
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
-M['textDocument/definition'] = location_callback
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
-M['textDocument/typeDefinition'] = location_callback
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
-M['textDocument/implementation'] = location_callback
-
---@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 callback
- -- 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_callback = 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_callback('from')
-
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy/outgoingCalls
-M['callHierarchy/outgoingCalls'] = make_call_hierarchy_callback('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 h