diff options
| author | TJ DeVries <devries.timothyj@gmail.com> | 2020-08-25 09:52:22 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-08-25 09:52:22 -0400 |
| commit | 43202964f39eed4e28fd8ad6ad0ec79a1868c33f (patch) | |
| tree | da9abce08cca5b7502f0a5a6320eba52ee42b8e8 /runtime/lua/vim/lsp | |
| parent | 8a333afec53a48c90de7b0bde30f9db2d02d1898 (diff) | |
| parent | 9d9edebceb73657899256430ed3e2f49c92898a8 (diff) | |
| download | rneovim-43202964f39eed4e28fd8ad6ad0ec79a1868c33f.tar.gz rneovim-43202964f39eed4e28fd8ad6ad0ec79a1868c33f.tar.bz2 rneovim-43202964f39eed4e28fd8ad6ad0ec79a1868c33f.zip | |
Merge pull request #12708 from runiq/lsp-doc
Add docs for some methods in vim.lsp
Diffstat (limited to 'runtime/lua/vim/lsp')
| -rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 98 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/callbacks.lua | 49 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/log.lua | 17 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 11 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/rpc.lua | 136 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/util.lua | 308 |
6 files changed, 520 insertions, 99 deletions
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 2e27617997..208082f241 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -7,14 +7,41 @@ local list_extend = vim.list_extend 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 callback (optional, functionnil) Handler +-- `function(err, method, params, client_id)` for this request. Defaults +-- to the client callback in `client.callbacks`. See |lsp-callbacks|. +-- +--@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, callback) validate { method = {method, 's'}; @@ -23,9 +50,10 @@ local function request(method, params, callback) return vim.lsp.buf_request(0, method, params, callback) end ---- Sends a notification through all clients associated with current buffer. --- ---@return `true` if server responds. +--- 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 @@ -74,6 +102,12 @@ 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 @@ -82,20 +116,27 @@ end --- Formats the current buffer. --- ---- The optional {options} table can be used to specify FormattingOptions, a ---- list of which is available at ---- https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting. +--@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 params = util.make_formatting_params(options) return request('textDocument/formatting', params) end ---- Perform |vim.lsp.buf.formatting()| synchronously. +--- 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()|. +--- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|. Example: +--- +--- <pre> +--- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()]] +--- </pre> +--- +--@param options Table with valid `FormattingOptions` entries +--@param timeout_ms (number) Request timeout function M.formatting_sync(options, timeout_ms) local params = util.make_formatting_params(options) local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms) @@ -104,6 +145,13 @@ function M.formatting_sync(options, timeout_ms) vim.lsp.util.apply_text_edits(result) 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 start_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) validate { options = {options, 't', true}; @@ -138,8 +186,10 @@ function M.range_formatting(options, start_pos, end_pos) return request('textDocument/rangeFormatting', params) end ---- Renames all references to the symbol under the cursor. If {new_name} is not ---- provided, the user will be prompted for a new name using |input()|. +--- 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. @@ -152,6 +202,8 @@ end --- Lists all the references to the symbol under the cursor in the quickfix window. --- +--@param context (table) Context for the request +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references function M.references(context) validate { context = { context, 't', true } } local params = util.make_position_params() @@ -169,6 +221,7 @@ function M.document_symbol() request('textDocument/documentSymbol', params) end +--@private local function pick_call_hierarchy_item(call_hierarchy_items) if not call_hierarchy_items then return end if #call_hierarchy_items == 1 then @@ -186,6 +239,9 @@ local function pick_call_hierarchy_item(call_hierarchy_items) return choice end +--- Lists all the call sites of the symbol under the cursor in the +--- |quickfix| window. If the symbol can resolve to multiple +--- items, the user can pick one in the |inputlist|. function M.incoming_calls() local params = util.make_position_params() request('textDocument/prepareCallHierarchy', params, function(_, _, result) @@ -194,6 +250,9 @@ function M.incoming_calls() end) end +--- Lists all the items that are called by the symbol under the +--- cursor in the |quickfix| window. If the symbol can resolve to +--- multiple items, the user can pick one in the |inputlist|. function M.outgoing_calls() local params = util.make_position_params() request('textDocument/prepareCallHierarchy', params, function(_, _, result) @@ -204,9 +263,11 @@ end --- Lists all symbols in the current workspace in the quickfix window. --- ---- The list is filtered against the optional argument {query}; ---- if the argument is omitted from the call, the user is prompted to enter a string on the command line. ---- An empty string means no filtering is done. +--- The list is filtered against {query}; if the argument is omitted from the +--- call, the user is prompted to enter a string on the command line. An empty +--- string means no filtering is done. +--- +--@param query (string, optional) function M.workspace_symbol(query) query = query or npcall(vfn.input, "Query: ") local params = {query = query} @@ -227,10 +288,17 @@ function M.document_highlight() request('textDocument/documentHighlight', params) end +--- Removes document highlights from current buffer. +--- function M.clear_references() util.buf_clear_references() 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 = util.get_line_diagnostics() } @@ -239,6 +307,10 @@ function M.code_action(context) request('textDocument/codeAction', 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' }, diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index 1ed58995d0..0ee03e6a2f 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -7,17 +7,24 @@ 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") @@ -51,6 +58,7 @@ M['textDocument/codeAction'] = function(_, _, actions) 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? @@ -64,6 +72,7 @@ M['workspace/applyEdit'] = function(_, _, workspace_edit) } 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 @@ -102,6 +111,7 @@ M['textDocument/publishDiagnostics'] = function(_, _, result) 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)) @@ -109,6 +119,13 @@ M['textDocument/references'] = function(_, _, result) 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 @@ -116,24 +133,30 @@ local symbol_callback = function(_, _, 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)) @@ -146,6 +169,7 @@ M['textDocument/completion'] = function(_, _, result) 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 @@ -166,6 +190,12 @@ M['textDocument/hover'] = function(_, method, result) 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') @@ -188,11 +218,16 @@ local function location_callback(_, method, 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) util.focusable_preview(method, function() if not (result and result.signatures and result.signatures[1]) then @@ -208,15 +243,21 @@ M['textDocument/signatureHelp'] = function(_, method, result) 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 --- direction is "from" for incoming calls and "to" for outgoing calls +--@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) - -- result is a CallHierarchy{Incoming,Outgoing}Call[] return function(_, _, result) if not result then return end local items = {} @@ -237,10 +278,13 @@ local make_call_hierarchy_callback = function(direction) 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 @@ -261,6 +305,7 @@ M['window/logMessage'] = function(_, _, result, client_id) 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 diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 696ce43a59..587a65cd96 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -2,6 +2,9 @@ local log = {} +-- FIXME: DOC +-- Should be exposed in the vim docs. +-- -- Log level dictionary with reverse lookup as well. -- -- Can be used to lookup the number from the name or the name from the number. @@ -21,12 +24,14 @@ local log_date_format = "%FT%H:%M:%S%z" do local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/" + --@private local function path_join(...) return table.concat(vim.tbl_flatten{...}, path_sep) end local logfilename = path_join(vim.fn.stdpath('data'), 'lsp.log') - --- Return the log filename. + --- Returns the log filename. + --@returns (string) log filename function log.get_filename() return logfilename end @@ -36,6 +41,9 @@ do for level, levelnr in pairs(log.levels) do -- Also export the log level on the root object. log[level] = levelnr + -- FIXME: DOC + -- Should be exposed in the vim docs. + -- -- Set the lowercase name as the main use function. -- If called without arguments, it will check whether the log level is -- greater than or equal to this one. When called with arguments, it will @@ -74,6 +82,8 @@ end -- interfere with iterating the levels vim.tbl_add_reverse_lookup(log.levels) +--- Sets the current log level. +--@param level (string or number) One of `vim.lsp.log.levels` function log.set_level(level) if type(level) == 'string' then current_log_level = assert(log.levels[level:upper()], string.format("Invalid log level: %q", level)) @@ -84,8 +94,9 @@ function log.set_level(level) end end --- Return whether the level is sufficient for logging. --- @param level number log level +--- Checks whether the level is sufficient for logging. +--@param level number log level +--@returns (bool) true if would log, false if not function log.should_log(level) return level >= current_log_level end diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index ef5e08680e..4e926381e0 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -2,6 +2,11 @@ local protocol = {} +--@private +--- Returns {a} if it is not nil, otherwise returns {b}. +--- +--@param a +--@param b local function ifnil(a, b) if a == nil then return b end return a @@ -9,12 +14,14 @@ end --[=[ --- Useful for interfacing with: --- https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md +--@private +--- Useful for interfacing with: +--- https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md function transform_schema_comments() nvim.command [[silent! '<,'>g/\/\*\*\|\*\/\|^$/d]] nvim.command [[silent! '<,'>s/^\(\s*\) \* \=\(.*\)/\1--\2/]] end +--@private function transform_schema_to_table() transform_schema_comments() nvim.command [[silent! '<,'>s/: \S\+//]] diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 81c92bfe05..680e1ba6ae 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -5,6 +5,11 @@ local protocol = require('vim.lsp.protocol') local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap -- TODO replace with a better implementation. +--@private +--- Encodes to JSON. +--- +--@param data (table) Data to encode +--@returns (string) Encoded object local function json_encode(data) local status, result = pcall(vim.fn.json_encode, data) if status then @@ -13,6 +18,11 @@ local function json_encode(data) return nil, result end end +--@private +--- Decodes from JSON. +--- +--@param data (string) Data to decode +--@returns (table) Decoded JSON object local function json_decode(data) local status, result = pcall(vim.fn.json_decode, data) if status then @@ -22,17 +32,26 @@ local function json_decode(data) end end +--@private +--- Checks whether a given path exists and is a directory. +--@param filename (string) path to check +--@returns (bool) local function is_dir(filename) local stat = vim.loop.fs_stat(filename) return stat and stat.type == 'directory' or false end local NIL = vim.NIL +--@private +--- Returns its argument, but converts `vim.NIL` to Lua `nil`. +--@param v (any) Argument +--@returns (any) local function convert_NIL(v) if v == NIL then return nil end return v end +--@private --- Merges current process env with the given env and returns the result as --- a list of "k=v" strings. --- @@ -42,6 +61,8 @@ end --- in: { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", } --- out: { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", } --- </pre> +--@param env (table) table of environment variable assignments +--@returns (table) list of `"k=v"` strings local function env_merge(env) if env == nil then return env @@ -56,6 +77,11 @@ local function env_merge(env) return final_env end +--@private +--- Embeds the given string into a table and correctly computes `Content-Length`. +--- +--@param encoded_message (string) +--@returns (table) table containing encoded message and `Content-Length` attribute local function format_message_with_content_length(encoded_message) return table.concat { 'Content-Length: '; tostring(#encoded_message); '\r\n\r\n'; @@ -63,8 +89,11 @@ local function format_message_with_content_length(encoded_message) } end ---- Parse an LSP Message's header --- @param header: The header to parse. +--@private +--- Parses an LSP Message's header +--- +--@param header: The header to parse. +--@returns Parsed headers local function parse_headers(header) if type(header) ~= 'string' then return nil @@ -92,6 +121,8 @@ end -- case insensitive pattern. local header_start_pattern = ("content"):gsub("%w", function(c) return "["..c..c:upper().."]" end) +--@private +--- The actual workhorse. local function request_parser_loop() local buffer = '' while true do @@ -138,6 +169,10 @@ local client_errors = vim.tbl_add_reverse_lookup { SERVER_RESULT_CALLBACK_ERROR = 7; } +--- Constructs an error message from an LSP error object. +--- +--@param err (table) The error object +--@returns (string) The formatted error message local function format_rpc_error(err) validate { err = { err, 't' }; @@ -182,23 +217,69 @@ local function rpc_response_error(code, message, data) end local default_handlers = {} +--@private +--- Default handler for notifications sent to an LSP server. +--- +--@param method (string) The invoked LSP method +--@param params (table): Parameters for the invoked LSP method function default_handlers.notification(method, params) local _ = log.debug() and log.debug('notification', method, params) end +--@private +--- Default handler for requests sent to an LSP server. +--- +--@param method (string) The invoked LSP method +--@param params (table): Parameters for the invoked LSP method +--@returns `nil` and `vim.lsp.protocol.ErrorCodes.MethodNotFound`. function default_handlers.server_request(method, params) local _ = log.debug() and log.debug('server_request', method, params) return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound) end +--@private +--- Default handler for when a client exits. +--- +--@param code (number): Exit code +--@param signal (number): Number describing the signal used to terminate (if +---any) function default_handlers.on_exit(code, signal) - local _ = log.info() and log.info("client exit", { code = code, signal = signal }) + local _ = log.info() and log.info("client_exit", { code = code, signal = signal }) end +--@private +--- Default handler for client errors. +--- +--@param code (number): Error code +--@param err (any): Details about the error +---any) function default_handlers.on_error(code, err) local _ = log.error() and log.error('client_error:', client_errors[code], err) end ---- Create and start an RPC client. --- @param cmd [ -local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_params) +--- Starts an LSP server process and create an LSP RPC client object to +--- interact with it. +--- +--@param cmd (string) Command to start the LSP server. +--@param cmd_args (table) List of additional string arguments to pass to {cmd}. +--@param handlers (table, optional) Handlers for LSP message types. Valid +---handler names are: +--- - `"notification"` +--- - `"server_request"` +--- - `"on_error"` +--- - `"on_exit"` +--@param extra_spawn_params (table, optional) Additional context for the LSP +--- server process. May contain: +--- - {cwd} (string) Working directory for the LSP server process +--- - {env} (table) Additional environment variables for LSP server process +--@returns Client RPC object. +--- +--@returns Methods: +--- - `notify()` |vim.lsp.rpc.notify()| +--- - `request()` |vim.lsp.rpc.request()| +--- +--@returns Members: +--- - {pid} (number) The LSP server's PID. +--- - {handle} A handle for low-level interaction with the LSP server process +--- |vim.loop|. +local function start(cmd, cmd_args, handlers, extra_spawn_params) local _ = log.info() and log.info("Starting RPC client", {cmd = cmd, args = cmd_args, extra = extra_spawn_params}) validate { cmd = { cmd, 's' }; @@ -242,6 +323,11 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para local handle, pid do + --@private + --- Callback for |vim.loop.spawn()| Closes all streams and runs the + --- `on_exit` handler. + --@param code (number) Exit code + --@param signal (number) Signal that was used to terminate (if any) local function onexit(code, signal) stdin:close() stdout:close() @@ -265,6 +351,12 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para handle, pid = uv.spawn(cmd, spawn_params, onexit) end + --@private + --- Encodes {payload} into a JSON-RPC message and sends it to the remote + --- process. + --- + --@param payload (table) Converted into a JSON string, see |json_encode()| + --@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing. local function encode_and_send(payload) local _ = log.debug() and log.debug("rpc.send.payload", payload) if handle:is_closing() then return false end @@ -276,7 +368,14 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para return true end - local function send_notification(method, params) + -- FIXME: DOC: Should be placed on the RPC client object returned by + -- `start()` + -- + --- Sends a notification to the LSP server. + --@param method (string) The invoked LSP method + --@param params (table): Parameters for the invoked LSP method + --@returns (bool) `true` if notification could be sent, `false` if not + local function notify(method, params) local _ = log.debug() and log.debug("rpc.notify", method, params) return encode_and_send { jsonrpc = "2.0"; @@ -285,6 +384,8 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para } end + --@private + --- sends an error object to the remote LSP process. local function send_response(request_id, err, result) return encode_and_send { id = request_id; @@ -294,7 +395,16 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para } end - local function send_request(method, params, callback) + -- FIXME: DOC: Should be placed on the RPC client object returned by + -- `start()` + -- + --- Sends a request to the LSP server and runs {callback} upon response. + --- + --@param method (string) The invoked LSP method + --@param params (table) Parameters for the invoked LSP method + --@param callback (function) Callback to invoke + --@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not + local function request(method, params, callback) validate { callback = { callback, 'f' }; } @@ -320,11 +430,13 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para end end) + --@private local function on_error(errkind, ...) assert(client_errors[errkind]) -- TODO what to do if this fails? pcall(handlers.on_error, errkind, ...) end + --@private local function pcall_handler(errkind, status, head, ...) if not status then on_error(errkind, head, ...) @@ -332,6 +444,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para end return status, head, ... end + --@private local function try_call(errkind, fn, ...) return pcall_handler(errkind, pcall(fn, ...)) end @@ -340,6 +453,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para -- time and log them. This would require storing the timestamp. I could call -- them with an error then, perhaps. + --@private local function handle_body(body) local decoded, err = json_decode(body) if not decoded then @@ -458,13 +572,13 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para return { pid = pid; handle = handle; - request = send_request; - notify = send_notification; + request = request; + notify = notify } end return { - start = create_and_start_client; + start = start; rpc_response_error = rpc_response_error; format_rpc_error = format_rpc_error; client_errors = client_errors; diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 33fca29ecd..3ec7311d65 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -7,6 +7,7 @@ local highlight = require 'vim.highlight' local M = {} +-- FIXME: DOC: Expose in vimdocs --- Diagnostics received from the server via `textDocument/publishDiagnostics` -- by buffer. -- @@ -33,18 +34,30 @@ local M = {} M.diagnostics_by_buf = {} local split = vim.split +--@private local function split_lines(value) return split(value, '\n', true) end +--@private local function ok_or_nil(status, ...) if not status then return end return ... end +--@private local function npcall(fn, ...) return ok_or_nil(pcall(fn, ...)) end +--- Replaces text in a range with new text. +--- +--- CAUTION: Changes in-place! +--- +--@param lines (table) Original list of strings +--@param A (table) Start position; a 2-tuple of {line, col} numbers +--@param B (table) End position; a 2-tuple of {line, col} numbers +--@param new_lines A list of strings to replace the original +--@returns (table) The modified {lines} object function M.set_lines(lines, A, B, new_lines) -- 0-indexing to 1-indexing local i_0 = A[1] + 1 @@ -78,6 +91,7 @@ function M.set_lines(lines, A, B, new_lines) return lines end +--@private local function sort_by_key(fn) return function(a,b) local ka, kb = fn(a), fn(b) @@ -91,13 +105,15 @@ local function sort_by_key(fn) return false end end +--@private local edit_sort_key = sort_by_key(function(e) return {e.A[1], e.A[2], e.i} end) +--@private --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position --- Returns a zero-indexed column, since set_lines() does the conversion to --- 1-indexed +--- Returns a zero-indexed column, since set_lines() does the conversion to +--- 1-indexed local function get_line_byte_from_position(bufnr, position) -- LSP's line and characters are 0-indexed -- Vim's line and columns are 1-indexed @@ -114,6 +130,9 @@ local function get_line_byte_from_position(bufnr, position) return col end +--- Applies a list of text edits to a buffer. +--@param text_edits (table) list of `TextEdit` objects +--@param buf_nr (number) Buffer id function M.apply_text_edits(text_edits, bufnr) if not next(text_edits) then return end if not api.nvim_buf_is_loaded(bufnr) then @@ -168,20 +187,30 @@ end -- function M.glob_to_regex(glob) -- end --- textDocument/completion response returns one of CompletionItem[], CompletionList or null. --- https://microsoft.github.io/language-server-protocol/specification#textDocument_completion +--- Can be used to extract the completion items from a +--- `textDocument/completion` request, which may return one of +--- `CompletionItem[]`, `CompletionList` or null. +--@param result (table) The result of a `textDocument/completion` request +--@returns (table) List of completion items +--@see https://microsoft.github.io/language-server-protocol/specification#textDocument_completion function M.extract_completion_items(result) if type(result) == 'table' and result.items then + -- result is a `CompletionList` return result.items elseif result ~= nil then + -- result is `CompletionItem[]` return result else + -- result is `null` return {} end end |