Extract the diagnostic implementation from
runtime/lua/vim/diagnostic.lua into focused internal modules covering
config, display, float rendering, jump/list helpers, namespace and
storage management, severity/shared utilities, and statusline support.
Move the builtin handlers into runtime/lua/vim/diagnostic/handlers/ and
keep runtime/lua/vim/diagnostic.lua as the public facade that lazily
dispatches to the split modules. This preserves the external
vim.diagnostic API while making the implementation easier to navigate
and reason about.
AI-assisted: Codex
Problem:
The `float` field of vim.diagnostic.config can be a function,
but diagnostic.open_float() does not handle it correctly.
Solution:
Add handling for it in open_float().
Problem: Statusline component of diagnostics allows only the default
format "sign:count".
Solution: Extend vim.diagnostic.Opts.Status to allow a custom signs
or formatting function that provides the status presentation.
Problem:
In autocmd examples, using "args" as the event-object name is vague and
may be confused with a user-command.
Solution:
Use "ev" as the conventional event-object name.
Problem:
`vim.diagnostic.fromqflist` ignores lines that are `item.valid == 0` (see
`getqflist`). Many qflists have messages that span multiple lines, which look
like this:
collection/src/Modelling/CdOd/Central.hs|496 col 80| error: [GHC-83865]
|| • Couldn't match expected type: InstanceWithForm
|| (FilePath
|| -> SelectValidCdInstWithForm
...
calling `vim.diagnostic.fromqflist(vim.fn.getqflist)` gets a diagnostic message
like this:
error: [GHC-83865]
only the first line is kept, but often, the remaing lines are useful as well.
Solution:
Introduce `merge_lines` option, which "squashes" lines from invalid qflist items
into the error message of the previous valid item, so that we get this
diagnostic message instead:
error: [GHC-83865]
• Couldn't match expected type: InstanceWithForm
(FilePath
-> SelectValidCdInstWithForm
Problem
LSP Related Information line and column numbers are 0-based. Displaying
them this way can confuse the user, since vim line/col numbers are
typically displayed 1-based.
Solution
Display the line and column numbers as 1-based.
Problem: The function get_logical_pos did not account for the possibility that a diagnostic might not have an associated extmark, leading to potential errors or incorrect behavior.
Solution: Add a check for diagnostic._extmark_id and return the logical positions directly if it does not exist.
Problem:
`diagnostic.status()` is configured via `config.signs`, but users may
want diagnostics only in statusline, not in the gutter (signs).
Solution:
Add `config.status`.
Problem: unnecessary and deprecated diagnostics use their own highlight
groups (`DiagnosticUnnecessary` and `DiagnosticDeprecated`) which
override the typical severity-based highlight groups (like
`DiagnosticUnderlineWarn`).
This can be misleading, since diagnostics about unused variables which
are warnings or errors, are shown like comments, since then only the
`DiagnosticUnnecessary` highlight group is used. Users do not see the
more eye-catching red/yellow highlight.
Solution: Instead of overriding the highlight group to
`DiagnosticUnnecessary` or `DiagnosticDeprecated`, set them in addition
to the normal severity-based highlights.
Problem:
diagnostic extmark used for positioning continues to exist after
deleting a range containing it, so it's possible to jump to a
next/previous diagnositc, which isn't visible in any way, including not
being shown via `open_float`.
Solution:
enable `invalidate` flag when setting an extmark to be able to filter
out diagnostics based on `invalid` flag when looking for next/previous
diagnostic to jump to.
Problem:
Diagnostic positions are not being updated after text changes, which
means `vim.diagnostic.open_float` and `vim.diagnostic.jump` will work
with outdated positions when text is changed until diagnostics are
updated again (if ever).
Solution:
Create extmarks in `vim.diagnostic.set` and use their positions for
`vim.diagnostic.open_float` and `next_diagnostic` (used by
`vim.diagnostic.jump`, `vim.diagnostic.get_next` and
`vim.diagnostic.get_prev`).
This commit allows users to jump to the location specified in a
diagnostic's `relatedInformation`, using `gf` from within the
`open_float` window. The cursor need only be on line that displays the
related info.
Problem:
virtual_text diagnostics are great when skimming a file, and
virtual_lines are great when "zooming in" on a particular problem.
Having both enabled results in duplicate diagnostics on-screen.
Solution:
This PR expands the behavior of `current_line` for virtual_text and
virtual_lines by making `virtual_text.current_line = false` distinct
from `nil`. If you set:
vim.diagnostic.config({
virtual_text = { current_line = false },
virtual_lines = { current_line = true },
})
With this configuration, virtual_text will be used to display
diagnostics until the cursor reaches the same line, at which point they
will be hidden and virtual_lines will take its place.
When multiple diagnostics appear on a single line, the virtual lines for
all diagnostics except the first were rendered with progressively fewer
columns.
Making this opt-out (on by default) was the wrong choice from the
beginning. It is too visually noisy to be enabled by default.
BREAKING CHANGE: Users must opt-in to the diagnostic virtual text
handler by adding
vim.diagnostic.config({ virtual_text = true })
to their config.
Problem:
Regression from de794f2d24: `vim.diagnostic.setqflist{open=true}` attempts to
open the location list instead of the diagnostics quickfix list if it didn't
exist before. This is because we are using `qf_id` to decide which to open, but
`qf_id=nil` when there is no existing diagnostics quickfix list with a given
title ("Diagnostics" by default).
Solution:
- Revert to using `loclist` to decide which to open.
- Add tests.
feat(diagnostics)!: sort underline with severity_sort
BREAKING CHANGE: underline will be applied with a higher value than `vim.hl.priorities.diagnostics`
Problem:
Tests have lots of exec_lua calls which input blocks of code
provided as unformatted strings.
Solution:
Teach exec_lua how to handle functions.
Deprecate vim.diagnostic.goto_prev() and vim.diagnostic.goto_next() in
favor of a unified vim.diagnostic.jump() interface.
We cannot name the function "goto()" because some of our tooling
(luacheck and stylua) fail to parse it, presumably because "goto" is a
keyword in newer versions of Lua.
vim.diagnostic.jump() also allows moving to a specific diagnostic and
moving by multiple diagnostics at a time (useful for creating mappings
that use v:count).
Follow-up to #28490
Problem:
The new behaviour of goto_next/prev() of navigating to the next highest
severity doesn't work well when diagnostic providers have different
interpretations of severities. E.g. the user may be blocked from
navigating to a useful LSP warning, due to some linter error.
Solution:
The behaviour of next highest severity is now a hidden option
`_highest = true`. We can revisit how to integrate this behaviour
during the 0.11 cycle.
Problem: when line is blank link then there will got an invalid column number in math.min compare.
Solution: make sure the min column number is 0 not an illegal number.
Specifically, functions that are run in the context of the test runner
are put in module `test/testutil.lua` while the functions that are run
in the context of the test session are put in
`test/functional/testnvim.lua`.
Closes https://github.com/neovim/neovim/issues/27004.
Problem:
We need to establish a pattern for `enable()`.
Solution:
- First `enable()` parameter is always `enable:boolean`.
- Update `vim.diagnostic.enable()`
- Update `vim.lsp.inlay_hint.enable()`.
- It was not released yet, so no deprecation is needed. But to help
HEAD users, it will show an informative error.
- vim.deprecate():
- Improve message when the "removal version" is a *current or older* version.
Problem:
vim.diagnostic.enable() does not match the signature of vim.lsp.inlay_hint.enable()
Solution:
- Change the signature so that the first 2 args are (bufnr, enable).
- Introduce a 3rd `opts` arg.
- Currently it only supports `opts.ns_id`.