neovim/neovim
Diagnostics
Purpose
vim.diagnostic is the unified display surface for compile errors, lint warnings, LSP diagnostics, and any other "something at file:line:col is wrong" stream. It owns the data model (a list of diagnostics keyed by namespace and buffer), the display logic (signs, virtual text, underline, virtual lines), and the navigation surface (vim.diagnostic.goto_next, the :Diagnostic Ex command).
Directory layout
runtime/lua/vim/
├── diagnostic.lua (~40k bytes) Top-level: vim.diagnostic.*
├── diagnostic/
│ ├── _meta.lua
│ └── ... Sub-helpers
runtime/lua/vim/lsp/
└── diagnostic.lua LSP → vim.diagnostic adapter
runtime/lua/vim/_meta/diagnostic.lua LuaCATS typesThe whole feature is Lua. The C side just exposes extmarks, autocmds, and namespaces.
Key abstractions
| Type / function | File | Description |
|---|---|---|
Diagnostic |
_meta/diagnostic.lua |
{ lnum, col, end_lnum, end_col, message, severity, source, code, data, namespace, bufnr }. |
| Severity | diagnostic.lua |
vim.diagnostic.severity.{ERROR, WARN, INFO, HINT}. |
| Namespace | API | An nvim_create_namespace integer id. The unique key for a producer. |
vim.diagnostic.set(ns, buf, diags) |
diagnostic.lua |
The single mutator. Replaces the namespace+buffer's diagnostics. |
vim.diagnostic.get(buf, opts) |
diagnostic.lua |
Read with filters. |
vim.diagnostic.config(config, ns) |
diagnostic.lua |
Display/handler configuration. |
How it works
graph LR
Producer[Producer<br/>LSP / linter / compiler] -->|set| Store[Per-namespace<br/>per-buffer table]
Store --> Render[Render handlers]
Render --> Signs[signs]
Render --> Virt[virtual text]
Render --> Under[underline]
Render --> VLines[virtual lines]
Store -->|get| Nav[Navigation:<br/>goto_next, jumplist]
Store -->|fire| Auto[DiagnosticChanged autocmd]Storage
vim.diagnostic.set(ns, buf, list) is the single write path. It:
- Replaces the previous list for
(ns, buf). - Calls each registered display handler to refresh visuals.
- Fires
DiagnosticChanged.
vim.diagnostic.get(buf, opts) reads. Filters: severity, namespace, lnum, severity range, etc.
Display handlers
The configurable display layer is a table of handlers, each implementing show(namespace, bufnr, diagnostics, opts) and hide(namespace, bufnr):
signs— sign in the sign column.virtual_text— at-end-of-line text.virtual_lines— full extra lines below the diagnostic.underline— extmark withhl_eol = falseandhl_group = DiagnosticUnderlineX.update_in_insert— refresh while typing (off by default for performance).
Each handler is configurable globally, per-namespace, or per-buffer:
vim.diagnostic.config({
virtual_text = { spacing = 4, prefix = '●' },
signs = false,
underline = true,
})Plugins can register custom handlers by adding to vim.diagnostic.handlers.
Severity model
Each diagnostic has a severity in {ERROR, WARN, INFO, HINT}. Highlight groups follow:
| Severity | Sign group | Virtual text group | Underline group |
|---|---|---|---|
| ERROR | DiagnosticSignError |
DiagnosticVirtualTextError |
DiagnosticUnderlineError |
| WARN | DiagnosticSignWarn |
DiagnosticVirtualTextWarn |
DiagnosticUnderlineWarn |
| INFO | DiagnosticSignInfo |
DiagnosticVirtualTextInfo |
DiagnosticUnderlineInfo |
| HINT | DiagnosticSignHint |
DiagnosticVirtualTextHint |
DiagnosticUnderlineHint |
Configurable filters (severity = { min = vim.diagnostic.severity.WARN }) are honored at both get and render time.
Navigation
vim.diagnostic.goto_next({ severity = ..., wrap = true, float = true })
vim.diagnostic.goto_prev(...)
vim.diagnostic.setqflist({ severity = ... }) -- populate quickfix
vim.diagnostic.setloclist(...) -- populate loclist
vim.diagnostic.open_float(...) -- show under cursorNavigation pushes onto the jumplist by default (jumplist = true) so <C-o> returns to the previous spot.
LSP integration
runtime/lua/vim/lsp/diagnostic.lua is the bridge from textDocument/publishDiagnostics to vim.diagnostic.set:
- Each client gets its own namespace (
vim.lsp.diagnostic.<client_id>). - On
publishDiagnostics, the LSPDiagnosticarray is mapped toDiagnosticrecords (URI → bufnr, range → lnum/col, optionaldata). vim.diagnostic.set(ns, buf, diags)is called.
The same path supports diagnostic pull (textDocument/diagnostic) for servers that prefer it.
Non-LSP producers
vim.diagnostic is producer-agnostic. Plugins commonly populate it from:
- A linter run (
vim.system+ parse stdout). - A compiler run (
:makeplus an errorformat parser). - A custom watchdog that observes file changes.
Each producer creates its own namespace via vim.api.nvim_create_namespace('mything') and calls vim.diagnostic.set(ns, buf, diags) whenever its results change.
Persistence
Diagnostics are not persisted across sessions. Re-attaching an LSP client or rerunning a linter on BufRead is the standard pattern.
Integration points
- LSP —
runtime/lua/vim/lsp/diagnostic.luais the canonical adapter. - Quickfix / loclist —
setqflistandsetloclistintegrate the lists. - Statusline —
vim.diagnostic.count(buf)returns counts by severity for in-statusline indicators. - Health —
:checkhealth diagnosticvalidates configuration. - Autocmds —
DiagnosticChangedfires after everysetcall. - Sign column — handled via extmarks, so the sign column is shared with the rest of the editor's sign-using features.
Entry points for modification
- Add a custom display style. Register a new handler in
vim.diagnostic.handlers.<name>withshowandhide. The renderer will call it. - Tweak rendering.
vim.diagnostic.config({...})covers most knobs. Per-namespace configuration isvim.diagnostic.config({...}, namespace). - Filter or transform.
severity = ...,filter = function(diag) ... end, customprefixandformatcallbacks. - Bridge a new producer. Mirror the structure of
runtime/lua/vim/lsp/diagnostic.lua— register a namespace, listen for the appropriate events, callvim.diagnostic.set.
Key source files
| File | Purpose |
|---|---|
runtime/lua/vim/diagnostic.lua |
Top-level public API |
runtime/lua/vim/_meta/diagnostic.lua |
Type stubs |
runtime/lua/vim/lsp/diagnostic.lua |
LSP → vim.diagnostic adapter |
runtime/doc/diagnostic.txt |
User docs (regenerated) |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.