diff options
Diffstat (limited to 'runtime/lua/vim/lsp.lua')
-rw-r--r-- | runtime/lua/vim/lsp.lua | 535 |
1 files changed, 284 insertions, 251 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index a541b63ee9..e99a7c282c 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1,58 +1,58 @@ local if_nil = vim.F.if_nil -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' -local util = require 'vim.lsp.util' -local sync = require 'vim.lsp.sync' +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') +local util = require('vim.lsp.util') +local sync = require('vim.lsp.sync') local vim = vim -local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option - = vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option +local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option = + vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option local uv = vim.loop local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend local validate = vim.validate local lsp = { - protocol = protocol; + protocol = protocol, - handlers = default_handlers; + handlers = default_handlers, - buf = require'vim.lsp.buf'; - diagnostic = require'vim.lsp.diagnostic'; - codelens = require'vim.lsp.codelens'; - util = util; + buf = require('vim.lsp.buf'), + diagnostic = require('vim.lsp.diagnostic'), + codelens = require('vim.lsp.codelens'), + util = util, -- Allow raw RPC access. - rpc = lsp_rpc; + rpc = lsp_rpc, -- Export these directly from rpc. - rpc_response_error = lsp_rpc.rpc_response_error; + rpc_response_error = lsp_rpc.rpc_response_error, } -- maps request name to the required server_capability in the client. lsp._request_name_to_capability = { - ['textDocument/hover'] = { 'hoverProvider' }; - ['textDocument/signatureHelp'] = { 'signatureHelpProvider' }; - ['textDocument/definition'] = { 'definitionProvider' }; - ['textDocument/implementation'] = { 'implementationProvider' }; - ['textDocument/declaration'] = { 'declarationProvider' }; - ['textDocument/typeDefinition'] = { 'typeDefinitionProvider' }; - ['textDocument/documentSymbol'] = { 'documentSymbolProvider' }; - ['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' }; - ['textDocument/rename'] = { 'renameProvider' }; - ['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider'} ; - ['textDocument/codeAction'] = { 'codeActionProvider' }; - ['textDocument/codeLens'] = { 'codeLensProvider' }; - ['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' }; - ['workspace/executeCommand'] = { 'executeCommandProvider' }; - ['workspace/symbol'] = { 'workspaceSymbolProvider' }; - ['textDocument/references'] = { 'referencesProvider' }; - ['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' }; - ['textDocument/formatting'] = { 'documentFormattingProvider' }; - ['textDocument/completion'] = { 'completionProvider' }; - ['textDocument/documentHighlight'] = { 'documentHighlightProvider' }; + ['textDocument/hover'] = { 'hoverProvider' }, + ['textDocument/signatureHelp'] = { 'signatureHelpProvider' }, + ['textDocument/definition'] = { 'definitionProvider' }, + ['textDocument/implementation'] = { 'implementationProvider' }, + ['textDocument/declaration'] = { 'declarationProvider' }, + ['textDocument/typeDefinition'] = { 'typeDefinitionProvider' }, + ['textDocument/documentSymbol'] = { 'documentSymbolProvider' }, + ['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' }, + ['textDocument/rename'] = { 'renameProvider' }, + ['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider' }, + ['textDocument/codeAction'] = { 'codeActionProvider' }, + ['textDocument/codeLens'] = { 'codeLensProvider' }, + ['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' }, + ['workspace/executeCommand'] = { 'executeCommandProvider' }, + ['workspace/symbol'] = { 'workspaceSymbolProvider' }, + ['textDocument/references'] = { 'referencesProvider' }, + ['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' }, + ['textDocument/formatting'] = { 'documentFormattingProvider' }, + ['textDocument/completion'] = { 'completionProvider' }, + ['textDocument/documentHighlight'] = { 'documentHighlightProvider' }, } -- TODO improve handling of scratch buffers with LSP attached. @@ -62,8 +62,8 @@ lsp._request_name_to_capability = { --- ---@param {...} (List of strings) List to write to the buffer local function err_message(...) - nvim_err_writeln(table.concat(vim.tbl_flatten{...})) - nvim_command("redraw") + nvim_err_writeln(table.concat(vim.tbl_flatten({ ... }))) + nvim_command('redraw') end ---@private @@ -73,7 +73,7 @@ end ---buffer if not given. ---@returns bufnr (number) Number of requested buffer local function resolve_bufnr(bufnr) - validate { bufnr = { bufnr, 'n', true } } + validate({ bufnr = { bufnr, 'n', true } }) if bufnr == nil or bufnr == 0 then return vim.api.nvim_get_current_buf() end @@ -85,7 +85,10 @@ end --- supported in any of the servers registered for the current buffer. ---@param method (string) name of the method function lsp._unsupported_method(method) - local msg = string.format("method %s is not supported by any of the servers registered for the current buffer", method) + local msg = string.format( + 'method %s is not supported by any of the servers registered for the current buffer', + method + ) log.warn(msg) return msg end @@ -96,23 +99,29 @@ end ---@param filename (string) path to check ---@returns true if {filename} exists and is a directory, false otherwise local function is_dir(filename) - validate{filename={filename,'s'}} + validate({ filename = { filename, 's' } }) local stat = uv.fs_stat(filename) return stat and stat.type == 'directory' or false end -local wait_result_reason = { [-1] = "timeout"; [-2] = "interrupted"; [-3] = "error" } +local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' } local valid_encodings = { - ["utf-8"] = 'utf-8'; ["utf-16"] = 'utf-16'; ["utf-32"] = 'utf-32'; - ["utf8"] = 'utf-8'; ["utf16"] = 'utf-16'; ["utf32"] = 'utf-32'; - UTF8 = 'utf-8'; UTF16 = 'utf-16'; UTF32 = 'utf-32'; + ['utf-8'] = 'utf-8', + ['utf-16'] = 'utf-16', + ['utf-32'] = 'utf-32', + ['utf8'] = 'utf-8', + ['utf16'] = 'utf-16', + ['utf32'] = 'utf-32', + UTF8 = 'utf-8', + UTF16 = 'utf-16', + UTF32 = 'utf-32', } local format_line_ending = { - ["unix"] = '\n', - ["dos"] = '\r\n', - ["mac"] = '\r', + ['unix'] = '\n', + ['dos'] = '\r\n', + ['mac'] = '\r', } ---@private @@ -138,10 +147,10 @@ local uninitialized_clients = {} ---@private local function for_each_buffer_client(bufnr, fn, restrict_client_ids) - validate { - fn = { fn, 'f' }; - restrict_client_ids = { restrict_client_ids, 't' , true}; - } + validate({ + fn = { fn, 'f' }, + restrict_client_ids = { restrict_client_ids, 't', true }, + }) bufnr = resolve_bufnr(bufnr) local client_ids = all_buffer_active_clients[bufnr] if not client_ids or tbl_isempty(client_ids) then @@ -169,9 +178,13 @@ end -- Error codes to be used with `on_error` from |vim.lsp.start_client|. -- Can be used to look up the string from a the number or the number -- from the string. -lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_reverse_lookup { - ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1; -}) +lsp.client_errors = tbl_extend( + 'error', + lsp_rpc.client_errors, + vim.tbl_add_reverse_lookup({ + ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1, + }) +) ---@private --- Normalizes {encoding} to valid LSP encoding names. @@ -179,11 +192,11 @@ lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_rever ---@param encoding (string) Encoding to normalize ---@returns (string) normalized encoding name local function validate_encoding(encoding) - validate { - encoding = { encoding, 's' }; - } + validate({ + encoding = { encoding, 's' }, + }) return valid_encodings[encoding:lower()] - or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding)) + or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding)) end ---@internal @@ -194,16 +207,19 @@ end ---@returns (string) the command ---@returns (list of strings) its arguments function lsp._cmd_parts(input) - vim.validate{cmd={ + vim.validate({ cmd = { input, - function() return vim.tbl_islist(input) end, - "list"}} + function() + return vim.tbl_islist(input) + end, + 'list', + } }) local cmd = input[1] local cmd_args = {} -- Don't mutate our input. for i, v in ipairs(input) do - vim.validate{["cmd argument"]={v, "s"}} + vim.validate({ ['cmd argument'] = { v, 's' } }) if i > 1 then table.insert(cmd_args, v) end @@ -233,31 +249,29 @@ end --- ---@see |vim.lsp.start_client()| local function validate_client_config(config) - validate { - config = { config, 't' }; - } - validate { - 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 }; - detached = { config.detached, "b", true }; - name = { config.name, 's', true }; - on_error = { config.on_error, "f", true }; - on_exit = { config.on_exit, "f", true }; - on_init = { config.on_init, "f", true }; - settings = { config.settings, "t", true }; - commands = { config.commands, 't', true }; - before_init = { config.before_init, "f", true }; - offset_encoding = { config.offset_encoding, "s", true }; - flags = { config.flags, "t", true }; - get_language_id = { config.get_language_id, "f", true }; - } + validate({ + config = { config, 't' }, + }) + validate({ + 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 }, + detached = { config.detached, 'b', true }, + name = { config.name, 's', true }, + on_error = { config.on_error, 'f', true }, + on_exit = { config.on_exit, 'f', true }, + on_init = { config.on_init, 'f', true }, + settings = { config.settings, 't', true }, + commands = { config.commands, 't', true }, + before_init = { config.before_init, 'f', true }, + offset_encoding = { config.offset_encoding, 's', true }, + flags = { config.flags, 't', true }, + get_language_id = { config.get_language_id, 'f', true }, + }) assert( - (not config.flags - or not config.flags.debounce_text_changes - or type(config.flags.debounce_text_changes) == 'number'), - "flags.debounce_text_changes must be a number with the debounce time in milliseconds" + (not config.flags or not config.flags.debounce_text_changes or type(config.flags.debounce_text_changes) == 'number'), + 'flags.debounce_text_changes must be a number with the debounce time in milliseconds' ) local cmd, cmd_args = lsp._cmd_parts(config.cmd) @@ -267,9 +281,9 @@ local function validate_client_config(config) end return { - cmd = cmd; - cmd_args = cmd_args; - offset_encoding = offset_encoding; + cmd = cmd, + cmd_args = cmd_args, + offset_encoding = offset_encoding, } end @@ -329,14 +343,15 @@ do function changetracking.init(client, bufnr) local use_incremental_sync = ( if_nil(client.config.flags.allow_incremental_sync, true) - and vim.tbl_get(client.server_capabilities, "textDocumentSync", "change") == protocol.TextDocumentSyncKind.Incremental + and vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change') + == protocol.TextDocumentSyncKind.Incremental ) local state = state_by_client[client.id] if not state then state = { - buffers = {}; + buffers = {}, debounce = client.config.flags.debounce_text_changes or 150, - use_incremental_sync = use_incremental_sync; + use_incremental_sync = use_incremental_sync, } state_by_client[client.id] = state end @@ -405,7 +420,6 @@ do ---@private function changetracking.prepare(bufnr, firstline, lastline, new_lastline) local incremental_changes = function(client, buf_state) - local prev_lines = buf_state.lines local curr_lines = buf_state.lines_tmp @@ -426,7 +440,14 @@ do local line_ending = buf_get_line_ending(bufnr) local incremental_change = sync.compute_diff( - buf_state.lines, curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending) + buf_state.lines, + curr_lines, + firstline, + lastline, + new_lastline, + client.offset_encoding or 'utf-16', + line_ending + ) -- Double-buffering of lines tables is used to reduce the load on the garbage collector. -- At this point the prev_lines table is useless, but its internal storage has already been allocated, @@ -443,12 +464,14 @@ do end local full_changes = once(function() return { - text = buf_get_full_text(bufnr); - }; + text = buf_get_full_text(bufnr), + } end) local uri = vim.uri_from_bufnr(bufnr) return function(client) - if vim.tbl_get(client.server_capabilities, "textDocumentSync", "change") == protocol.TextDocumentSyncKind.None then + if + vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change') == protocol.TextDocumentSyncKind.None + then return end local state = state_by_client[client.id] @@ -467,7 +490,7 @@ do return end local changes = state.use_incremental_sync and buf_state.pending_changes or { full_changes() } - client.notify("textDocument/didChange", { + client.notify('textDocument/didChange', { textDocument = { uri = uri, version = util.buf_versions[bufnr], @@ -519,7 +542,6 @@ do end end - ---@private --- Default handler for the 'textDocument/didOpen' LSP notification. --- @@ -527,7 +549,7 @@ end ---@param client Client object local function text_document_did_open_handler(bufnr, client) changetracking.init(client, bufnr) - if not vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then + if not vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then return end if not vim.api.nvim_buf_is_loaded(bufnr) then @@ -537,11 +559,11 @@ local function text_document_did_open_handler(bufnr, client) local params = { textDocument = { - version = 0; - uri = vim.uri_from_bufnr(bufnr); - languageId = client.config.get_language_id(bufnr, filetype); - text = buf_get_full_text(bufnr); - } + version = 0, + uri = vim.uri_from_bufnr(bufnr), + languageId = client.config.get_language_id(bufnr, filetype), + text = buf_get_full_text(bufnr), + }, } client.notify('textDocument/didOpen', params) util.buf_versions[bufnr] = params.textDocument.version @@ -763,13 +785,15 @@ function lsp.start_client(config) -- By default, get_language_id just returns the exact filetype it is passed. -- It is possible to pass in something that will calculate a different filetype, -- to be sent by the client. - config.get_language_id = config.get_language_id or function(_, filetype) return filetype end + config.get_language_id = config.get_language_id or function(_, filetype) + return filetype + end local client_id = next_client_id() local handlers = config.handlers or {} local name = config.name or tostring(client_id) - local log_prefix = string.format("LSP[%s]", name) + local log_prefix = string.format('LSP[%s]', name) local dispatch = {} @@ -794,7 +818,7 @@ function lsp.start_client(config) local handler = resolve_handler(method) if handler then -- Method name is provided here for convenience. - handler(nil, params, {method=method, client_id=client_id}) + handler(nil, params, { method = method, client_id = client_id }) end end @@ -807,10 +831,10 @@ function lsp.start_client(config) local _ = log.trace() and log.trace('server_request', method, params) local handler = resolve_handler(method) if handler then - local _ = log.trace() and log.trace("server_request: found handler for", method) - return handler(nil, params, {method=method, client_id=client_id}) + local _ = log.trace() and log.trace('server_request: found handler for', method) + return handler(nil, params, { method = method, client_id = client_id }) end - local _ = log.warn() and log.warn("server_request: no handler found for", method) + local _ = log.warn() and log.warn('server_request: no handler found for', method) return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound) end @@ -822,12 +846,12 @@ function lsp.start_client(config) ---@see |vim.lsp.rpc.client_errors| for possible errors. Use ---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name. function dispatch.on_error(code, err) - local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = 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 local status, usererr = pcall(config.on_error, code, err) if not status then - local _ = log.error() and log.error(log_prefix, "user on_error failed", { err = usererr }) + local _ = log.error() and log.error(log_prefix, 'user on_error failed', { err = usererr }) err_message(log_prefix, ' user on_error failed: ', tostring(usererr)) end end @@ -853,7 +877,7 @@ function lsp.start_client(config) end if code ~= 0 or (signal ~= 0 and signal ~= 15) then - local msg = string.format("Client %s quit with exit code %s and signal %s", client_id, code, signal) + local msg = string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal) vim.schedule(function() vim.notify(msg, vim.log.levels.WARN) end) @@ -862,38 +886,41 @@ function lsp.start_client(config) -- Start the RPC client. local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, { - cwd = config.cmd_cwd; - env = config.cmd_env; - detached = config.detached; + cwd = config.cmd_cwd, + env = config.cmd_env, + detached = config.detached, }) -- Return nil if client fails to start - if not rpc then return end + if not rpc then + return + end local client = { - id = client_id; - name = name; - rpc = rpc; - offset_encoding = offset_encoding; - config = config; - attached_buffers = {}; + id = client_id, + name = name, + rpc = rpc, + offset_encoding = offset_encoding, + config = config, + attached_buffers = {}, - handlers = handlers; - commands = config.commands or {}; + handlers = handlers, + commands = config.commands or {}, - requests = {}; + requests = {}, -- for $/progress report - messages = { name = name, messages = {}, progress = {}, status = {} }; + messages = { name = name, messages = {}, progress = {}, status = {} }, } - -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes. - uninitialized_clients[client_id] = client; + uninitialized_clients[client_id] = client ---@private local function initialize() local valid_traces = { - off = 'off'; messages = 'messages'; verbose = 'verbose'; + off = 'off', + messages = 'messages', + verbose = 'verbose', } local version = vim.version() @@ -902,10 +929,12 @@ function lsp.start_client(config) local root_path if config.workspace_folders or config.root_dir then if config.root_dir and not config.workspace_folders then - workspace_folders = {{ - uri = vim.uri_from_fname(config.root_dir); - name = string.format("%s", config.root_dir); - }}; + workspace_folders = { + { + uri = vim.uri_from_fname(config.root_dir), + name = string.format('%s', config.root_dir), + }, + } else workspace_folders = config.workspace_folders end @@ -922,41 +951,41 @@ function lsp.start_client(config) -- the process has not been started by another process. If the parent -- process is not alive then the server should exit (see exit notification) -- its process. - processId = uv.getpid(); + processId = uv.getpid(), -- Information about the client -- since 3.15.0 clientInfo = { - name = "Neovim", - version = string.format("%s.%s.%s", version.major, version.minor, version.patch) - }; + name = 'Neovim', + version = string.format('%s.%s.%s', version.major, version.minor, version.patch), + }, -- The rootPath of the workspace. Is null if no folder is open. -- -- @deprecated in favour of rootUri. - rootPath = root_path or vim.NIL; + rootPath = root_path or vim.NIL, -- The rootUri of the workspace. Is null if no folder is open. If both -- `rootPath` and `rootUri` are set `rootUri` wins. - rootUri = root_uri or vim.NIL; + rootUri = root_uri or vim.NIL, -- The workspace folders configured in the client when the server starts. -- This property is only available if the client supports workspace folders. -- It can be `null` if the client supports workspace folders but none are -- configured. - workspaceFolders = workspace_folders or vim.NIL; + workspaceFolders = workspace_folders or vim.NIL, -- User provided initialization options. - initializationOptions = config.init_options; + initializationOptions = config.init_options, -- The capabilities provided by the client (editor or tool) - capabilities = config.capabilities or protocol.make_client_capabilities(); + capabilities = config.capabilities or protocol.make_client_capabilities(), -- The initial trace setting. If omitted trace is disabled ("off"). -- trace = "off" | "messages" | "verbose"; - trace = valid_traces[config.trace] or 'off'; + trace = valid_traces[config.trace] or 'off', } if config.before_init then -- TODO(ashkan) handle errors here. pcall(config.before_init, initialize_params, config) end - local _ = log.trace() and log.trace(log_prefix, "initialize_params", initialize_params) + local _ = log.trace() and log.trace(log_prefix, 'initialize_params', initialize_params) rpc.request('initialize', initialize_params, function(init_err, result) assert(not init_err, tostring(init_err)) - assert(result, "server sent empty result") + assert(result, 'server sent empty result') rpc.notify('initialized', vim.empty_dict()) client.initialized = true uninitialized_clients[client_id] = nil @@ -973,10 +1002,13 @@ function lsp.start_client(config) local mt = {} mt.__index = function(table, key) if key == 'resolved_capabilities' then - vim.notify_once("[LSP] Accessing client.resolved_capabilities is deprecated, " .. - "update your plugins or configuration to access client.server_capabilities instead." .. - "The new key/value pairs in server_capabilities directly match those " .. - "defined in the language server protocol", vim.log.levels.WARN) + vim.notify_once( + '[LSP] Accessing client.resolved_capabilities is deprecated, ' + .. 'update your plugins or configuration to access client.server_capabilities instead.' + .. 'The new key/value pairs in server_capabilities directly match those ' + .. 'defined in the language server protocol', + vim.log.levels.WARN + ) rawset(table, key, protocol._resolve_capabilities_compat(client.server_capabilities)) return rawget(table, key) else @@ -1004,7 +1036,8 @@ function lsp.start_client(config) pcall(handlers.on_error, lsp.client_errors.ON_INIT_CALLBACK_ERROR, err) end end - local _ = log.info() and log.info(log_prefix, "server_capabilities", { server_capabilities = client.server_capabilities }) + local _ = log.info() + and log.info(log_prefix, 'server_capabilities', { server_capabilities = client.server_capabilities }) -- Only assign after initialized. active_clients[client_id] = client @@ -1039,22 +1072,22 @@ function lsp.start_client(config) 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)) + or error(string.format('not found: %q request handler for client %q.', method, client.name)) end -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state changetracking.flush(client, bufnr) bufnr = resolve_bufnr(bufnr) - local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr) + local _ = log.debug() and log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr) local success, request_id = rpc.request(method, params, function(err, result) - handler(err, result, {method=method, client_id=client_id, bufnr=bufnr, params=params}) + handler(err, result, { method = method, client_id = client_id, bufnr = bufnr, params = params }) end, function(request_id) client.requests[request_id] = nil - nvim_command("doautocmd <nomodeline> User LspRequest") + nvim_command('doautocmd <nomodeline> User LspRequest') end) if success then - client.requests[request_id] = { type='pending', bufnr=bufnr, method=method } - nvim_command("doautocmd <nomodeline> User LspRequest") + client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method } + nvim_command('doautocmd <nomodeline> User LspRequest') end return success, request_id @@ -1081,9 +1114,10 @@ function lsp.start_client(config) request_result = { err = err, result = result } end - local success, request_id = client.request(method, params, _sync_handler, - bufnr) - if not success then return nil end + local success, request_id = client.request(method, params, _sync_handler, bufnr) + if not success then + return nil + end local wait_result, reason = vim.wait(timeout_ms or 1000, function() return request_result ~= nil @@ -1118,13 +1152,13 @@ function lsp.start_client(config) ---@returns true if any client returns true; false otherwise ---@see |vim.lsp.client.notify()| function client.cancel_request(id) - validate{id = {id, 'n'}} + validate({ id = { id, 'n' } }) local request = client.requests[id] if request and request.type == 'pending' then request.type = 'cancel' - nvim_command("doautocmd <nomodeline> User LspRequest") + nvim_command('doautocmd <nomodeline> User LspRequest') end - return rpc.notify("$/cancelRequest", { id = id }) + return rpc.notify('$/cancelRequest', { id = id }) end -- Track this so that we can escalate automatically if we've already tried a @@ -1139,7 +1173,6 @@ function lsp.start_client(config) --- ---@param force (bool, optional) function client.stop(force) - lsp.diagnostic.reset(client_id, all_buffer_active_clients) changetracking.reset(client_id) for _, client_ids in pairs(all_buffer_active_clients) do @@ -1150,7 +1183,7 @@ function lsp.start_client(config) if handle:is_closing() then return end - if force or (not client.initialized) or graceful_shutdown_failed then + if force or not client.initialized or graceful_shutdown_failed then handle:kill(15) return end @@ -1198,7 +1231,6 @@ end local text_document_did_change_handler do text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline) - -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then return true @@ -1215,17 +1247,17 @@ function lsp._text_document_did_save_handler(bufnr) local uri = vim.uri_from_bufnr(bufnr) local text = once(buf_get_full_text) for_each_buffer_client(bufnr, function(client) - local save_capability = vim.tbl_get(client.server_capabilities, "textDocumentSync", "save") + local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save') if save_capability then local included_text - if type(save_capability) == "table" and save_capability.includeText then + if type(save_capability) == 'table' and save_capability.includeText then included_text = text(bufnr) end client.notify('textDocument/didSave', { textDocument = { - uri = uri; - }; - text = included_text; + uri = uri, + }, + text = included_text, }) end end) @@ -1239,15 +1271,13 @@ end ---@param bufnr (number) Buffer handle, or 0 for current ---@param client_id (number) Client id function lsp.buf_attach_client(bufnr, client_id) - validate { - bufnr = {bufnr, 'n', true}; - client_id = {client_id, 'n'}; - } + validate({ + bufnr = { bufnr, 'n', true }, + client_id = { client_id, 'n' }, + }) bufnr = resolve_bufnr(bufnr) if not vim.api.nvim_buf_is_loaded(bufnr) then - local _ = log.warn() and log.warn( - string.format("buf_attach_client called on unloaded buffer (id: %d): ", bufnr) - ) + local _ = log.warn() and log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr)) return false end local buffer_client_ids = all_buffer_active_clients[bufnr] @@ -1266,36 +1296,38 @@ function lsp.buf_attach_client(bufnr, client_id) vim.api.nvim_exec(string.format(buf_did_save_autocommand, client_id, bufnr, bufnr), false) -- First time, so attach and set up stuff. vim.api.nvim_buf_attach(bufnr, false, { - on_lines = text_document_did_change_handler; + on_lines = text_document_did_change_handler, on_reload = function() - local params = { textDocument = { uri = uri; } } + local params = { textDocument = { uri = uri } } for_each_buffer_client(bufnr, function(client, _) changetracking.reset_buf(client, bufnr) - if vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then client.notify('textDocument/didClose', params) end text_document_did_open_handler(bufnr, client) end) - end; + end, on_detach = function() - local params = { textDocument = { uri = uri; } } + local params = { textDocument = { uri = uri } } for_each_buffer_client(bufnr, function(client, _) changetracking.reset_buf(client, bufnr) - if vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then client.notify('textDocument/didClose', params) end end) util.buf_versions[bufnr] = nil all_buffer_active_clients[bufnr] = nil - end; + end, -- TODO if we know all of the potential clients ahead of time, then we -- could conditionally set this. -- utf_sizes = size_index > 1; - utf_sizes = true; + utf_sizes = true, }) end - if buffer_client_ids[client_id] then return end + if buffer_client_ids[client_id] then + return + end -- This is our first time attaching this client to this buffer. buffer_client_ids[client_id] = true @@ -1315,25 +1347,23 @@ end ---@param bufnr number Buffer handle, or 0 for current ---@param client_id number Client id function lsp.buf_detach_client(bufnr, client_id) - validate { - bufnr = {bufnr, 'n', true}; - client_id = {client_id, 'n'}; - } + validate({ + bufnr = { bufnr, 'n', true }, + client_id = { client_id, 'n' }, + }) bufnr = resolve_bufnr(bufnr) local client = lsp.get_client_by_id(client_id) if not client or not client.attached_buffers[bufnr] then - vim.notify( - string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr) - ) + vim.notify(string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr)) return end changetracking.reset_buf(client, bufnr) - if vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then local uri = vim.uri_from_bufnr(bufnr) - local params = { textDocument = { uri = uri; } } + local params = { textDocument = { uri = uri } } client.notify('textDocument/didClose', params) end @@ -1349,7 +1379,6 @@ function lsp.buf_detach_client(bufnr, client_id) vim.diagnostic.reset(namespace, bufnr) vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id)) - end --- Checks if a buffer is attached for a particular client. @@ -1394,7 +1423,7 @@ end ---@param client_id client id or |vim.lsp.client| object, or list thereof ---@param force boolean (optional) shutdown forcefully function lsp.stop_client(client_id, force) - local ids = type(client_id) == 'table' and client_id or {client_id} + local ids = type(client_id) == 'table' and client_id or { client_id } for _, id in ipairs(ids) do if type(id) == 'table' and id.stop ~= nil then id.stop(force) @@ -1414,7 +1443,7 @@ function lsp.get_active_clients() end function lsp._vim_exit_handler() - log.info("exit_handler", active_clients) + log.info('exit_handler', active_clients) for _, client in pairs(uninitialized_clients) do client.stop(true) end @@ -1466,8 +1495,7 @@ function lsp._vim_exit_handler() end end -nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()") - +nvim_command('autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()') --- Sends an async request for all active clients attached to the --- buffer. @@ -1483,11 +1511,11 @@ nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()") --- - 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, handler) - validate { - bufnr = { bufnr, 'n', true }; - method = { method, 's' }; - handler = { handler, 'f', true }; - } + validate({ + bufnr = { bufnr, 'n', true }, + method = { method, 's' }, + handler = { handler, 'f', true }, + }) local supported_clients = {} local method_supported = false @@ -1501,18 +1529,18 @@ function lsp.buf_request(bufnr, method, params, handler) -- if has client but no clients support the given method, notify the user if not tbl_isempty(all_buffer_active_clients[resolve_bufnr(bufnr)] or {}) and not method_supported then vim.notify(lsp._unsupported_method(method), vim.log.levels.ERROR) - vim.api.nvim_command("redraw") + vim.api.nvim_command('redraw') return {}, function() end end local client_request_ids = {} for_each_buffer_client(bufnr, function(client, client_id, 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. - if request_success then - client_request_ids[client_id] = request_id - end + 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. + if request_success then + client_request_ids[client_id] = request_id + end end, supported_clients) local function _cancel_all_requests() @@ -1543,7 +1571,7 @@ function lsp.buf_request_all(bufnr, method, params, callback) local result_count = 0 local expected_result_count = 0 - local set_expected_result_count = once(function () + local set_expected_result_count = once(function() for_each_buffer_client(bufnr, function(client) if client.supports_method(method) then expected_result_count = expected_result_count + 1 @@ -1607,18 +1635,19 @@ end --- ---@returns true if any client returns true; false otherwise function lsp.buf_notify(bufnr, method, params) - validate { - bufnr = { bufnr, 'n', true }; - method = { method, 's' }; - } + validate({ + bufnr = { bufnr, 'n', true }, + method = { method, 's' }, + }) local resp = false for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr) - if client.rpc.notify(method, params) then resp = true end + if client.rpc.notify(method, params) then + resp = true + end end) return resp end - ---@private local function adjust_start_col(lnum, line, items, encoding) local min_start_char = nil @@ -1650,7 +1679,7 @@ end --- - findstart=0: column where the completion starts, or -2 or -3 --- - findstart=1: list of matches (actually just calls |complete()|) function lsp.omnifunc(findstart, base) - local _ = log.debug() and log.debug("omnifunc.findstart", { findstart = findstart, base = base }) + local _ = log.debug() and log.debug('omnifunc.findstart', { findstart = findstart, base = base }) local bufnr = resolve_bufnr() local has_buffer_clients = not tbl_isempty(all_buffer_active_clients[bufnr] or {}) @@ -1663,12 +1692,12 @@ function lsp.omnifunc(findstart, base) end -- Then, perform standard completion request - local _ = log.info() and log.info("base ", base) + local _ = log.info() and log.info('base ', base) local pos = vim.api.nvim_win_get_cursor(0) local line = vim.api.nvim_get_current_line() local line_to_cursor = line:sub(1, pos[2]) - local _ = log.trace() and log.trace("omnifunc.line", pos, line) + local _ = log.trace() and log.trace('omnifunc.line', pos, line) -- Get the start position of the current keyword local textMatch = vim.fn.match(line_to_cursor, '\\k*$') @@ -1677,7 +1706,9 @@ function lsp.omnifunc(findstart, base) local items = {} lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result, ctx) - if err or not result or vim.fn.mode() ~= "i" then return end + if err or not result or vim.fn.mode() ~= 'i' then + return + end -- Completion response items may be relative to a position different than `textMatch`. -- Concrete example, with sumneko/lua-language-server: @@ -1723,7 +1754,7 @@ function lsp.formatexpr(opts) opts = opts or {} local timeout_ms = opts.timeout_ms or 500 - if vim.tbl_contains({'i', 'R', 'ic', 'ix'}, vim.fn.mode()) then + if vim.tbl_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then -- `formatexpr` is also called when exceeding `textwidth` in insert mode -- fall back to internal formatting return 1 @@ -1734,14 +1765,14 @@ function lsp.formatexpr(opts) if start_line > 0 and end_line > 0 then local params = { - textDocument = util.make_text_document_params(); + textDocument = util.make_text_document_params(), range = { - start = { line = start_line - 1; character = 0; }; - ["end"] = { line = end_line - 1; character = 0; }; - }; - }; + start = { line = start_line - 1, character = 0 }, + ['end'] = { line = end_line - 1, character = 0 }, + }, + } params.options = util.make_formatting_params().options - local client_results = vim.lsp.buf_request_sync(0, "textDocument/rangeFormatting", params, timeout_ms) + local client_results = vim.lsp.buf_request_sync(0, 'textDocument/rangeFormatting', params, timeout_ms) -- Apply the text edits from one and only one of the clients. for client_id, response in pairs(client_results) do @@ -1785,11 +1816,11 @@ end ---@param bufnr (optional, number): Buffer handle, or 0 for current function lsp.buf_get_clients(bufnr) bufnr = resolve_bufnr(bufnr) - local result = {} - for_each_buffer_client(bufnr, function(client, client_id) - result[client_id] = client - end) - return result + local result = {} + for_each_buffer_client(bufnr, function(client, client_id) + result[client_id] = client + end) + return result end -- Log level dictionary with reverse lookup as well. @@ -1815,7 +1846,7 @@ function lsp.set_log_level(level) if type(level) == 'string' or type(level) == 'number' then log.set_level(level) else - error(string.format("Invalid log level: %q", level)) + error(string.format('Invalid log level: %q', level)) end end @@ -1845,7 +1876,7 @@ end ---@param override_config (table) Table containing the keys to override behavior of the {handler} function lsp.with(handler, override_config) return function(err, result, ctx, config) - return handler(err, result, ctx, vim.tbl_deep_extend("force", config or {}, override_config)) + return handler(err, result, ctx, vim.tbl_deep_extend('force', config or {}, override_config)) end end @@ -1860,12 +1891,16 @@ function lsp._with_extend(name, options, user_config) 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)) - ))) + 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 @@ -1880,7 +1915,6 @@ function lsp._with_extend(name, options, user_config) return resulting_config end - --- Registry for client side commands. --- This is an extension point for plugins to handle custom commands which are --- not part of the core language server protocol specification. @@ -1902,12 +1936,11 @@ end --- The second argument is the `ctx` of |lsp-handler| lsp.commands = setmetatable({}, { __newindex = function(tbl, key, value) - assert(type(key) == 'string', "The key for commands in `vim.lsp.commands` must be a string") - assert(type(value) == 'function', "Command added to `vim.lsp.commands` must be a function") + assert(type(key) == 'string', 'The key for commands in `vim.lsp.commands` must be a string') + assert(type(value) == 'function', 'Command added to `vim.lsp.commands` must be a function') rawset(tbl, key, value) - end; + end, }) - return lsp -- vim:sw=2 ts=2 et |