diag: squiggle zero-width ranges at their anchor cell
User report: a removed comma produced the column-0 marker but no squiggle. rust-analyzer anchors 'expected COMMA' as a zero-width range one past the line's last character (verified: `rust-analyzer diagnostics` reports col 12 → col 12 on a 12-byte line), and the per-line clamp collapsed it into the empty-range skip. Zero-width ranges now underline the single cell at the anchor — one past EOL is a blank cell inside the window, and a squiggled space is how other editors surface exactly this error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
98fb6bb7a4
commit
acc91fafae
99
src/diag.rs
99
src/diag.rs
|
|
@ -536,14 +536,8 @@ impl View for DiagnosticView {
|
|||
} else {
|
||||
line_byte_len
|
||||
};
|
||||
if byte_end <= byte_start {
|
||||
// Empty range on a line --- no underline to
|
||||
// paint, but the column-0 marker recorded above
|
||||
// keeps the diagnostic visible.
|
||||
continue;
|
||||
}
|
||||
let (start_col, end_col) =
|
||||
byte_range_to_display_cols(line_bytes, byte_start as usize, byte_end as usize);
|
||||
underline_cols_for_line(line_bytes, byte_start, byte_end);
|
||||
if end_col <= start_col {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -595,6 +589,25 @@ fn line_at_offset(line_offsets: &[u32], offset: u32) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resolve the display-column span to underline for one line of a
|
||||
/// diagnostic. Zero-width ranges — the shape parsers use for
|
||||
/// "expected COMMA"-style errors anchored one past the last token
|
||||
/// (rust-analyzer reports a missing comma as `col 12 → col 12` at
|
||||
/// end of line, and the caller's `.min(line_byte_len)` clamps
|
||||
/// collapse any past-EOL anchor the same way) — get a single-cell
|
||||
/// span at the anchor: one past EOL is a blank cell inside the
|
||||
/// window, and a squiggled space is exactly how other editors
|
||||
/// surface it.
|
||||
fn underline_cols_for_line(line_bytes: &[u8], byte_start: u32, byte_end: u32) -> (u32, u32) {
|
||||
if byte_end <= byte_start {
|
||||
let (anchor, _) =
|
||||
byte_range_to_display_cols(line_bytes, byte_start as usize, byte_start as usize);
|
||||
(anchor, anchor + 1)
|
||||
} else {
|
||||
byte_range_to_display_cols(line_bytes, byte_start as usize, byte_end as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_range_to_display_cols(line_bytes: &[u8], byte_start: usize, byte_end: usize) -> (u32, u32) {
|
||||
let bs = byte_start.min(line_bytes.len());
|
||||
let be = byte_end.min(line_bytes.len());
|
||||
|
|
@ -983,14 +996,82 @@ mod tests {
|
|||
// glyph survives untouched.
|
||||
assert_eq!(grid.get(CellCoord::new(0, 0)).style.bg, Color::Indexed(1));
|
||||
assert_eq!(grid.get(CellCoord::new(0, 0)).glyph, Glyph::Char('h'));
|
||||
// Line 1: zero-width warning still produces a marker, and no
|
||||
// underline anywhere on the line (the range is empty).
|
||||
// Line 1: zero-width warning gets a marker and a single-cell
|
||||
// squiggle at its anchor column — and only there.
|
||||
assert_eq!(grid.get(CellCoord::new(1, 0)).style.bg, Color::Indexed(3));
|
||||
assert_eq!(
|
||||
grid.get(CellCoord::new(1, 2)).style.underline,
|
||||
UnderlineStyle::Curly
|
||||
);
|
||||
assert_eq!(
|
||||
grid.get(CellCoord::new(1, 3)).style.underline,
|
||||
UnderlineStyle::None
|
||||
);
|
||||
// Line 2: clean — no marker.
|
||||
assert_eq!(grid.get(CellCoord::new(2, 0)).style.bg, Color::Default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end_of_line_zero_width_error_squiggles_the_cell_past_eol() {
|
||||
// The missing-comma shape: rust-analyzer anchors "expected
|
||||
// COMMA" as a zero-width range one past the line's last
|
||||
// character (`b: 2` → col 12..12 on a 12-byte line). The
|
||||
// squiggle must land on the blank cell just past EOL, not
|
||||
// vanish in the empty-range clamp.
|
||||
use crate::cell::{Cell, CellSize, UnderlineStyle};
|
||||
|
||||
let store = make_shared_store();
|
||||
store.lock().expect("diag store").set(
|
||||
"file:///a",
|
||||
vec![Diagnostic {
|
||||
start_line: 0,
|
||||
start_col: 5, // one past "hello" (5 bytes)
|
||||
end_line: 0,
|
||||
end_col: 5,
|
||||
severity: DiagnosticSeverity::Error,
|
||||
message: "expected COMMA".to_owned(),
|
||||
source: None,
|
||||
code: None,
|
||||
}],
|
||||
);
|
||||
|
||||
let mut buf = Buffer::new(crate::buffer::BufferId::next(), "test.rs");
|
||||
buf.apply_edit(crate::buffer::EditOp::Insert {
|
||||
pos: 0,
|
||||
bytes: b"hello\nworld\n",
|
||||
})
|
||||
.expect("seed buffer");
|
||||
|
||||
let mut view = DiagnosticView::new("file:///a", store);
|
||||
let mut backing = vec![Cell::default(); 20];
|
||||
let mut grid = CellGrid {
|
||||
cells: &mut backing,
|
||||
stride: 10,
|
||||
size: CellSize::new(2, 10),
|
||||
};
|
||||
view.render(
|
||||
&buf,
|
||||
Viewport {
|
||||
buffer_start: 0,
|
||||
buffer_end: buf.len(),
|
||||
cell_origin: CellCoord::new(0, 0),
|
||||
cell_size: CellSize::new(2, 10),
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
||||
// Single-cell red squiggle on the blank cell past "hello".
|
||||
let cell = grid.get(CellCoord::new(0, 5));
|
||||
assert_eq!(cell.style.underline, UnderlineStyle::Curly);
|
||||
assert_eq!(cell.style.underline_color, Color::Indexed(1));
|
||||
// Nothing under the word itself or beyond the anchor.
|
||||
assert_eq!(
|
||||
grid.get(CellCoord::new(0, 4)).style.underline,
|
||||
UnderlineStyle::None
|
||||
);
|
||||
assert_eq!(
|
||||
grid.get(CellCoord::new(0, 6)).style.underline,
|
||||
UnderlineStyle::None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue