Open-Source Wikis

/

Neovim

/

Features

/

Diagnostics

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 types

The 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:

  1. Replaces the previous list for (ns, buf).
  2. Calls each registered display handler to refresh visuals.
  3. 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 with hl_eol = false and hl_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.

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 cursor

Navigation 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:

  1. Each client gets its own namespace (vim.lsp.diagnostic.<client_id>).
  2. On publishDiagnostics, the LSP Diagnostic array is mapped to Diagnostic records (URI → bufnr, range → lnum/col, optional data).
  3. 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 (:make plus 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

  • LSPruntime/lua/vim/lsp/diagnostic.lua is the canonical adapter.
  • Quickfix / loclistsetqflist and setloclist integrate the lists.
  • Statuslinevim.diagnostic.count(buf) returns counts by severity for in-statusline indicators.
  • Health:checkhealth diagnostic validates configuration.
  • AutocmdsDiagnosticChanged fires after every set call.
  • 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> with show and hide. The renderer will call it.
  • Tweak rendering. vim.diagnostic.config({...}) covers most knobs. Per-namespace configuration is vim.diagnostic.config({...}, namespace).
  • Filter or transform. severity = ..., filter = function(diag) ... end, custom prefix and format callbacks.
  • Bridge a new producer. Mirror the structure of runtime/lua/vim/lsp/diagnostic.lua — register a namespace, listen for the appropriate events, call vim.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.

Diagnostics – Neovim wiki | Factory