1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! Per-document diagnostic queries (0009): gutter signs, the cursor
//! line's end-of-line note, modeline chips. Data arrives via LSP events
//! already resolved to byte-domain columns; these read it.
use strop_lsp::{ResolvedDiag, Severity};
use super::Editor;
pub struct DocumentDiagnostics {
pub revision: strop_core::id::BufferRevision,
pub items: Vec<ResolvedDiag>,
}
impl Editor {
/// (errors, warnings) on the buffer — the modeline's diag chips.
pub fn diag_counts(&self, idx: strop_core::id::DocumentId) -> (usize, usize) {
let mut e = 0;
let mut w = 0;
for d in self.diags_for(idx).into_iter().flatten() {
match d.severity {
Severity::Error => e += 1,
Severity::Warning => w += 1,
_ => {}
}
}
(e, w)
}
/// Cached diagnostics belong to a document incarnation and text revision,
/// not a pathname shared by several full/range/tail views.
pub(super) fn diags_for(&self, idx: strop_core::id::DocumentId) -> Option<&[ResolvedDiag]> {
let cached = self.diags.get(&idx)?;
(self.docs.get(idx)?.buf.revision() == cached.revision).then_some(cached.items.as_slice())
}
/// The worst diagnostic's (severity, message) on a 1-based line —
/// the cursor line's end-of-line note (0009 UX).
pub fn diag_message_at(
&self,
idx: strop_core::id::DocumentId,
line_1based: usize,
) -> Option<(Severity, &str)> {
self.diags_for(idx)?
.iter()
.filter(|d| d.line.get() + 1 == line_1based)
.min_by_key(|d| d.severity)
.map(|d| (d.severity, d.message.as_str()))
}
/// Diagnostic spans on a 1-based line as (col, end_col, severity)
/// — the undercurl layer (0009 UX). Same-line diags only; columns
/// are byte offsets into the line.
pub fn diag_ranges_at(
&self,
idx: strop_core::id::DocumentId,
line_1based: usize,
) -> Vec<(usize, usize, Severity)> {
self.diags_for(idx)
.map(|ds| {
ds.iter()
.filter(|d| d.line.get() + 1 == line_1based)
.map(|d| {
(
d.col.get(),
d.end_col.get().max(d.col.get() + 1),
d.severity,
)
})
.collect()
})
.unwrap_or_default()
}
/// Worst diagnostic severity for a 1-based line of buffer `idx`, if
/// any (0001 pillar 4: merges with the git gutter). Per-buffer, so
/// panes show their own diagnostics.
pub fn diag_severity_at(
&self,
idx: strop_core::id::DocumentId,
line_1based: usize,
) -> Option<Severity> {
let diags = self.diags_for(idx)?;
let mut best: Option<Severity> = None;
for d in diags {
if d.line.get() + 1 == line_1based {
best = Some(best.map_or(d.severity, |b: Severity| b.min(d.severity)));
}
}
best
}
}