## Overview
- Move vim.lsp.diagnostic to vim.diagnostic
- Refactor client ids to diagnostic namespaces
- Update tests
- Write/update documentation and function signatures
Currently, non-LSP diagnostics in Neovim must hook into the LSP subsystem. This
is what e.g. null-ls and nvim-lint do. This is necessary because none of the
diagnostic API is exposed separately from the LSP subsystem.
This commit addresses this by generalizing the diagnostic subsystem beyond the
scope of LSP. The `vim.lsp.diagnostic` module is now simply a specific
diagnostic producer and primarily maintains the interface between LSP clients
and the broader diagnostic API.
The current diagnostic API uses "client ids" which only makes sense in the
context of LSP. We replace "client ids" with standard API namespaces generated
from `nvim_create_namespace`.
This PR is *mostly* backward compatible (so long as plugins are only using the
publicly documented API): LSP diagnostics will continue to work as usual, as
will pseudo-LSP clients like null-ls and nvim-lint. However, the latter can now
use the new interface, which looks something like this:
```lua
-- The namespace *must* be given a name. Anonymous namespaces will not work with diagnostics
local ns = vim.api.nvim_create_namespace("foo")
-- Generate diagnostics
local diagnostics = generate_diagnostics()
-- Set diagnostics for the current buffer
vim.diagnostic.set(ns, diagnostics, bufnr)
```
Some public facing API utility methods were removed and internalized directly in `vim.diagnostic`:
* `vim.lsp.util.diagnostics_to_items`
## API Design
`vim.diagnostic` contains most of the same API as `vim.lsp.diagnostic` with
`client_id` simply replaced with `namespace`, with some differences:
* Generally speaking, functions that modify or add diagnostics require a namespace as their first argument, e.g.
```lua
vim.diagnostic.set({namespace}, {bufnr}, {diagnostics}[, {opts}])
```
while functions that read or query diagnostics do not (although in many cases one may be supplied optionally):
```lua
vim.diagnostic.get({bufnr}[, {namespace}])
```
* We use our own severity levels to decouple `vim.diagnostic` from LSP. These
are designed similarly to `vim.log.levels` and currently include:
```lua
vim.diagnostic.severity.ERROR
vim.diagnostic.severity.WARN
vim.diagnostic.severity.INFO
vim.diagnostic.severity.HINT
```
In practice, these match the LSP diagnostic severity levels exactly, but we
should treat this as an interface and not assume that they are the same. The
"translation" between the two severity types is handled transparently in
`vim.lsp.diagnostic`.
* The actual "diagnostic" data structure is: (**EDIT:** Updated 2021-09-09):
```lua
{
lnum = <number>,
col = <number>,
end_lnum = <number>,
end_col = <number>,
severity = <vim.diagnostic.severity>,
message = <string>
}
```
This differs from the LSP definition of a diagnostic, so we transform them in
the handler functions in vim.lsp.diagnostic.
## Configuration
The `vim.lsp.with` paradigm still works for configuring how LSP diagnostics are
displayed, but this is a specific use-case for the `publishDiagnostics` handler.
Configuration with `vim.diagnostic` is instead done with the
`vim.diagnostic.config` function:
```lua
vim.diagnostic.config({
virtual_text = true,
signs = false,
underline = true,
update_in_insert = true,
severity_sort = false,
}[, namespace])
```
(or alternatively passed directly to `set()` or `show()`.)
When the `namespace` argument is `nil`, settings are set globally (i.e. for
*all* diagnostic namespaces). This is what user's will typically use for their
local configuration. Diagnostic producers can also set configuration options for
their specific namespace, although this is generally discouraged in order to
respect the user's global settings. All of the values in the table passed to
`vim.diagnostic.config()` are resolved in the same way that they are in
`on_publish_diagnostics`; that is, the value can be a boolean, a table, or
a function:
```lua
vim.diagnostic.config({
virtual_text = function(namespace, bufnr)
-- Only enable virtual text in buffer 3
return bufnr == 3
end,
})
```
## Misc Notes
* `vim.diagnostic` currently depends on `vim.lsp.util` for floating window
previews. I think this is okay for now, although ideally we'd want to decouple
these completely.
This generalizes diagnostic handling outside of just the scope of LSP.
LSP clients are now a specific case of a diagnostic producer, but the
diagnostic subsystem is decoupled from the LSP subsystem (or will be,
eventually).
More discussion at [1].
[1]: https://github.com/neovim/neovim/pull/15585
* Simplify rpc encode/decode messages to rpc.send/rcp.receive
* Make missing handlers message throw a warning
* Clean up formatting style in log
* Move all non-RPC loop messages to trace instead of debug
* Add format func option to log to allow newlines in per log entry
Previously, the handler signature was:
function(err, method, params, client_id, bufnr, config)
In order to better support external plugins that wish to extend the
protocol, there is other information which would be advantageous to
forward to the client, such as the original params of the request that
generated the callback.
In order to do this, we would need to break symmetry of the handlers, to
add an additional "params" as the 7th argument.
Instead, this PR changes the signature of the handlers to:
function(err, result, ctx, config)
where ctx (the context) includes params, client_id, and bufnr. This also leaves
flexibility for future use-cases.
BREAKING_CHANGE: changes the signature of the built-in client handlers, requiring
updating handler calls
Resolve an issue with deferred clearing of highlight failing if the
buffer is deleted before the timeout by checking whether the
buffer is valid first.
Declaration, type-definition, and implementation capabilities were
previously disabled if the client received table output from the server
capabilities. The workDoneProgress capability is sent for many servers
for all supported capabilities as part of this table. Default to setting
capability to table instead of false.
The official developer documentation in in :h dev-lua-doc specifies to
use "--@" for special/magic tokens. However, this format is not
consistent with EmmyLua notation (used by some Lua language servers) nor
with the C version of the magic docstring tokens which use three comment
characters.
Further, the code base is currently split between usage of "--@",
"---@", and "--- @". In an effort to remain consistent, change all Lua
magic tokens to use "---@" and update the developer documentation
accordingly.
According to the protocol definition `rootPath`, `rootUri` and
`workspaceFolders` are allowed to be null.
Some language servers utilize this to provide "single file" support.
If all three are null, they don't attempt to index a directory but
instead only provide capabilities for a single file.
This changes the behavior of the hl_cache to the old one.
- when the capture exists as a hlgroup -> use it
- when hl_map contains a mapping -> use it
- else do nothing (before: map capture to non-existing capture)
Before also captures `@foo.bar` would intend to use the hlgroup `foo.bar`
which results in a confusing error since hlgroups can't contain dots.
Add a new function to redraw diagnostics from the current diagnostic
cache, without receiving a "publishDiagnostics" message from the server.
This is already being done in two places in the Lua stdlib, so this
function unifies that functionality in addition to providing it to third
party plugins.
An example use case for this could be a command or key-binding for
toggling diagnostics virtual text. The virtual text configuration option
can be toggled using `vim.lsp.with` followed by
`vim.lsp.diagnostic.redraw()` to immediately redraw the diagnostics
with the updated setting.
The handlers for textDocument/references, textDocument/documentSymbol,
and workspace/symbol open their results in the quickfix list by default
and are not configurable. They are also incompatible with `vim.lsp.with`
as they do not accept a configuration parameter.
Add a `config` parameter to the handler for these three messages which
allows them to be configured with `vim.lsp.with`. Additionally, add a
new configuration option 'loclist' that, when true, causes these
handlers to open their results in the location list rather than the
quickfix list.
Some language servers *cough*rust-analyzer*cough* need an empty/custom
workspaceFolders for certain usecases. For example, rust-analyzer
needs an empty workspaceFolders table for standalone file support
(See https://github.com/rust-analyzer/rust-analyzer/pull/8955).
This can also be useful for other languages that need to commonly
open a certain directory (like flutter or lua), which would help
prevent spinning up a new language server altogether.
In case no workspaceFolders are passed, we fallback to what we had
before.
Passing `nil` is equivalent to passing 0, i.e. it simply uses the
current buffer number.
This fixes a bug when vim.lsp.diagnostic.disable() is called without
arguments.