You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When an on-save action rewrites the buffer (format-on-save, or trim-trailing-whitespace-on-save), the cursor is not mapped through the content change, so it lands on the wrong text — and non-deterministically.
Repro (from #2706): un-indented Rust file, cursor at end of line 3 (let b = 2;, Ln 3, Col 11), format-on-save prepends 4 spaces. Expected the cursor to track its text (end at Ln 3, Col 15, or at worst stay at Col 11); observed it landing inside the word let (Ln 3, Col 7), and a separate run from the same start landed on a different line (Ln 4, Col 3). Same input, different landing spot.
The other on-save defects (the false "File changed on disk" prompt from stale on-disk state, and the spurious "Reverted to saved file" that reset CRLF→LF) are handled separately in #2711 / the on-save-state PR — this issue is only the cursor mapping.
Proposed approach
Map the cursor through a diff of the pre-transform buffer vs. the formatter output, in the core on-save path (crates/fresh-editor/src/app/on_save_actions.rs / app/file_operations.rs) — not a plugin, since it repositions core cursor/selection state.
Reuse the existing internal diffcrates/fresh-editor/src/model/line_diff.rs (diff_lines, LCS-based). No git and no new dependency — it's a pure in-memory old-vs-new text diff.
Line diff first, then character-refine only the changed line(s); map the cursor's old offset through equal/changed regions, snapping to the end of the longest common prefix when it falls inside a changed hunk.
Also map any active selection endpoints the same way.
Performance / huge files
The current line_diff is an O(N·M) DP. To keep it cheap regardless of file size:
Trim the common prefix/suffix before diffing (formatter output ≈ input, so this collapses the problem to the changed span).
Line-level first; char-refine only changed lines.
A size/complexity cap with graceful fallback to a cheap line-anchored reposition (or clamp) so the editor never stalls on pathological input.
(Optional future: swap the DP for a Myers O(N·D) diff to remove the caveat entirely.)
Acceptance
After format-on-save / trim-on-save, the cursor stays anchored to the same logical text (the Format On Save #2706 repro lands at the end of let b = 2;), deterministically.
Selections are preserved analogously.
No measurable stall on large files (prefix/suffix trim + fallback).
Split off from #2706 as a tracked follow-up.
Problem
When an on-save action rewrites the buffer (format-on-save, or trim-trailing-whitespace-on-save), the cursor is not mapped through the content change, so it lands on the wrong text — and non-deterministically.
Repro (from #2706): un-indented Rust file, cursor at end of line 3 (
let b = 2;,Ln 3, Col 11), format-on-save prepends 4 spaces. Expected the cursor to track its text (end atLn 3, Col 15, or at worst stay at Col 11); observed it landing inside the wordlet(Ln 3, Col 7), and a separate run from the same start landed on a different line (Ln 4, Col 3). Same input, different landing spot.The other on-save defects (the false "File changed on disk" prompt from stale on-disk state, and the spurious "Reverted to saved file" that reset CRLF→LF) are handled separately in #2711 / the on-save-state PR — this issue is only the cursor mapping.
Proposed approach
Map the cursor through a diff of the pre-transform buffer vs. the formatter output, in the core on-save path (
crates/fresh-editor/src/app/on_save_actions.rs/app/file_operations.rs) — not a plugin, since it repositions core cursor/selection state.crates/fresh-editor/src/model/line_diff.rs(diff_lines, LCS-based). No git and no new dependency — it's a pure in-memory old-vs-new text diff.Performance / huge files
The current
line_diffis an O(N·M) DP. To keep it cheap regardless of file size:(Optional future: swap the DP for a Myers O(N·D) diff to remove the caveat entirely.)
Acceptance
let b = 2;), deterministically.