From ac05343a1023874243ee9fdd490c21c42a737157 Mon Sep 17 00:00:00 2001 From: Patrice Peterson Date: Wed, 19 Aug 2020 18:17:08 +0200 Subject: [PATCH] Add docs for most vim.lsp methods Most of the lsp.log will be addressed in a separate PR. --- runtime/doc/api.txt | 11 +- runtime/doc/lsp.txt | 816 ++++++++++++++++++------------ runtime/lua/vim/lsp.lua | 255 ++++++++-- runtime/lua/vim/lsp/buf.lua | 98 +++- runtime/lua/vim/lsp/callbacks.lua | 47 +- runtime/lua/vim/lsp/log.lua | 11 +- runtime/lua/vim/lsp/protocol.lua | 11 +- runtime/lua/vim/lsp/rpc.lua | 132 ++++- runtime/lua/vim/lsp/util.lua | 308 ++++++++--- scripts/gen_vimdoc.py | 6 +- src/nvim/api/vim.c | 8 +- 11 files changed, 1238 insertions(+), 465 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index ea3a8242ae..7ac7f7d7ea 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -734,13 +734,14 @@ nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()* On execution error: does not fail, but updates v:errmsg. - If you need to input sequences like use nvim_replace_termcodes - to replace the termcodes and then pass the resulting string to - nvim_feedkeys. You'll also want to enable escape_csi. + If you need to input sequences like use + |nvim_replace_termcodes| to replace the termcodes and then + pass the resulting string to nvim_feedkeys. You'll also want + to enable escape_csi. Example: > - :let key = nvim_replace_termcodes("", v:true, v:false, v:true) - :call nvim_feedkeys(key, 'n', v:true) + :let key = nvim_replace_termcodes("", v:true, v:false, v:true) + :call nvim_feedkeys(key, 'n', v:true) < Parameters: ~ diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index b934d2dfa0..9db478ac41 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -353,9 +353,6 @@ buf_get_clients({bufnr}) *vim.lsp.buf_get_clients()* {bufnr} (optional, number): Buffer handle, or 0 for current -buf_get_full_text({bufnr}) *vim.lsp.buf_get_full_text()* - TODO: Documentation - buf_is_attached({bufnr}, {client_id}) *vim.lsp.buf_is_attached()* Checks if a buffer is attached for a particular client. @@ -416,71 +413,69 @@ buf_request_sync({bufnr}, {method}, {params}, {timeout_ms}) error, returns `(nil, err)` where `err` is a string describing the failure reason. -cancel_request({id}) *vim.lsp.cancel_request()* - TODO: Documentation - client() *vim.lsp.client* - LSP client object. + LSP client object. You can get an active client object via + |vim.lsp.get_client_by_id()| or + |vim.lsp.get_active_clients()|. • Methods: - • request(method, params, [callback]) Send a request to the - server. If callback is not specified, it will use + • request(method, params, [callback], bufnr) Send 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. This is a thin - wrapper around {client.rpc.request} with some additional - checking. Returns a boolean to indicate if the - notification was successful. If it is false, then it will - always be false (the client has shutdown). If it was - successful, then it will return the request id as the - second result. You can use this with `notify("$/cancel", { - id = request_id })` to cancel the request. This helper is - made automatically with |vim.lsp.buf_request()| Returns: - status, [client_id] - • notify(method, params) This is just {client.rpc.notify}() - Returns a boolean to indicate if the notification was - successful. If it is false, then it will always be false - (the client has shutdown). Returns: status - • cancel_request(id) This is just - {client.rpc.notify}("$/cancelRequest", { id = id }) - Returns the same as `notify()` . + 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). + If {status} is `true` , the function returns {request_id} + as the second result. You can use this with + `client.cancel_request(request_id)` to cancel the request. + • notify(method, params) Send a notification to an LSP + server. Returns: a boolean to indicate if the notification + was successful. If it is false, then it will always be + false (the client has shutdown). + • cancel_request(id) Cancels a request with a given request + id. Returns: same as `notify()` . • stop([force]) Stop a client, optionally with force. By default, it will just ask the server to shutdown without force. If you request to stop a client which has previously been requested to shutdown, it will automatically escalate and force shutdown. - • is_stopped() Returns true if the client is fully stopped. + • is_stopped() Checks whether a client is stopped. Returns: + true if the client is fully stopped. + • on_attach(bufnr) Runs the on_attach function from the + client's config if it was defined. • Members - • id (number): The id allocated to the client. - • name (string): If a name is specified on creation, that + • {id} (number): The id allocated to the client. + • {name} (string): If a name is specified on creation, that will be used. Otherwise it is just the client id. This is used for logs and messages. - • offset_encoding (string): The encoding used for + • {rpc} (table): RPC client object, for low level + interaction with the client. See |vim.lsp.rpc.start()|. + • {offset_encoding} (string): The encoding used for communicating with the server. You can modify this in the - `on_init` method before text is sent to the server. - • callbacks (table): The callbacks used by the client as + `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|. - • config (table): copy of the table that was passed by the + • {config} (table): copy of the table that was passed by the user to |vim.lsp.start_client()|. - • server_capabilities (table): Response from the server sent - on `initialize` describing the server's capabilities. - • resolved_capabilities (table): Normalized table of + • {server_capabilities} (table): Response from the server + sent on `initialize` describing the server's capabilities. + • {resolved_capabilities} (table): Normalized table of capabilities that we have detected based on the initialize response from the server in `server_capabilities` . client_is_stopped({client_id}) *vim.lsp.client_is_stopped()* - TODO: Documentation + Checks whether a client is stopped. - *vim.lsp.define_default_sign()* -define_default_sign({name}, {properties}) - TODO: Documentation + Parameters: ~ + {client_id} (Number) -err_message({...}) *vim.lsp.err_message()* - TODO: Documentation - - *vim.lsp.for_each_buffer_client()* -for_each_buffer_client({bufnr}, {callback}) - TODO: Documentation + Return: ~ + true if client is stopped, false otherwise. get_active_clients() *vim.lsp.get_active_clients()* Gets all active clients. @@ -499,25 +494,10 @@ get_client_by_id({client_id}) *vim.lsp.get_client_by_id()* |vim.lsp.client| object, or nil get_log_path() *vim.lsp.get_log_path()* - TODO: Documentation + Gets the path of the logfile used by the LSP client. -initialize() *vim.lsp.initialize()* - TODO: Documentation - -is_dir({filename}) *vim.lsp.is_dir()* - TODO: Documentation - -is_stopped() *vim.lsp.is_stopped()* - TODO: Documentation - -next_client_id() *vim.lsp.next_client_id()* - TODO: Documentation - -notification({method}, {params}) *vim.lsp.notification()* - TODO: Documentation - -notify({...}) *vim.lsp.notify()* - TODO: Documentation + Return: ~ + (String) Path to logfile. omnifunc({findstart}, {base}) *vim.lsp.omnifunc()* Implements 'omnifunc' compatible LSP completion. @@ -538,30 +518,6 @@ omnifunc({findstart}, {base}) *vim.lsp.omnifunc()* |complete-items| |CompleteDone| -on_error({code}, {err}) *vim.lsp.on_error()* - TODO: Documentation - -on_exit({code}, {signal}) *vim.lsp.on_exit()* - TODO: Documentation - -once({fn}) *vim.lsp.once()* - TODO: Documentation - -optional_validator({fn}) *vim.lsp.optional_validator()* - TODO: Documentation - -request({method}, {params}, {callback}, {bufnr}) *vim.lsp.request()* - TODO: Documentation - -resolve_bufnr({bufnr}) *vim.lsp.resolve_bufnr()* - TODO: Documentation - -resolve_callback({method}) *vim.lsp.resolve_callback()* - TODO: Documentation - -server_request({method}, {params}) *vim.lsp.server_request()* - TODO: Documentation - set_log_level({level}) *vim.lsp.set_log_level()* Sets the global log level for LSP logging. @@ -582,6 +538,9 @@ start_client({config}) *vim.lsp.start_client()* Parameters `cmd` and `root_dir` are required. + The following parameters describe fields in the {config} + table. + Parameters: ~ {root_dir} (required, string) Directory where the LSP server will base its rootUri on @@ -612,13 +571,13 @@ start_client({config}) *vim.lsp.start_client()* array. {callbacks} Map of language server method names to `function(err, method, params, client_id)` handler. Invoked for: - • Notifications from the server, where + • Notifications to the server, where `err` will always be `nil` . - • Requests initiated 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 + • 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 @@ -647,8 +606,9 @@ start_client({config}) *vim.lsp.start_client()* where `params` contains the parameters being sent to the server and `config` is the config that was passed to - `start_client()` . You can use this to - modify parameters before they are sent. + |vim.lsp.start_client()|. You can use + this to modify parameters before they + are sent. {on_init} Callback (client, initialize_result) invoked after LSP "initialize", where `result` is a table of `capabilities` @@ -680,9 +640,6 @@ start_client({config}) *vim.lsp.start_client()* error). Use `on_init` to do any actions once the client has been initialized. -stop({force}) *vim.lsp.stop()* - TODO: Documentation - stop_client({client_id}, {force}) *vim.lsp.stop_client()* Stops a client(s). @@ -702,26 +659,10 @@ stop_client({client_id}, {force}) *vim.lsp.stop_client()* thereof {force} boolean (optional) shutdown forcefully - *vim.lsp.text_document_did_open_handler()* -text_document_did_open_handler({bufnr}, {client}) - TODO: Documentation - -unsupported_method({method}) *vim.lsp.unsupported_method()* - TODO: Documentation - -validate_client_config({config}) *vim.lsp.validate_client_config()* - TODO: Documentation - -validate_encoding({encoding}) *vim.lsp.validate_encoding()* - TODO: Documentation - ============================================================================== Lua module: vim.lsp.protocol *lsp-protocol* -ifnil({a}, {b}) *vim.lsp.protocol.ifnil()* - TODO: Documentation - *vim.lsp.protocol.make_client_capabilities()* make_client_capabilities() Gets a new ClientCapabilities object describing the LSP client @@ -739,28 +680,38 @@ resolve_capabilities({server_capabilities}) characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a` , `example.b` , but not `example.0` ) - *vim.lsp.protocol.transform_schema_comments()* -transform_schema_comments() - TODO: Documentation - - *vim.lsp.protocol.transform_schema_to_table()* -transform_schema_to_table() - TODO: Documentation - ============================================================================== Lua module: vim.lsp.buf *lsp-buf* clear_references() *vim.lsp.buf.clear_references()* - TODO: Documentation + Removes document highlights from current buffer. code_action({context}) *vim.lsp.buf.code_action()* - TODO: Documentation + Select a code action from the input list that is available at + the current cursor position. + + Parameters: ~ + {context} (table, optional) Valid `CodeActionContext` + object + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction completion({context}) *vim.lsp.buf.completion()* Retrieves the completion items at the current cursor position. Can only be called in Insert mode. + Parameters: ~ + {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 also: ~ + |vim.lsp.protocol.constants.CompletionTriggerKind| + declaration() *vim.lsp.buf.declaration()* Jumps to the declaration of the symbol under the cursor. @@ -782,14 +733,25 @@ document_symbol() *vim.lsp.buf.document_symbol()* window. execute_command({command}) *vim.lsp.buf.execute_command()* - TODO: Documentation + Execute an LSP server command. + + Parameters: ~ + {command} A valid `ExecuteCommandParams` object + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand formatting({options}) *vim.lsp.buf.formatting()* 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 . Some unspecified options will be automatically derived from - the current Neovim options. + Parameters: ~ + {options} (optional, table) Can be used to specify + FormattingOptions. Some unspecified options + will be automatically derived from the current + Neovim options. + + See also: ~ + https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting *vim.lsp.buf.formatting_sync()* formatting_sync({options}, {timeout_ms}) @@ -797,7 +759,15 @@ formatting_sync({options}, {timeout_ms}) 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()|. + |vim.lsp.buf_request_sync()|. Example: +> + + vim.api.nvim_command[[autocmd BufWritePre lua vim.lsp.buf.formatting_sync()]] +< + + Parameters: ~ + {options} Table with valid `FormattingOptions` entries + {timeout_ms} (number) Request timeout hover() *vim.lsp.buf.hover()* Displays hover information about the symbol under the cursor @@ -808,29 +778,49 @@ implementation() *vim.lsp.buf.implementation()* Lists all the implementations for the symbol under the cursor in the quickfix window. -npcall({fn}, {...}) *vim.lsp.buf.npcall()* - TODO: Documentation +incoming_calls() *vim.lsp.buf.incoming_calls()* + 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|. -ok_or_nil({status}, {...}) *vim.lsp.buf.ok_or_nil()* - TODO: Documentation +outgoing_calls() *vim.lsp.buf.outgoing_calls()* + 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|. *vim.lsp.buf.range_formatting()* range_formatting({options}, {start_pos}, {end_pos}) - TODO: Documentation + Perform |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()|. + + Parameters: ~ + {options} Table with valid `FormattingOptions` entries + {timeout_ms} (number) Request timeout references({context}) *vim.lsp.buf.references()* Lists all the references to the symbol under the cursor in the quickfix window. -rename({new_name}) *vim.lsp.buf.rename()* - 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()|. + Parameters: ~ + {context} (table) Context for the request -request({method}, {params}, {callback}) *vim.lsp.buf.request()* - TODO: Documentation + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references + +rename({new_name}) *vim.lsp.buf.rename()* + Renames all references to the symbol under the cursor. + + Parameters: ~ + {new_name} (string) If not provided, the user will be + prompted for a new name using |input()|. server_ready() *vim.lsp.buf.server_ready()* + Checks whether the language servers attached to the current + buffer are ready. + Return: ~ `true` if server responds. @@ -846,115 +836,75 @@ workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* 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. -incoming_calls() *vim.lsp.buf.incoming_calls()* - 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|. - -outgoing_calls() *vim.lsp.buf.outgoing_calls()* - 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|. - - -============================================================================== -Lua module: vim.lsp.callbacks *lsp-callbacks* - -err_message({...}) *vim.lsp.callbacks.err_message()* - TODO: Documentation - - *vim.lsp.callbacks.location_callback()* -location_callback({_}, {method}, {result}) - TODO: Documentation + Parameters: ~ + {query} (string, optional) ============================================================================== Lua module: vim.lsp.log *lsp-log* get_filename() *vim.lsp.log.get_filename()* - TODO: Documentation + Returns the log filename. -path_join({...}) *vim.lsp.log.path_join()* - TODO: Documentation + Return: ~ + (string) log filename set_level({level}) *vim.lsp.log.set_level()* - TODO: Documentation + Sets the current log level. + + Parameters: ~ + {level} (string or number) One of `vim.lsp.log.levels` should_log({level}) *vim.lsp.log.should_log()* - TODO: Documentation + Checks whether the level is sufficient for logging. + + Parameters: ~ + {level} number log level + + Return: ~ + (bool) true if would log, false if not ============================================================================== Lua module: vim.lsp.rpc *lsp-rpc* -convert_NIL({v}) *vim.lsp.rpc.convert_NIL()* - TODO: Documentation - - *vim.lsp.rpc.create_and_start_client()* -create_and_start_client({cmd}, {cmd_args}, {handlers}, - {extra_spawn_params}) - TODO: Documentation - -encode_and_send({payload}) *vim.lsp.rpc.encode_and_send()* - TODO: Documentation - -env_merge({env}) *vim.lsp.rpc.env_merge()* - Merges current process env with the given env and returns the - result as a list of "k=v" strings. -> - - Example: -< - - > 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", } -< - - *vim.lsp.rpc.format_message_with_content_length()* -format_message_with_content_length({encoded_message}) - TODO: Documentation - format_rpc_error({err}) *vim.lsp.rpc.format_rpc_error()* - TODO: Documentation + Constructs an error message from an LSP error object. -handle_body({body}) *vim.lsp.rpc.handle_body()* - TODO: Documentation + Parameters: ~ + {err} (table) The error object -is_dir({filename}) *vim.lsp.rpc.is_dir()* - TODO: Documentation + Return: ~ + (string) The formatted error message -json_decode({data}) *vim.lsp.rpc.json_decode()* - TODO: Documentation +notify({method}, {params}) *vim.lsp.rpc.notify()* + Sends a notification to the LSP server. -json_encode({data}) *vim.lsp.rpc.json_encode()* - TODO: Documentation + Parameters: ~ + {method} (string) The invoked LSP method + {params} (table): Parameters for the invoked LSP method -notification({method}, {params}) *vim.lsp.rpc.notification()* - TODO: Documentation + Return: ~ + (bool) `true` if notification could be sent, `false` if + not -on_error({errkind}, {...}) *vim.lsp.rpc.on_error()* - TODO: Documentation +request({method}, {params}, {callback}) *vim.lsp.rpc.request()* + Sends a request to the LSP server and runs {callback} upon + response. -on_exit({code}, {signal}) *vim.lsp.rpc.on_exit()* - TODO: Documentation + Parameters: ~ + {method} (string) The invoked LSP method + {params} (table) Parameters for the invoked LSP method + {callback} (function) Callback to invoke -onexit({code}, {signal}) *vim.lsp.rpc.onexit()* - TODO: Documentation - -parse_headers({header}) *vim.lsp.rpc.parse_headers()* - TODO: Documentation - - *vim.lsp.rpc.pcall_handler()* -pcall_handler({errkind}, {status}, {head}, {...}) - TODO: Documentation - -request_parser_loop() *vim.lsp.rpc.request_parser_loop()* - TODO: Documentation + Return: ~ + (bool, number) `(true, message_id)` if request could be + sent, `false` if not *vim.lsp.rpc.rpc_response_error()* rpc_response_error({code}, {message}, {data}) @@ -966,48 +916,84 @@ rpc_response_error({code}, {message}, {data}) {message} (optional) arbitrary message to send to server {data} (optional) arbitrary data to send to server -send_notification({method}, {params}) *vim.lsp.rpc.send_notification()* - TODO: Documentation + *vim.lsp.rpc.start()* +start({cmd}, {cmd_args}, {handlers}, {extra_spawn_params}) + Start an LSP server process and create an LSP RPC client + object to interact with it. - *vim.lsp.rpc.send_request()* -send_request({method}, {params}, {callback}) - TODO: Documentation + Parameters: ~ + {cmd} (string) Command to start the LSP + server. + {cmd_args} (table) List of additional string + arguments to pass to {cmd}. + {handlers} (table, optional) Handlers for LSP + message types. Valid handler names + are: + • `"notification"` + • `"server_request"` + • `"on_error"` + • `"on_exit"` + {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 - *vim.lsp.rpc.send_response()* -send_response({request_id}, {err}, {result}) - TODO: Documentation + Return: ~ + Client RPC object. + Methods: + • `notify()` |vim.lsp.rpc.notify()| + • `request()` |vim.lsp.rpc.request()| -server_request({method}, {params}) *vim.lsp.rpc.server_request()* - TODO: Documentation - -try_call({errkind}, {fn}, {...}) *vim.lsp.rpc.try_call()* - TODO: Documentation + Members: + • {pid} (number) The LSP server's PID. + • {handle} A handle for low-level interaction with the LSP + server process |vim.loop|. ============================================================================== Lua module: vim.lsp.util *lsp-util* - *vim.lsp.util.apply_syntax_to_region()* -apply_syntax_to_region({ft}, {start}, {finish}) - TODO: Documentation - *vim.lsp.util.apply_text_document_edit()* apply_text_document_edit({text_document_edit}) - TODO: Documentation + Apply a `TextDocumentEdit` , which is a list of changes to a + single document. + + Parameters: ~ + {text_document_edit} (table) a `TextDocumentEdit` object + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit *vim.lsp.util.apply_text_edits()* apply_text_edits({text_edits}, {bufnr}) - TODO: Documentation + Applies a list of text edits to a buffer. + + Parameters: ~ + {text_edits} (table) list of `TextEdit` objects + {buf_nr} (number) Buffer id *vim.lsp.util.apply_workspace_edit()* apply_workspace_edit({workspace_edit}) - TODO: Documentation + Applies a `WorkspaceEdit` . + + Parameters: ~ + {workspace_edit} (table) `WorkspaceEdit` buf_clear_diagnostics({bufnr}) *vim.lsp.util.buf_clear_diagnostics()* - TODO: Documentation + Clear diagnostics for a buffer. + + Parameters: ~ + {bufnr} (number) buffer id buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* - TODO: Documentation + Removes document highlights from a buffer. + + Parameters: ~ + {bufnr} buffer id buf_diagnostics_count({kind}) *vim.lsp.util.buf_diagnostics_count()* Returns the number of diagnostics of given kind for current @@ -1040,12 +1026,14 @@ buf_diagnostics_count({kind}) *vim.lsp.util.buf_diagnostics_count()* *vim.lsp.util.buf_diagnostics_save_positions()* buf_diagnostics_save_positions({bufnr}, {diagnostics}) - Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf + Saves diagnostics into + vim.lsp.util.diagnostics_by_buf[{bufnr}]. Parameters: ~ - {bufnr} bufnr for which the diagnostics are for. - {diagnostics} Diagnostics[] received from the language - server. + {bufnr} (number) buffer id for which the + diagnostics are for + {diagnostics} list of `Diagnostic` s received from the + LSP server *vim.lsp.util.buf_diagnostics_signs()* buf_diagnostics_signs({bufnr}, {diagnostics}) @@ -1061,38 +1049,117 @@ buf_diagnostics_signs({bufnr}, {diagnostics}) *vim.lsp.util.buf_diagnostics_underline()* buf_diagnostics_underline({bufnr}, {diagnostics}) - TODO: Documentation + Highlights a list of diagnostics in a buffer by underlining + them. + + Parameters: ~ + {bufnr} (number) buffer id + {diagnostics} (list of `Diagnostic` s) *vim.lsp.util.buf_diagnostics_virtual_text()* buf_diagnostics_virtual_text({bufnr}, {diagnostics}) - TODO: Documentation + Given a list of diagnostics, set the corresponding virtual + text for a buffer. + + Parameters: ~ + {bufnr} buffer id + {diagnostics} (table) list of `Diagnostic` s *vim.lsp.util.buf_highlight_references()* buf_highlight_references({bufnr}, {references}) - TODO: Documentation + Show a list of document highlights for a certain buffer. + + Parameters: ~ + {bufnr} buffer id + {references} List of `DocumentHighlight` objects to + highlight character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()* - TODO: Documentation + Returns the UTF-32 and UTF-16 offsets for a position in a + certain buffer. + + Parameters: ~ + {buf} buffer id (0 for current) + {row} 0-indexed line + {col} 0-indexed byte offset in line + + Return: ~ + (number, number) UTF-32 and UTF-16 index of the character + in line {row} column {col} in buffer {buf} *vim.lsp.util.close_preview_autocmd()* close_preview_autocmd({events}, {winnr}) - TODO: Documentation + Create autocommands to close a preview window when events + happen. + + Parameters: ~ + {events} (table) list of events + {winnr} (number) window id of preview window + + See also: ~ + |autocmd-events| *vim.lsp.util.convert_input_to_markdown_lines()* convert_input_to_markdown_lines({input}, {contents}) - TODO: Documentation + Convert any of `MarkedString` | `MarkedString[]` | + `MarkupContent` into a list of lines containing valid + markdown. Useful to populate the hover window for + `textDocument/hover` , for parsing the result of + `textDocument/signatureHelp` , and potentially others. + + Parameters: ~ + {input} ( `MarkedString` | `MarkedString[]` | + `MarkupContent` ) + {contents} (table, optional, default `{}` ) List of + strings to extend with converted lines + + Return: ~ + {contents}, extended with lines of converted markdown. + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover *vim.lsp.util.convert_signature_help_to_markdown_lines()* convert_signature_help_to_markdown_lines({signature_help}) - TODO: Documentation + Convert `textDocument/SignatureHelp` response to markdown + lines. + + Parameters: ~ + {signature_help} Response of `textDocument/SignatureHelp` + + Return: ~ + list of lines of converted markdown. + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp *vim.lsp.util.diagnostics_group_by_line()* diagnostics_group_by_line({diagnostics}) - TODO: Documentation + Groups a list of diagnostics by line. + + Parameters: ~ + {diagnostics} (table) list of `Diagnostic` s + + Return: ~ + (table) dictionary mapping lines to lists of diagnostics + valid on those lines + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic *vim.lsp.util.extract_completion_items()* extract_completion_items({result}) - TODO: Documentation + Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null. + + Parameters: ~ + {result} (table) The result of a + `textDocument/completion` request + + Return: ~ + (table) List of completion items + + See also: ~ + https://microsoft.github.io/language-server-protocol/specification#textDocument_completion *vim.lsp.util.fancy_floating_markdown()* fancy_floating_markdown({contents}, {opts}) @@ -1100,8 +1167,7 @@ fancy_floating_markdown({contents}, {opts}) the code blocks and converting them into highlighted code. This will by default insert a blank line separator after those code block regions to improve readability. The result is shown - in a floating preview TODO: refactor to separate - stripping/converting and make use of open_floating_preview + in a floating preview Parameters: ~ {contents} table of lines to show in window @@ -1110,22 +1176,34 @@ fancy_floating_markdown({contents}, {opts}) Return: ~ width,height size of float -find_window_by_var({name}, {value}) *vim.lsp.util.find_window_by_var()* - TODO: Documentation - focusable_float({unique_name}, {fn}) *vim.lsp.util.focusable_float()* - TODO: Documentation + Parameters: ~ + {unique_name} (string) Window variable + {fn} (function) should return create a new + window and return a tuple of + ({focusable_buffer_id}, {window_id}). if + {focusable_buffer_id} is a valid buffer id, + the newly created window will be the new + focus associated with the current buffer + via the tag `unique_name` . + + Return: ~ + (pbufnr, pwinnr) if `fn()` has created a new window; nil + otherwise *vim.lsp.util.focusable_preview()* focusable_preview({unique_name}, {fn}) - TODO: Documentation + Focus/unfocus the floating preview window associated with the + current buffer via the window variable `unique_name` . If no + such preview window exists, make a new one. -get_completion_word({item}) *vim.lsp.util.get_completion_word()* - TODO: Documentation - - *vim.lsp.util.get_current_line_to_cursor()* -get_current_line_to_cursor() - TODO: Documentation + Parameters: ~ + {unique_name} (string) Window variable + {fn} (function) The return values of this + function will be passed directly to + |vim.lsp.util.open_floating_preview()|, in + the case that a new floating window should + be created get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* Get visual width of tabstop. @@ -1140,48 +1218,104 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* See also: ~ |softtabstop| - *vim.lsp.util.get_line_byte_from_position()* -get_line_byte_from_position({bufnr}, {position}) - TODO: Documentation - get_line_diagnostics() *vim.lsp.util.get_line_diagnostics()* - TODO: Documentation + Get list of diagnostics for the current line. + + Return: ~ + (table) list of `Diagnostic` tables + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic *vim.lsp.util.get_severity_highlight_name()* get_severity_highlight_name({severity}) - TODO: Documentation + Get the name of a severity's highlight group. + + Parameters: ~ + {severity} A member of + `vim.lsp.protocol.DiagnosticSeverity` + + Return: ~ + (string) Highlight group name jump_to_location({location}) *vim.lsp.util.jump_to_location()* - TODO: Documentation + Jumps to a location. + + Parameters: ~ + {location} ( `Location` | `LocationLink` ) + + Return: ~ + `true` if the jump succeeded locations_to_items({locations}) *vim.lsp.util.locations_to_items()* - TODO: Documentation + Returns the items with the byte position calculated correctly + and in sorted order, for display in quickfix and location + lists. + + Parameters: ~ + {locations} (table) list of `Location` s or + `LocationLink` s + + Return: ~ + (table) list of items *vim.lsp.util.make_floating_popup_options()* make_floating_popup_options({width}, {height}, {opts}) - TODO: Documentation + Creates a table with sensible default options for a floating + window. The table can be passed to |nvim_open_win()|. + + Parameters: ~ + {width} (number) window width (in character cells) + {height} (number) window height (in character cells) + {opts} (table, optional) + + Return: ~ + (table) Options *vim.lsp.util.make_formatting_params()* make_formatting_params({options}) - TODO: Documentation + Creates a `FormattingOptions` object for the current buffer + and cursor position. -make_position_param() *vim.lsp.util.make_position_param()* - TODO: Documentation + Parameters: ~ + {options} Table with valid `FormattingOptions` entries + + Return: ~ + `FormattingOptions object + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting make_position_params() *vim.lsp.util.make_position_params()* - TODO: Documentation + Creates a `TextDocumentPositionParams` object for the current + buffer and cursor position. + + Return: ~ + `TextDocumentPositionParams` object + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams make_range_params() *vim.lsp.util.make_range_params()* - TODO: Documentation + Using the current position in the current buffer, creates an + object that can be used as a building block for several LSP + requests, such as `textDocument/codeAction` , + `textDocument/colorPresentation` , + `textDocument/rangeFormatting` . + + Return: ~ + { textDocument = { uri = `current_file_uri` }, range = { + start = `current_position` , end = `current_position` } } make_text_document_params() *vim.lsp.util.make_text_document_params()* - TODO: Documentation + Creates a `TextDocumentIdentifier` object for the current + buffer. -npcall({fn}, {...}) *vim.lsp.util.npcall()* - TODO: Documentation + Return: ~ + `TextDocumentIdentifier` -ok_or_nil({status}, {...}) *vim.lsp.util.ok_or_nil()* - TODO: Documentation + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier *vim.lsp.util.open_floating_preview()* open_floating_preview({contents}, {filetype}, {opts}) @@ -1193,17 +1327,20 @@ open_floating_preview({contents}, {filetype}, {opts}) {opts} dictionary with optional fields Return: ~ - bufnr,winnr buffer and window number of floating window or - nil + bufnr,winnr buffer and window number of the newly created + floating preview window parse_snippet({input}) *vim.lsp.util.parse_snippet()* - TODO: Documentation + Parses snippets in a completion entry. -parse_snippet_rec({input}, {inner}) *vim.lsp.util.parse_snippet_rec()* - TODO: Documentation + Parameters: ~ + {input} (string) unparsed snippet + + Return: ~ + (string) parsed snippet preview_location({location}) *vim.lsp.util.preview_location()* - Preview a location in a floating windows + Preview a location in a floating window behavior depends on type of location: • for Location, range is shown (e.g., function definition) @@ -1211,36 +1348,45 @@ preview_location({location}) *vim.lsp.util.preview_location()* function definition) Parameters: ~ - {location} a single Location or LocationLink + {location} a single `Location` or `LocationLink` Return: ~ - bufnr,winnr buffer and window number of floating window or - nil - - *vim.lsp.util.remove_unmatch_completion_items()* -remove_unmatch_completion_items({items}, {prefix}) - TODO: Documentation + (bufnr,winnr) buffer and window number of floating window + or nil set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()* - TODO: Documentation + Replace text in a range with new text. + + CAUTION: Changes in-place! + + Parameters: ~ + {lines} (table) Original list of strings + {A} (table) Start position; a 2-tuple of {line, + col} numbers + {B} (table) End position; a 2-tuple of {line, + col} numbers + {new_lines} A list of strings to replace the original + + Return: ~ + (table) The modified {lines} object set_loclist({items}) *vim.lsp.util.set_loclist()* - TODO: Documentation + Fills current window's location list with given list of items. + Can be obtained with e.g. |vim.lsp.util.locations_to_items()|. + + Parameters: ~ + {items} (table) list of items set_qflist({items}) *vim.lsp.util.set_qflist()* - TODO: Documentation + Fills quickfix list with given list of items. Can be obtained + with e.g. |vim.lsp.util.locations_to_items()|. + + Parameters: ~ + {items} (table) list of items show_line_diagnostics() *vim.lsp.util.show_line_diagnostics()* - TODO: Documentation - -sort_by_key({fn}) *vim.lsp.util.sort_by_key()* - TODO: Documentation - -sort_completion_items({items}) *vim.lsp.util.sort_completion_items()* - TODO: Documentation - -split_lines({value}) *vim.lsp.util.split_lines()* - TODO: Documentation + Displays the diagnostics for the current line in a floating + hover window. symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()* Convert symbols to quickfix list items @@ -1250,13 +1396,43 @@ symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()* *vim.lsp.util.text_document_completion_list_to_complete_items()* text_document_completion_list_to_complete_items({result}, {prefix}) - TODO: Documentation + Turns the result of a `textDocument/completion` request into + vim-compatible |complete-items|. + + Parameters: ~ + {result} The result of a `textDocument/completion` call, + e.g. from |vim.lsp.buf.completion()|, which may + be one of `CompletionItem[]` , `CompletionList` + or `null` + {prefix} (string) the prefix to filter the completion + items + + Return: ~ + { matches = complete-items table, incomplete = bool } + + See also: ~ + |complete-items| trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()* - TODO: Documentation + Remove empty lines from the beginning and end. + + Parameters: ~ + {lines} (table) list of lines to trim + + Return: ~ + (table) trimmed list of lines *vim.lsp.util.try_trim_markdown_code_blocks()* try_trim_markdown_code_blocks({lines}) - TODO: Documentation + Accepts markdown lines and tries to reduce them to a filetype + if they comprise just a single code block. + + CAUTION: Modifies the input in-place! + + Parameters: ~ + {lines} (table) list of lines + + Return: ~ + (string) filetype or 'markdown' if it was unchanged. vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 6fe1d15b7e..9d7ab9b74f 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -27,11 +27,21 @@ local lsp = { -- TODO improve handling of scratch buffers with LSP attached. +--@private +--- Concatenates and writes a list of strings to the Vim error buffer. +--- +--@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") end +--@private +--- Returns the buffer number for the given {bufnr}. +--- +--@param bufnr (number) Buffer number to resolve. Defaults to the current +---buffer if not given. +--@returns bufnr (number) Number of requested buffer local function resolve_bufnr(bufnr) validate { bufnr = { bufnr, 'n', true } } if bufnr == nil or bufnr == 0 then @@ -40,6 +50,11 @@ local function resolve_bufnr(bufnr) return bufnr end +--@private +--- Checks whether a given path is a directory. +--- +--@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'}} local stat = uv.fs_stat(filename) @@ -55,6 +70,10 @@ local valid_encodings = { } local client_index = 0 +--@private +--- Returns a new, unused client id. +--- +--@returns (number) client id local function next_client_id() client_index = client_index + 1 return client_index @@ -64,6 +83,12 @@ local active_clients = {} local all_buffer_active_clients = {} local uninitialized_clients = {} +--@private +--- Invokes a callback 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 +---each client attached to that buffer. local function for_each_buffer_client(bufnr, callback) validate { callback = { callback, 'f' }; @@ -88,6 +113,11 @@ lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_rever ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1; }) +--@private +--- Normalizes {encoding} to valid LSP encoding names. +--- +--@param encoding (string) Encoding to normalize +--@returns (string) normalized encoding name local function validate_encoding(encoding) validate { encoding = { encoding, 's' }; @@ -96,6 +126,13 @@ local function validate_encoding(encoding) or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding)) end +--@internal +--- Parses a command invocation into the command itself and its args. If there +--- are no arguments, an empty table is returned as the second argument. +--- +--@param input (List) +--@returns (string) the command +--@returns (list of strings) its arguments function lsp._cmd_parts(input) vim.validate{cmd={ input, @@ -114,12 +151,27 @@ function lsp._cmd_parts(input) return cmd, cmd_args end +--@private +--- Augments a validator function with support for optional (nil) values. +--- +--@param fn (function(v)) The original validator function; should return a +---bool. +--@returns (function(v)) The augmented function. Also returns true if {v} is +---`nil`. local function optional_validator(fn) return function(v) return v == nil or fn(v) end end +--@private +--- Validates a client configuration as given to |vim.lsp.start_client()|. +--- +--@param config (table) +--@returns (table) "Cleaned" config, containing only the command, its +---arguments, and a valid encoding. +--- +--@see |vim.lsp.start_client()| local function validate_client_config(config) validate { config = { config, 't' }; @@ -148,6 +200,11 @@ local function validate_client_config(config) } end +--@private +--- Returns full text of buffer {bufnr} as a string. +--- +--@param bufnr (number) Buffer handle, or 0 for current. +--@returns Buffer text as string. local function buf_get_full_text(bufnr) local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), '\n') if nvim_buf_get_option(bufnr, 'eol') then @@ -156,6 +213,11 @@ local function buf_get_full_text(bufnr) return text end +--@private +--- Default handler for the 'textDocument/didOpen' LSP notification. +--- +--@param bufnr (Number) Number of the buffer, or 0 for current +--@param client Client object local function text_document_did_open_handler(bufnr, client) if not client.resolved_capabilities.text_document_open_close then return @@ -176,74 +238,88 @@ local function text_document_did_open_handler(bufnr, client) util.buf_versions[bufnr] = params.textDocument.version end ---- LSP client object. +--- LSP client object. You can get an active client object via +--- |vim.lsp.get_client_by_id()| or |vim.lsp.get_active_clients()|. --- --- - Methods: --- ---- - request(method, params, [callback]) ---- Send a request to the server. 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. +--- - request(method, params, [callback], bufnr) +--- Sends a request to the server. --- This is a thin wrapper around {client.rpc.request} with some additional --- checking. ---- Returns a boolean to indicate if the notification was successful. If it ---- is false, then it will always be false (the client has shutdown). ---- If it was successful, then it will return the request id as the second ---- result. You can use this with `notify("$/cancel", { id = request_id })` ---- to cancel the request. This helper is made automatically with ---- |vim.lsp.buf_request()| ---- Returns: status, [client_id] +--- 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. +--- 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). +--- If {status} is `true`, the function returns {request_id} as the second +--- result. You can use this with `client.cancel_request(request_id)` +--- to cancel the request. --- --- - notify(method, params) ---- This is just {client.rpc.notify}() ---- Returns a boolean to indicate if the notification was successful. If it ---- is false, then it will always be false (the client has shutdown). ---- Returns: status +--- Sends a notification to an LSP server. +--- Returns: a boolean to indicate if the notification was successful. If +--- it is false, then it will always be false (the client has shutdown). --- --- - cancel_request(id) ---- This is just {client.rpc.notify}("$/cancelRequest", { id = id }) ---- Returns the same as `notify()`. +--- Cancels a request with a given request id. +--- Returns: same as `notify()`. --- --- - stop([force]) ---- Stop a client, optionally with force. +--- Stops a client, optionally with force. --- By default, it will just ask the server to shutdown without force. --- If you request to stop a client which has previously been requested to --- shutdown, it will automatically escalate and force shutdown. --- --- - is_stopped() ---- Returns true if the client is fully stopped. +--- Checks whether a client is stopped. +--- Returns: true if the client is fully stopped. +--- +--- - on_attach(bufnr) +--- Runs the on_attach function from the client's config if it was defined. --- --- - Members ---- - id (number): The id allocated to the client. +--- - {id} (number): The id allocated to the client. --- ---- - name (string): If a name is specified on creation, that will be +--- - {name} (string): If a name is specified on creation, that will be --- used. Otherwise it is just the client id. This is used for --- logs and messages. --- ---- - offset_encoding (string): The encoding used for communicating ---- with the server. You can modify this in the `on_init` method +--- - {rpc} (table): RPC client object, for low level interaction with the +--- client. See |vim.lsp.rpc.start()|. +--- +--- - {offset_encoding} (string): The encoding used for communicating +--- 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 +--- - {callbacks} (table): The callbacks used by the client as --- described in |lsp-callbacks|. --- ---- - config (table): copy of the table that was passed by the user +--- - {config} (table): copy of the table that was passed by the user --- to |vim.lsp.start_client()|. --- ---- - server_capabilities (table): Response from the server sent on +--- - {server_capabilities} (table): Response from the server sent on --- `initialize` describing the server's capabilities. --- ---- - resolved_capabilities (table): Normalized table of +--- - {resolved_capabilities} (table): Normalized table of --- capabilities that we have detected based on the initialize --- response from the server in `server_capabilities`. function lsp.client() error() end +-- FIXME: Currently all methods on the `vim.lsp.client` object are documented +-- twice: Here, and on the methods themselves (e.g. `client.request()`). This +-- is a workaround for the vimdoc generator script not handling method names +-- correctly. If you change the documentation on either, please make sure to +-- update the other as well. + --- Starts and initializes a client with the given configuration. --- --- Parameters `cmd` and `root_dir` are required. --- +--- The following parameters describe fields in the {config} table. +--- --@param root_dir: (required, string) Directory where the LSP server will base --- its rootUri on initialization. --- @@ -271,8 +347,8 @@ end --- --@param callbacks Map of language server method names to --- `function(err, method, params, client_id)` handler. Invoked for: ---- - Notifications from the server, where `err` will always be `nil`. ---- - Requests initiated by the server. For these you can respond by returning +--- - 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. @@ -297,7 +373,7 @@ end --@param before_init Callback with parameters (initialize_params, config) --- invoked before the LSP "initialize" phase, where `params` contains the --- parameters being sent to the server and `config` is the config that was ---- passed to `start_client()`. You can use this to modify parameters before +--- passed to |vim.lsp.start_client()|. You can use this to modify parameters before --- they are sent. --- --@param on_init Callback (client, initialize_result) invoked after LSP @@ -335,10 +411,23 @@ function lsp.start_client(config) local handlers = {} + --@private + --- Returns the callback associated with an LSP method. Returns the default + --- callback 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] end + --@private + --- Handles a notification sent by an LSP server by invoking the + --- corresponding callback. + --- + --@param method (string) LSP method name + --@param params (table) The parameters for that method. function handlers.notification(method, params) local _ = log.debug() and log.debug('notification', method, params) local callback = resolve_callback(method) @@ -348,6 +437,12 @@ function lsp.start_client(config) end end + --@private + --- Handles a request from an LSP server by invoking the corresponding + --- callback. + --- + --@param method (string) LSP method name + --@param params (table) The parameters for that method function handlers.server_request(method, params) local _ = log.debug() and log.debug('server_request', method, params) local callback = resolve_callback(method) @@ -359,6 +454,13 @@ function lsp.start_client(config) return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound) end + --@private + --- Invoked when the client operation throws an error. + --- + --@param code (number) Error code + --@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) 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)) @@ -371,6 +473,11 @@ function lsp.start_client(config) end end + --@private + --- Invoked on client exit. + --- + --@param code (number) exit code of the process + --@param signal (number) the signal used to terminate (if any) function handlers.on_exit(code, signal) active_clients[client_id] = nil uninitialized_clients[client_id] = nil @@ -411,6 +518,7 @@ function lsp.start_client(config) -- initialize finishes. uninitialized_clients[client_id] = client; + --@private local function initialize() local valid_traces = { off = 'off'; messages = 'messages'; verbose = 'verbose'; @@ -488,6 +596,12 @@ function lsp.start_client(config) end) end + --@private + --- Throws error for a method that is not supported by the current LSP + --- server. + --- + --@param method (string) an LSP method name not supported by the LSP server. + --@returns (error) a 'MethodNotFound' JSON-RPC error response. local function unsupported_method(method) local msg = "server doesn't support "..method local _ = log.warn() and log.warn(msg) @@ -495,8 +609,28 @@ function lsp.start_client(config) return lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound, msg) end - --- Checks capabilities before rpc.request-ing. + --@private + --- Sends a request to the server. + --- + --- This is a thin wrapper around {client.rpc.request} with some additional + --- checks for capabilities and callback 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 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 + ---always be `false` (the client has shutdown). If it was + ---successful, then it will return {request_id} as the + ---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)) @@ -521,10 +655,25 @@ function lsp.start_client(config) end) end + --@private + --- Sends a notification to an LSP server. + --- + --@param method (string) LSP method name. + --@param params (optional, table) LSP request params. + --@param bufnr (number) Buffer handle, or 0 for current. + --@returns {status} (bool) true if the notification was successful. + ---If it is false, then it will always be false + ---(the client has shutdown). function client.notify(...) return rpc.notify(...) end + --@private + --- Cancels a request with a given request id. + --- + --@param id (number) id of request to cancel + --@returns true if any client returns true; false otherwise + --@see |vim.lsp.client.notify()| function client.cancel_request(id) validate{id = {id, 'n'}} return rpc.notify("$/cancelRequest", { id = id }) @@ -533,6 +682,14 @@ function lsp.start_client(config) -- Track this so that we can escalate automatically if we've alredy tried a -- graceful shutdown local tried_graceful_shutdown = false + --@private + --- Stops a client, optionally with force. + --- + ---By default, it will just ask the - server to shutdown without force. If + --- you request to stop a client which has previously been requested to + --- shutdown, it will automatically escalate and force shutdown. + --- + --@param force (bool, optional) function client.stop(force) local handle = rpc.handle if handle:is_closing() then @@ -554,10 +711,18 @@ function lsp.start_client(config) end) end + --@private + --- Checks whether a client is stopped. + --- + --@returns (bool) true if client is stopped or in the process of being + ---stopped; false otherwise function client.is_stopped() return rpc.handle:is_closing() end + --@private + --- Runs the on_attach function from the client's config if it was defined. + --@param bufnr (number) Buffer number function client._on_attach(bufnr) text_document_did_open_handler(bufnr, client) if config.on_attach then @@ -571,6 +736,12 @@ function lsp.start_client(config) return client_id end +--@private +--- Memoizes a function. On first run, the function return value is saved and +--- immediately returned on subsequent runs. +--- +--@param fn (function) Function to run +--@returns (function) Memoized function local function once(fn) local value return function(...) @@ -579,6 +750,9 @@ local function once(fn) end end +--@private +--@fn text_document_did_change_handler(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size) +--- Notify all attached clients that a buffer has changed. local text_document_did_change_handler do local encoding_index = { ["utf-8"] = 1; ["utf-16"] = 2; ["utf-32"] = 3; } @@ -735,7 +909,7 @@ end --- --@param client_id client id number --- ---@return |vim.lsp.client| object, or nil +--@returns |vim.lsp.client| object, or nil function lsp.get_client_by_id(client_id) return active_clients[client_id] end @@ -769,7 +943,7 @@ end --- Gets all active clients. --- ---@return Table of |vim.lsp.client| objects +--@returns Table of |vim.lsp.client| objects function lsp.get_active_clients() return vim.tbl_values(active_clients) end @@ -904,7 +1078,7 @@ end --@param findstart 0 or 1, decides behavior --@param base If findstart=0, text to match against --- ---@return (number) Decided by `findstart`: +--@returns (number) Decided by `findstart`: --- - 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) @@ -948,6 +1122,10 @@ function lsp.omnifunc(findstart, base) return -2 end +---Checks whether a client is stopped. +--- +--@param client_id (Number) +--@returns true if client is stopped, false otherwise. function lsp.client_is_stopped(client_id) return active_clients[client_id] == nil end @@ -992,12 +1170,17 @@ function lsp.set_log_level(level) end --- Gets the path of the logfile used by the LSP client. +--@returns (String) Path to logfile. function lsp.get_log_path() return log.get_filename() end --- Define the LspDiagnostics signs if they're not defined already. +-- 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) 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: +--- +---
+--- vim.api.nvim_command[[autocmd BufWritePre  lua vim.lsp.buf.formatting_sync()]]
+--- 
+--- +--@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..9920af0124 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -7,17 +7,22 @@ local buf = require 'vim.lsp.buf' local M = {} +--@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 +56,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 +70,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 +109,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 +117,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 +131,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 +167,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 +188,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 +216,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 +241,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 +276,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 +303,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..f19367851f 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -21,12 +21,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 @@ -74,6 +76,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 +88,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..8ed09bf34a 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", } --- +--@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,12 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para return true end - local function send_notification(method, params) + -- FIXME: 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 +382,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 +393,14 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para } end - local function send_request(method, params, callback) + -- FIXME: 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 +426,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 +440,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 +449,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 +568,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..7e94de7ec3 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: Expose in documentation --- 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 ---- Apply the TextDocumentEdit response. --- @params TextDocumentEdit [table] see https://microsoft.github.io/language-server-protocol/specification +--- Applies a `TextDocumentEdit`, which is a list of changes to a single +-- document. +--- +--@param text_document_edit (table) a `TextDocumentEdit` object +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit function M.apply_text_document_edit(text_document_edit) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) @@ -195,6 +224,13 @@ function M.apply_text_document_edit(text_document_edit) M.apply_text_edits(text_document_edit.edits, bufnr) end +--@private +--- Recursively parses snippets in a completion entry. +--- +--@param input (string) Snippet text to parse for snippets +--@param inner (bool) Whether this function is being called recursively +--@returns 2-tuple of strings: The first is the parsed result, the second is the +---unparsed rest of the input local function parse_snippet_rec(input, inner) local res = "" @@ -248,15 +284,20 @@ local function parse_snippet_rec(input, inner) return res, input end --- Parse completion entries, consuming snippet tokens +--- Parses snippets in a completion entry. +--- +--@param input (string) unparsed snippet +--@returns (string) parsed snippet function M.parse_snippet(input) local res, _ = parse_snippet_rec(input, false) return res end --- Sort by CompletionItem.sortText --- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +--@private +--- Sorts by CompletionItem.sortText. +--- +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function sort_completion_items(items) if items[1] and items[1].sortText then table.sort(items, function(a, b) return a.sortText < b.sortText @@ -264,9 +305,10 @@ local function sort_completion_items(items) end end --- Returns text that should be inserted when selecting completion item. The precedence is as follows: --- textEdit.newText > insertText > label --- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +--@private +--- Returns text that should be inserted when selecting completion item. The +--- precedence is as follows: textEdit.newText > insertText > label +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function get_completion_word(item) if item.textEdit ~= nil and item.textEdit.newText ~= nil then if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then @@ -284,8 +326,10 @@ local function get_completion_word(item) return item.label end --- Some language servers return complementary candidates whose prefixes do not match are also returned. --- So we exclude completion candidates whose prefix does not match. +--@private +--- Some language servers return complementary candidates whose prefixes do not +--- match are also returned. So we exclude completion candidates whose prefix +--- does not match. local function remove_unmatch_completion_items(items, prefix) return vim.tbl_filter(function(item) local word = get_completion_word(item) @@ -293,16 +337,26 @@ local function remove_unmatch_completion_items(items, prefix) end, items) end --- Acording to LSP spec, if the client set "completionItemKind.valueSet", --- the client must handle it properly even if it receives a value outside the specification. --- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +--- Acording to LSP spec, if the client set `completionItemKind.valueSet`, +--- the client must handle it properly even if it receives a value outside the +--- specification. +--- +--@param completion_item_kind (`vim.lsp.protocol.completionItemKind`) +--@returns (`vim.lsp.protocol.completionItemKind`) +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion function M._get_completion_item_kind_name(completion_item_kind) return protocol.CompletionItemKind[completion_item_kind] or "Unknown" end ---- Getting vim complete-items with incomplete flag. --- @params CompletionItem[], CompletionList or nil (https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) --- @return { matches = complete-items table, incomplete = boolean } +--- Turns the result of a `textDocument/completion` request into vim-compatible +--- |complete-items|. +--- +--@param result The result of a `textDocument/completion` call, e.g. from +---|vim.lsp.buf.completion()|, which may be one of `CompletionItem[]`, +--- `CompletionList` or `null` +--@param prefix (string) the prefix to filter the completion items +--@returns { matches = complete-items table, incomplete = bool } +--@see |complete-items| function M.text_document_completion_list_to_complete_items(result, prefix) local items = M.extract_completion_items(result) if vim.tbl_isempty(items) then @@ -350,7 +404,10 @@ function M.text_document_completion_list_to_complete_items(result, prefix) return matches end --- @params WorkspaceEdit [table] see https://microsoft.github.io/language-server-protocol/specification +--- Applies a `WorkspaceEdit`. +--- +--@param workspace_edit (table) `WorkspaceEdit` +-- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then for _, change in ipairs(workspace_edit.documentChanges) do @@ -375,9 +432,15 @@ function M.apply_workspace_edit(workspace_edit) end end ---- Convert any of MarkedString | MarkedString[] | MarkupContent into markdown text lines --- see https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_hover --- Useful for textDocument/hover, textDocument/signatureHelp, and potentially others. +--- Converts any of `MarkedString` | `MarkedString[]` | `MarkupContent` into +--- a list of lines containing valid markdown. Useful to populate the hover +--- window for `textDocument/hover`, for parsing the result of +--- `textDocument/signatureHelp`, and potentially others. +--- +--@param input (`MarkedString` | `MarkedString[]` | `MarkupContent`) +--@param contents (table, optional, default `{}`) List of strings to extend with converted lines +--@returns {contents}, extended with lines of converted markdown. +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover function M.convert_input_to_markdown_lines(input, contents) contents = contents or {} -- MarkedString variation 1 @@ -416,8 +479,11 @@ function M.convert_input_to_markdown_lines(input, contents) return contents end ---- Convert SignatureHelp response to markdown lines. --- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp +--- Converts `textDocument/SignatureHelp` response to markdown lines. +--- +--@param signature_help Response of `textDocument/SignatureHelp` +--@returns list of lines of converted markdown. +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp function M.convert_signature_help_to_markdown_lines(signature_help) if not signature_help.signatures then return @@ -475,6 +541,13 @@ function M.convert_signature_help_to_markdown_lines(signature_help) return contents end +--- Creates a table with sensible default options for a floating window. The +--- table can be passed to |nvim_open_win()|. +--- +--@param width (number) window width (in character cells) +--@param height (number) window height (in character cells) +--@param opts (table, optional) +--@returns (table) Options function M.make_floating_popup_options(width, height, opts) validate { opts = { opts, 't', true }; @@ -520,6 +593,10 @@ function M.make_floating_popup_options(width, height, opts) } end +--- Jumps to a location. +--- +--@param location (`Location`|`LocationLink`) +--@returns `true` if the jump succeeded function M.jump_to_location(location) -- location may be Location or LocationLink local uri = location.uri or location.targetUri @@ -543,14 +620,14 @@ function M.jump_to_location(location) return true end ---- Preview a location in a floating windows +--- Previews a location in a floating window --- --- behavior depends on type of location: --- - for Location, range is shown (e.g., function definition) --- - for LocationLink, targetRange is shown (e.g., body of function definition) --- ---@param location a single Location or LocationLink ---@return bufnr,winnr buffer and window number of floating window or nil +--@param location a single `Location` or `LocationLink` +--@returns (bufnr,winnr) buffer and window number of floating window or nil function M.preview_location(location) -- location may be LocationLink or Location (more useful for the former) local uri = location.targetUri or location.uri @@ -565,6 +642,7 @@ function M.preview_location(location) return M.open_floating_preview(contents, filetype) end +--@private local function find_window_by_var(name, value) for _, win in ipairs(api.nvim_list_wins()) do if npcall(api.nvim_win_get_var, win, name) == value then @@ -573,12 +651,18 @@ local function find_window_by_var(name, value) end end --- Check if a window with `unique_name` tagged is associated with the current --- buffer. If not, make a new preview. --- --- fn()'s return bufnr, winnr --- case that a new floating window should be created. +--- Enters/leaves the focusable window associated with the current buffer via the +--window - variable `unique_name`. If no such window exists, run the function +--{fn}. +--- +--@param unique_name (string) Window variable +--@param fn (function) should return create a new window and return a tuple of +---({focusable_buffer_id}, {window_id}). if {focusable_buffer_id} is a valid +---buffer id, the newly created window will be the new focus associated with +---the current buffer via the tag `unique_name`. +--@returns (pbufnr, pwinnr) if `fn()` has created a new window; nil otherwise function M.focusable_float(unique_name, fn) + -- Go back to previous window if we are in a focusable one if npcall(api.nvim_win_get_var, 0, unique_name) then return api.nvim_command("wincmd p") end @@ -598,18 +682,21 @@ function M.focusable_float(unique_name, fn) end end --- Check if a window with `unique_name` tagged is associated with the current --- buffer. If not, make a new preview. --- --- fn()'s return values will be passed directly to open_floating_preview in the --- case that a new floating window should be created. +--- Focuses/unfocuses the floating preview window associated with the current +--- buffer via the window variable `unique_name`. If no such preview window +--- exists, makes a new one. +--- +--@param unique_name (string) Window variable +--@param fn (function) The return values of this function will be passed +---directly to |vim.lsp.util.open_floating_preview()|, in the case that a new +---floating window should be created function M.focusable_preview(unique_name, fn) return M.focusable_float(unique_name, function() return M.open_floating_preview(fn()) end) end ---- Trim empty lines from input and pad left and right with spaces +--- Trims empty lines from input and pad left and right with spaces --- --@param contents table of lines to trim and pad --@param opts dictionary with optional fields @@ -617,7 +704,7 @@ end -- - pad_right number of columns to pad contents at right (default 1) -- - pad_top number of lines to pad contents at top (default 0) -- - pad_bottom number of lines to pad contents at bottom (default 0) ---@return contents table of trimmed and padded lines +--@returns contents table of trimmed and padded lines function M._trim_and_pad(contents, opts) validate { contents = { contents, 't' }; @@ -645,12 +732,13 @@ end ---- Convert markdown into syntax highlighted regions by stripping the code +-- TODO: refactor to separate stripping/converting and make use of open_floating_preview +-- +--- Converts markdown into syntax highlighted regions by stripping the code --- blocks and converting them into highlighted code. --- This will by default insert a blank line separator after those code block --- regions to improve readability. ---- The result is shown in a floating preview ---- TODO: refactor to separate stripping/converting and make use of open_floating_preview +--- The result is shown in a floating preview. --- --@param contents table of lines to show in window --@param opts dictionary with optional fields @@ -664,7 +752,7 @@ end -- - pad_top number of lines to pad contents at top -- - pad_bottom number of lines to pad contents at bottom -- - separator insert separator after code block ---@return width,height size of float +--@returns width,height size of float function M.fancy_floating_markdown(contents, opts) validate { contents = { contents, 't' }; @@ -738,6 +826,7 @@ function M.fancy_floating_markdown(contents, opts) vim.cmd("ownsyntax markdown") local idx = 1 + --@private local function apply_syntax_to_region(ft, start, finish) if ft == '' then return end local name = ft..idx @@ -763,11 +852,17 @@ function M.fancy_floating_markdown(contents, opts) return bufnr, winnr end +--- Creates autocommands to close a preview window when events happen. +--- +--@param events (table) list of events +--@param winnr (number) window id of preview window +--@see |autocmd-events| function M.close_preview_autocmd(events, winnr) api.nvim_command("autocmd "..table.concat(events, ',').." ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)") end ---- Compute size of float needed to show contents (with optional wrapping) +--@internal +--- Computes size of float needed to show contents (with optional wrapping) --- --@param contents table of lines to show in window --@param opts dictionary with optional fields @@ -776,7 +871,7 @@ end -- - wrap_at character to wrap at for computing height -- - max_width maximal width of floating window -- - max_height maximal height of floating window ---@return width,height size of float +--@returns width,height size of float function M._make_floating_popup_size(contents, opts) validate { contents = { contents, 't' }; @@ -827,7 +922,7 @@ function M._make_floating_popup_size(contents, opts) return width, height end ---- Show contents in a floating window +--- Shows contents in a floating window. --- --@param contents table of lines to show in window --@param filetype string of filetype to set for opened buffer @@ -841,7 +936,8 @@ end -- - pad_right number of columns to pad contents at right -- - pad_top number of lines to pad contents at top -- - pad_bottom number of lines to pad contents at bottom ---@return bufnr,winnr buffer and window number of floating window or nil +--@returns bufnr,winnr buffer and window number of the newly created floating +---preview window function M.open_floating_preview(contents, filetype, opts) validate { contents = { contents, 't' }; @@ -912,6 +1008,9 @@ do severity_floating_highlights[severity] = floating_highlight_name end + --- Clears diagnostics for a buffer. + --- + --@param bufnr (number) buffer id function M.buf_clear_diagnostics(bufnr) validate { bufnr = {bufnr, 'n', true} } bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr @@ -923,10 +1022,18 @@ do api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1) end + --- Gets the name of a severity's highlight group. + --- + --@param severity A member of `vim.lsp.protocol.DiagnosticSeverity` + --@returns (string) Highlight group name function M.get_severity_highlight_name(severity) return severity_highlights[severity] end + --- Gets list of diagnostics for the current line. + --- + --@returns (table) list of `Diagnostic` tables + --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic function M.get_line_diagnostics() local bufnr = api.nvim_get_current_buf() local linenr = api.nvim_win_get_cursor(0)[1] - 1 @@ -941,6 +1048,8 @@ do return diagnostics_by_line[linenr] or {} end + --- Displays the diagnostics for the current line in a floating hover + --- window. function M.show_line_diagnostics() -- local marks = api.nvim_buf_get_extmarks(bufnr, diagnostic_ns, {line, 0}, {line, -1}, {}) -- if #marks == 0 then @@ -977,10 +1086,10 @@ do return popup_bufnr, winnr end - --- Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf + --- Saves diagnostics into vim.lsp.util.diagnostics_by_buf[{bufnr}]. --- - --@param bufnr bufnr for which the diagnostics are for. - --@param diagnostics Diagnostics[] received from the language server. + --@param bufnr (number) buffer id for which the diagnostics are for + --@param diagnostics list of `Diagnostic`s received from the LSP server function M.buf_diagnostics_save_positions(bufnr, diagnostics) validate { bufnr = {bufnr, 'n', true}; @@ -1000,6 +1109,10 @@ do M.diagnostics_by_buf[bufnr] = diagnostics end + --- Highlights a list of diagnostics in a buffer by underlining them. + --- + --@param bufnr (number) buffer id + --@param diagnostics (list of `Diagnostic`s) function M.buf_diagnostics_underline(bufnr, diagnostics) for _, diagnostic in ipairs(diagnostics) do local start = diagnostic.range["start"] @@ -1020,11 +1133,18 @@ do end end + --- Removes document highlights from a buffer. + --- + --@param bufnr buffer id function M.buf_clear_references(bufnr) validate { bufnr = {bufnr, 'n', true} } api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1) end + --- Shows a list of document highlights for a certain buffer. + --- + --@param bufnr buffer id + --@param references List of `DocumentHighlight` objects to highlight function M.buf_highlight_references(bufnr, references) validate { bufnr = {bufnr, 'n', true} } for _, reference in ipairs(references) do @@ -1040,11 +1160,19 @@ do end end + --- Groups a list of diagnostics by line. + --- + --@param diagnostics (table) list of `Diagnostic`s + --@returns (table) dictionary mapping lines to lists of diagnostics valid on + ---those lines + --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic function M.diagnostics_group_by_line(diagnostics) if not diagnostics then return end local diagnostics_by_line = {} for _, diagnostic in ipairs(diagnostics) do local start = diagnostic.range.start + -- TODO: Are diagnostics only valid for a single line? I don't understand + -- why this would be okay otherwise local line_diagnostics = diagnostics_by_line[start.line] if not line_diagnostics then line_diagnostics = {} @@ -1055,6 +1183,11 @@ do return diagnostics_by_line end + --- Given a list of diagnostics, sets the corresponding virtual text for a + --- buffer. + --- + --@param bufnr buffer id + --@param diagnostics (table) list of `Diagnostic`s function M.buf_diagnostics_virtual_text(bufnr, diagnostics) if not diagnostics then return @@ -1093,8 +1226,7 @@ do --- --- --@param kind Diagnostic severity kind: See |vim.lsp.protocol.DiagnosticSeverity| - --- - --@return Count of diagnostics + --@returns Count of diagnostics function M.buf_diagnostics_count(kind) local bufnr = vim.api.nvim_get_current_buf() local diagnostics = M.diagnostics_by_buf[bufnr] @@ -1115,7 +1247,7 @@ do [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign"; } - --- Place signs for each diagnostic in the sign column. + --- Places signs for each diagnostic in the sign column. --- --- Sign characters can be customized with the following commands: --- @@ -1136,8 +1268,11 @@ local position_sort = sort_by_key(function(v) return {v.start.line, v.start.character} end) --- Returns the items with the byte position calculated correctly and in sorted --- order. +--- Returns the items with the byte position calculated correctly and in sorted +--- order, for display in quickfix and location lists. +--- +--@param locations (table) list of `Location`s or `LocationLink`s +--@returns (table) list of items function M.locations_to_items(locations) local items = {} local grouped = setmetatable({}, { @@ -1180,6 +1315,10 @@ function M.locations_to_items(locations) return items end +--- Fills current window's location list with given list of items. +--- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|. +--- +--@param items (table) list of items function M.set_loclist(items) vim.fn.setloclist(0, {}, ' ', { title = 'Language Server'; @@ -1187,6 +1326,10 @@ function M.set_loclist(items) }) end +--- Fills quickfix list with given list of items. +--- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|. +--- +--@param items (table) list of items function M.set_qflist(items) vim.fn.setqflist({}, ' ', { title = 'Language Server'; @@ -1201,10 +1344,11 @@ function M._get_symbol_kind_name(symbol_kind) return protocol.SymbolKind[symbol_kind] or "Unknown" end ---- Convert symbols to quickfix list items +--- Converts symbols to quickfix list items. --- --@param symbols DocumentSymbol[] or SymbolInformation[] function M.symbols_to_items(symbols, bufnr) + --@private local function _symbols_to_items(_symbols, _items, _bufnr) for _, symbol in ipairs(_symbols) do if symbol.location then -- SymbolInformation type @@ -1239,7 +1383,9 @@ function M.symbols_to_items(symbols, bufnr) return _symbols_to_items(symbols, {}, bufnr) end --- Remove empty lines from the beginning and end. +--- Removes empty lines from the beginning and end. +--@param lines (table) list of lines to trim +--@returns (table) trimmed list of lines function M.trim_empty_lines(lines) local start = 1 for i = 1, #lines do @@ -1258,11 +1404,13 @@ function M.trim_empty_lines(lines) return vim.list_extend({}, lines, start, finish) end --- Accepts markdown lines and tries to reduce it to a filetype if it is --- just a single code block. --- Note: This modifies the input. --- --- Returns: filetype or 'markdown' if it was unchanged. +--- Accepts markdown lines and tries to reduce them to a filetype if they +--- comprise just a single code block. +--- +--- CAUTION: Modifies the input in-place! +--- +--@param lines (table) list of lines +--@returns (string) filetype or 'markdown' if it was unchanged. function M.try_trim_markdown_code_blocks(lines) local language_id = lines[1]:match("^```(.*)") if language_id then @@ -1285,6 +1433,7 @@ function M.try_trim_markdown_code_blocks(lines) end local str_utfindex = vim.str_utfindex +--@private local function make_position_param() local row, col = unpack(api.nvim_win_get_cursor(0)) row = row - 1 @@ -1293,6 +1442,10 @@ local function make_position_param() return { line = row; character = col; } end +--- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. +--- +--@returns `TextDocumentPositionParams` object +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams function M.make_position_params() return { textDocument = M.make_text_document_params(); @@ -1300,6 +1453,13 @@ function M.make_position_params() } end +--- Using the current position in the current buffer, creates an object that +--- can be used as a building block for several LSP requests, such as +--- `textDocument/codeAction`, `textDocument/colorPresentation`, +--- `textDocument/rangeFormatting`. +--- +--@returns { textDocument = { uri = `current_file_uri` }, range = { start = +---`current_position`, end = `current_position` } } function M.make_range_params() local position = make_position_param() return { @@ -1308,11 +1468,15 @@ function M.make_range_params() } end +--- Creates a `TextDocumentIdentifier` object for the current buffer. +--- +--@returns `TextDocumentIdentifier` +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier function M.make_text_document_params() return { uri = vim.uri_from_bufnr(0) } end ---- Get visual width of tabstop. +--- Returns visual width of tabstop. --- --@see |softtabstop| --@param bufnr (optional, number): Buffer handle, defaults to current @@ -1324,6 +1488,11 @@ function M.get_effective_tabstop(bufnr) return (sts > 0 and sts) or (sts < 0 and bo.shiftwidth) or bo.tabstop end +--- Creates a `FormattingOptions` object for the current buffer and cursor position. +--- +--@param options Table with valid `FormattingOptions` entries +--@returns `FormattingOptions object +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting function M.make_formatting_params(options) validate { options = {options, 't', true} } options = vim.tbl_extend('keep', options or {}, { @@ -1336,9 +1505,12 @@ function M.make_formatting_params(options) } end --- @param buf buffer handle or 0 for current. --- @param row 0-indexed line --- @param col 0-indexed byte offset in line +--- Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer. +--- +--@param buf buffer id (0 for current) +--@param row 0-indexed line +--@param col 0-indexed byte offset in line +--@returns (number, number) UTF-32 and UTF-16 index of the character in line {row} column {col} in buffer {buf} function M.character_offset(buf, row, col) local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1] -- If the col is past the EOL, use the line length. diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index a61690e99f..f2f003033e 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -960,7 +960,11 @@ def main(config): i = 0 for filename in CONFIG[target]['section_order']: - title, helptag, section_doc = sections.pop(filename) + try: + title, helptag, section_doc = sections.pop(filename) + except KeyError: + print("Warning:", filename, "has empty docs, skipping") + continue i += 1 if filename not in CONFIG[target]['append_only']: docs += sep diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d6f95c7a5f..305d5e6968 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -203,10 +203,10 @@ Integer nvim_get_hl_id_by_name(String name) /// flags. This is a blocking call, unlike |nvim_input()|. /// /// On execution error: does not fail, but updates v:errmsg. -// -// If you need to input sequences like use nvim_replace_termcodes -// to replace the termcodes and then pass the resulting string to -// nvim_feedkeys. You'll also want to enable escape_csi. +/// +/// If you need to input sequences like use |nvim_replace_termcodes| +/// to replace the termcodes and then pass the resulting string to +/// nvim_feedkeys. You'll also want to enable escape_csi. /// /// Example: ///