local vim = vim local validate = vim.validate local vfn = vim.fn local util = require 'vim.lsp.util' local M = {} --@private --- Returns nil if {status} is false or nil, otherwise returns the rest of the --- arguments. local function ok_or_nil(status, ...) if not status then return end return ... end --@private --- Swallows errors. --- --@param fn Function to run --@param ... Function arguments --@returns Result of `fn(...)` if there are no errors, otherwise nil. --- Returns nil if errors occur during {fn}, otherwise returns local function npcall(fn, ...) return ok_or_nil(pcall(fn, ...)) end --@private --- Sends an async request to all active clients attached to the current --- buffer. --- --@param method (string) LSP method name --@param params (optional, table) Parameters to send to the server --@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. --- - Function which can be used to cancel all the requests. You could instead --- iterate all clients and call their `cancel_request()` methods. --- --@see |vim.lsp.buf_request()| local function request(method, params, handler) validate { method = {method, 's'}; handler = {handler, 'f', true}; } return vim.lsp.buf_request(0, method, params, handler) end --- Checks whether the language servers attached to the current buffer are --- ready. --- --@returns `true` if server responds. function M.server_ready() return not not vim.lsp.buf_notify(0, "window/progress", {}) end --- Displays hover information about the symbol under the cursor in a floating --- window. Calling the function twice will jump into the floating window. function M.hover() local params = util.make_position_params() request('textDocument/hover', params) 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() request('textDocument/declaration', params) end --- Jumps to the definition of the symbol under the cursor. --- function M.definition() local params = util.make_position_params() request('textDocument/definition', params) end --- Jumps to the definition of the type of the symbol under the cursor. --- function M.type_definition() local params = util.make_position_params() request('textDocument/typeDefinition', params) end --- Lists all the implementations for the symbol under the cursor in the --- quickfix window. function M.implementation() local params = util.make_position_params() request('textDocument/implementation', params) end --- Displays signature information about the symbol under the cursor in a --- floating window. function M.signature_help() local params = util.make_position_params() request('textDocument/signatureHelp', params) end --- Retrieves the completion items at the current cursor position. Can only be --- called in Insert mode. --- --@param context (context support not yet implemented) Additional information --- about the context in which a completion was triggered (how it was triggered, --- and by which trigger character, if applicable) --- --@see |vim.lsp.protocol.constants.CompletionTriggerKind| function M.completion(context) local params = util.make_position_params() params.context = context return request('textDocument/completion', params) end --@private --- If there is more than one client that supports the given method, --- asks the user to select one. -- --@returns The client that the user selected or nil local function select_client(method) local clients = vim.tbl_values(vim.lsp.buf_get_clients()); clients = vim.tbl_filter(function (client) return client.supports_method(method) end, clients) -- better UX when choices are always in the same order (between restarts) table.sort(clients, function (a, b) return a.name < b.name end) if #clients > 1 then local choices = {} for k,v in pairs(clients) do table.insert(choices, string.format("%d %s", k, v.name)) end local user_choice = vim.fn.confirm( "Select a language server:", table.concat(choices, "\n"), 0, "Question" ) if user_choice == 0 then return nil end return clients[user_choice] elseif #clients < 1 then return nil else return clients[1] end end --- Formats the current buffer. --- --@param options (optional, table) Can be used to specify FormattingOptions. --- Some unspecified options will be automatically derived from the current --- Neovim options. -- --@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting function M.formatting(options) local client = select_client("textDocument/formatting") if client == nil then return end local params = util.make_formatting_params(options) return client.request("textDocument/formatting", params, nil, vim.api.nvim_get_current_buf()) end --- Performs |vim.lsp.buf.formatting()| synchronously. --- --- Useful for running on save, to make sure buffer is formatted prior to being --- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|. Example: --- ---
--- vim.api.nvim_command[[autocmd BufWritePre--- --@param options Table with valid `FormattingOptions` entries --@param timeout_ms (number) Request timeout --@see |vim.lsp.buf.formatting_seq_sync| function M.formatting_sync(options, timeout_ms) local client = select_client("textDocument/formatting") if client == nil then return end local params = util.make_formatting_params(options) local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf()) if result and result.result then util.apply_text_edits(result.result) elseif err then vim.notify("vim.lsp.buf.formatting_sync: " .. err, vim.log.levels.WARN) end end --- Formats the current buffer by sequentially requesting formatting from attached clients. --- --- Useful when multiple clients with formatting capability are attached. --- --- Since it's synchronous, can be used for running on save, to make sure buffer is formatted --- prior to being saved. {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method. --- Example: ---lua vim.lsp.buf.formatting_sync()]] ---
--- vim.api.nvim_command[[autocmd BufWritePre--- --@param options (optional, table) `FormattingOptions` entries --@param timeout_ms (optional, number) Request timeout --@param order (optional, table) List of client names. Formatting is requested from clients ---in the following order: first all clients that are not in the `order` list, then ---the remaining clients in the order as they occur in the `order` list. function M.formatting_seq_sync(options, timeout_ms, order) local clients = vim.tbl_values(vim.lsp.buf_get_clients()); -- sort the clients according to `order` for _, client_name in pairs(order or {}) do -- if the client exists, move to the end of the list for i, client in pairs(clients) do if client.name == client_name then table.insert(clients, table.remove(clients, i)) break end end end -- loop through the clients and make synchronous formatting requests for _, client in pairs(clients) do if client.resolved_capabilities.document_formatting then local params = util.make_formatting_params(options) local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf()) if result and result.result then util.apply_text_edits(result.result) elseif err then vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN) end end end end --- Formats a given range. --- --@param options Table with valid `FormattingOptions` entries. --@param start_pos ({number, number}, optional) mark-indexed position. ---Defaults to the start of the last visual selection. --@param end_pos ({number, number}, optional) mark-indexed position. ---Defaults to the end of the last visual selection. function M.range_formatting(options, start_pos, end_pos) local client = select_client("textDocument/rangeFormatting") if client == nil then return end local params = util.make_given_range_params(start_pos, end_pos) params.options = util.make_formatting_params(options).options return client.request("textDocument/rangeFormatting", params) end --- Renames all references to the symbol under the cursor. --- --@param new_name (string) If not provided, the user will be prompted for a new ---name using |input()|. function M.rename(new_name) -- TODO(ashkan) use prepareRename -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position. local params = util.make_position_params() new_name = new_name or npcall(vfn.input, "New Name: ", vfn.expand('lua vim.lsp.buf.formatting_seq_sync()]] ---
--- vim.api.nvim_command [[autocmd CursorHold--- --- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups --- to be defined or you won't be able to see the actual highlights. --- |LspReferenceText| --- |LspReferenceRead| --- |LspReferenceWrite| function M.document_highlight() local params = util.make_position_params() request('textDocument/documentHighlight', params) end --- Removes document highlights from current buffer. --- function M.clear_references() util.buf_clear_references() end --- Requests code actions from all clients and calls the handler exactly once --- with all aggregated results --@private local function code_action_request(params) local bufnr = vim.api.nvim_get_current_buf() local method = 'textDocument/codeAction' vim.lsp.buf_request_all(bufnr, method, params, function(results) local actions = {} for _, r in pairs(results) do vim.list_extend(actions, r.result or {}) end vim.lsp.handlers[method](nil, method, actions, nil, bufnr) end) end --- Selects a code action from the input list that is available at the current --- cursor position. -- --@param context: (table, optional) Valid `CodeActionContext` object --@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 = vim.lsp.diagnostic.get_line_diagnostics() } local params = util.make_range_params() params.context = context code_action_request(params) end --- Performs |vim.lsp.buf.code_action()| for a given range. --- --@param context: (table, optional) Valid `CodeActionContext` object --@param start_pos ({number, number}, optional) mark-indexed position. ---Defaults to the start of the last visual selection. --@param end_pos ({number, number}, optional) mark-indexed position. ---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 = vim.lsp.diagnostic.get_line_diagnostics() } local params = util.make_given_range_params(start_pos, end_pos) params.context = context code_action_request(params) end --- Executes an LSP server command. --- --@param command A valid `ExecuteCommandParams` object --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand function M.execute_command(command) validate { command = { command.command, 's' }, arguments = { command.arguments, 't', true } } request('workspace/executeCommand', command) end return M -- vim:sw=2 ts=2 etlua vim.lsp.buf.document_highlight()]] --- vim.api.nvim_command [[autocmd CursorHoldI lua vim.lsp.buf.document_highlight()]] --- vim.api.nvim_command [[autocmd CursorMoved lua vim.lsp.buf.clear_references()]] ---