Skip to main content

lattice_protocol/
error_list.rs

1//! Error-list entries: the value type every error-list producer
2//! (compilation, LSP diagnostics, references, pickers) writes, tagged with
3//! which producer wrote it.
4//!
5//! CM.3a (2026-07-22): the error **entry** value type, lowered
6//! from `lattice-host` to the protocol floor so the below-host
7//! compilation parser (`lattice-compilation`) and the effect payload
8//! (`lattice_grammar::AppEffect::SetErrorList`) share ONE type.
9//!
10//! Only the entry + its severity live here — the *list* (`ErrorList`,
11//! with its navigation index) stays in `lattice-host` as core/host
12//! state alongside `position_history` (its consumer is generic host
13//! dispatch; see `docs/dev/architecture/compilation-mode.md` §3).
14//! `lattice-host` re-exports these two types so existing callers
15//! (`lattice_host::error_list::ErrorEntry`, the CM.2 tests) are
16//! unchanged.
17//!
18//! `Serialize` / `Deserialize` are added here (the host-local
19//! definitions did not carry them) because the type now rides inside
20//! the serde-derived `AppEffect` enum.
21
22use std::path::PathBuf;
23
24use serde::{Deserialize, Serialize};
25
26/// Severity of one error entry.
27///
28/// Deliberately independent of LSP's `DiagnosticSeverity`: the
29/// error list is core substrate and must not depend on the LSP
30/// crate. Producers map their own severity onto this small set.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32pub enum ErrorSeverity {
33    /// A hard error (rustc `error`, LSP `Error`).
34    Error,
35    /// A warning.
36    Warning,
37    /// Informational (LSP `Information`; also any compiler severity word the
38    /// compilation parser does not recognise, e.g. `help`).
39    Info,
40    /// A note or hint (compiler `note`, LSP `Hint`).
41    Note,
42}
43
44/// Which producer a run of error entries came from.
45///
46/// EP.1 (2026-08-10): the error list is shared by more than one
47/// producer, and a write replaces only its own source's slice. Without
48/// the tag a write is a clobber: the language server republishes on
49/// every edit-debounce, so a live diagnostic feed would overwrite a
50/// compile run's entries *while the user is walking them*.
51///
52/// Deliberately a small closed enum. A `Plugin(id)` variant lands with
53/// the plugin path — the boundary currently refuses `SetErrorList`
54/// outright, and tagging is the precondition for lifting that, not a
55/// reason to speculate about the shape now.
56///
57/// See `docs/dev/architecture/error-list.md` §3.1.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
59pub enum ErrorSource {
60    /// `lattice-compilation` — `:compile` / `:recompile` output parsed
61    /// into entries.
62    Compilation,
63    /// `lattice-lsp` — `publishDiagnostics`, coalesced.
64    Lsp,
65    /// `lattice-lsp` — reference sites from a `textDocument/references`
66    /// query. Not errors; the list is a navigable set of source
67    /// locations and fifteen call sites is exactly that. Opt-in via
68    /// `lsp.references-to-error-list` (default off) — see
69    /// `error-list.md` §3.2b.
70    References,
71    /// LR.5 — the user sent a picker's filtered results here with
72    /// `<C-q>` (telescope's `send_to_qflist`). Deliberately ONE slice
73    /// for every picker: the defining fact is "I sent these", not which
74    /// picker they came from, and a second `<C-q>` replaces the first
75    /// exactly as telescope's does.
76    Picker,
77}
78
79impl ErrorSource {
80    /// Fixed presentation order. Slices concatenate in this order and
81    /// each keeps its producer's own ordering — producer order carries
82    /// meaning (rustc emits the root cause ahead of the errors it
83    /// cascades into), so sorting the merged list would destroy it.
84    pub const PRESENTATION_ORDER: [ErrorSource; 4] = [
85        ErrorSource::Compilation,
86        ErrorSource::Lsp,
87        ErrorSource::References,
88        ErrorSource::Picker,
89    ];
90
91    /// Short label for echoes and filters.
92    pub fn label(self) -> &'static str {
93        match self {
94            ErrorSource::Compilation => "compile",
95            ErrorSource::Lsp => "lsp",
96            ErrorSource::References => "refs",
97            ErrorSource::Picker => "picker",
98        }
99    }
100}
101
102/// Whether a producer's write is a fresh run or a live refresh.
103///
104/// EP.2/EP.3 (2026-08-10): the two are indistinguishable by looking at
105/// the entries — a language-server republish and a re-run compile can
106/// carry byte-identical lists — so the producer declares which it is
107/// and the host picks `ErrorList::set` or `ErrorList::refresh`.
108///
109/// Getting this wrong is user-visible: a refresh that resets snaps
110/// someone walking the list back to entry 1 on every keystroke.
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
112pub enum ErrorWrite {
113    /// A fresh run — the user asked for new results and expects to
114    /// start at the top. Compilation always sends this.
115    NewRun,
116    /// A live update of an existing feed — keep the user where they
117    /// were. The language server always sends this, including its
118    /// first push (an empty list has nothing to anchor to, so it
119    /// degrades to index 0 anyway).
120    Refresh,
121}
122
123/// One navigable location on the error list. `line` / `col` are
124/// 0-based (the convention `Editor::jump_to_file_line_col` expects),
125/// matching LSP diagnostics.
126///
127/// # Examples
128///
129/// ```
130/// use lattice_protocol::error_list::{ErrorEntry, ErrorSeverity, ErrorSource};
131///
132/// // rustc's `src/main.rs:12:5` is 1-based; the entry stores it 0-based.
133/// let entry = ErrorEntry {
134///     path: "src/main.rs".into(),
135///     line: 11,
136///     col: 4,
137///     severity: ErrorSeverity::Error,
138///     message: "mismatched types".into(),
139/// };
140/// assert_eq!(entry.line + 1, 12);
141///
142/// // Slices from different producers concatenate in a fixed order.
143/// assert_eq!(ErrorSource::PRESENTATION_ORDER[0], ErrorSource::Compilation);
144/// assert_eq!(ErrorSource::Lsp.label(), "lsp");
145/// ```
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147pub struct ErrorEntry {
148    /// The file the entry points into, as the producer reported it (compiler
149    /// output is typically relative to the working directory; LSP paths are
150    /// absolute).
151    pub path: PathBuf,
152    /// 0-based line.
153    pub line: u32,
154    /// 0-based byte column.
155    pub col: u32,
156    /// How serious the entry is.
157    pub severity: ErrorSeverity,
158    /// The producer's message text.
159    pub message: String,
160}