pmacs-gpu: optimistically clear a diagnostic when an edit touches it
The squiggle was holding a stale wave over corrected text until rust-analyzer re-analyzed and republished (0.5-2s for a fixed error) — the daemon's hold-while-stale carries the diagnostic through the stale window and the GPU translates it through local edits, so a fixed line kept its red squiggle until republish. (The 2px bar did this too; the bold wave just made it glaring.) translate_decorations now drops a diagnostic decoration the moment a local edit touches its range, instead of translating it: editing or fixing an error clears that squiggle immediately. Scoped to the touched diagnostic — an error elsewhere still translates and holds, preserving the no-blink benefit — and to diagnostic kinds only, so selection / current-line decorations are untouched. If the error survives the edit, the next republish re-adds it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4d71bf7455
commit
77f76f14b0
|
|
@ -4381,6 +4381,19 @@ fn translate_style_spans(spans: &mut Vec<StyleSpan>, edit: TextProjectionEdit) {
|
|||
fn translate_decorations(decorations: &mut Vec<Decoration>, edit: TextProjectionEdit) {
|
||||
let mut translated = Vec::with_capacity(decorations.len());
|
||||
for mut decoration in decorations.drain(..) {
|
||||
// Optimistic clear (diagnostics only): an edit that touches a
|
||||
// diagnostic's range invalidates it locally, so drop the
|
||||
// squiggle now instead of holding a stale wave over the text
|
||||
// you just changed until the LSP re-analyzes and republishes.
|
||||
// Scoped to the *touched* diagnostic — an error elsewhere
|
||||
// still translates and holds, so the no-blink benefit of the
|
||||
// producer's hold-while-stale survives. Non-diagnostic
|
||||
// decorations (selection / current-line) always translate.
|
||||
if decoration_kind_to_underline_color(decoration.kind).is_some()
|
||||
&& edit_touches_range(decoration.range, edit)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(range) = translate_byte_range(decoration.range, edit) {
|
||||
decoration.range = range;
|
||||
translated.push(decoration);
|
||||
|
|
@ -4389,6 +4402,14 @@ fn translate_decorations(decorations: &mut Vec<Decoration>, edit: TextProjection
|
|||
*decorations = translated;
|
||||
}
|
||||
|
||||
/// True when `edit` touches `range` in the pre-edit coordinate space.
|
||||
/// An insert (`old_end == start`) touches when its point lies within
|
||||
/// `[start, end]` (inclusive — typing at either edge of an error
|
||||
/// token counts); a delete/replace touches when its span overlaps.
|
||||
fn edit_touches_range(range: ByteRange, edit: TextProjectionEdit) -> bool {
|
||||
edit.start <= range.end && range.start <= edit.old_end
|
||||
}
|
||||
|
||||
fn translate_inline_adornments(adornments: &mut [InlineAdornment], edit: TextProjectionEdit) {
|
||||
for adornment in adornments {
|
||||
adornment.at = translate_byte_position(adornment.at, edit);
|
||||
|
|
@ -5496,6 +5517,45 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editing_a_diagnostic_clears_it_optimistically_but_holds_untouched_ones() {
|
||||
let diag = |start, end| Decoration {
|
||||
range: ByteRange { start, end },
|
||||
kind: DecorationKind::DiagnosticError,
|
||||
};
|
||||
// Insert the missing char right at the 1-byte widened anchor of
|
||||
// an end-of-line "expected COMMA": the squiggle clears now.
|
||||
let mut decos = vec![diag(11, 12), diag(40, 50)];
|
||||
translate_decorations(
|
||||
&mut decos,
|
||||
TextProjectionEdit {
|
||||
start: 11,
|
||||
old_end: 11,
|
||||
inserted_len: 1,
|
||||
},
|
||||
);
|
||||
// The touched diagnostic is gone; the far one is kept (shifted
|
||||
// right by the insert) — hold-while-stale still applies to it.
|
||||
assert_eq!(decos.len(), 1, "only the touched diagnostic clears");
|
||||
assert_eq!(decos[0].range, ByteRange { start: 41, end: 51 });
|
||||
|
||||
// A non-diagnostic decoration over the edited region is never
|
||||
// dropped — selection / current-line translate as before.
|
||||
let mut sel = vec![Decoration {
|
||||
range: ByteRange { start: 8, end: 14 },
|
||||
kind: DecorationKind::Selection,
|
||||
}];
|
||||
translate_decorations(
|
||||
&mut sel,
|
||||
TextProjectionEdit {
|
||||
start: 10,
|
||||
old_end: 10,
|
||||
inserted_len: 2,
|
||||
},
|
||||
);
|
||||
assert_eq!(sel.len(), 1, "selection survives an edit in its range");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_line_range_handles_empty_and_leading_newline() {
|
||||
assert_eq!(source_line_range("", 0), (0, 0));
|
||||
|
|
|
|||
Loading…
Reference in New Issue