| Baselines, Myers line diffs, debouncing, undo/redo, and the edge cases behind SavePoint Diff's gutter markers.
I recently shipped SavePoint Diff, a VS Code extension that shows gutter markers for unsaved changes. Here's the design, in case you're thinking about building something similar.
The core idea: a baseline
Everything revolves around one string per file, the baseline, meaning the last saved content.
- File opens: capture its content as the baseline.
- Edit: diff baseline against the current buffer, then render the result as gutter decorations.
- Save: the baseline becomes the new content, and the markers clear.
The diff itself uses a small, well-established Myers line-diff library rather than anything custom.
Debouncing
Diffing on every keystroke is wasteful, so recalculation is delayed until typing pauses. The delay is configurable:
"savepointDiff.debounceMs": 150
Files above a size threshold skip live diffing entirely to protect performance (savepointDiff.maxFileSize, default 2,000,000 bytes).
Why undo/redo is free
Instead of tracking edits incrementally, the diff is always recomputed from baseline vs. current text. That means undo back to the saved state yields an empty diff, so markers vanish, and redo brings them back. There's no delta bookkeeping to corrupt.
Edge cases that bit me
The changelog lists a lot of these, but the interesting ones were:
- CRLF and lone CR line endings need normalizing, or the diff lies.
- Multi-change reverts: applying several edits must go in descending order, or earlier edits shift the ranges of later ones.
- Revert File / external rewrites: if the file stops being dirty without a save, its content now matches disk, so the baseline re-syncs automatically.
-
Invalid settings: a bad
maxFileSize(zero, negative, non-numeric) used to be able to silently disable diffing. It now falls back to the default. - Stale badges on hidden tabs needed explicit handling.
Savepoints are just a baseline move
The manual savepoint feature falls out of the design. It moves the baseline to the current in-memory content without writing to disk. No new machinery needed.
Takeaway
Recompute from two strings, resist clever incremental state, and spend your effort on the edge cases. The code is MIT licensed if you want to read it or contribute.
Top comments (0)