DEV Community

Cover image for How a Live Unsaved-Changes Diff Works in a VS Code Extension
John Wick
John Wick

Posted on

How a Live Unsaved-Changes Diff Works in a VS Code Extension

| 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.

  1. File opens: capture its content as the baseline.
  2. Edit: diff baseline against the current buffer, then render the result as gutter decorations.
  3. 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
Enter fullscreen mode Exit fullscreen mode

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.

GitHub ยท Marketplace

Top comments (0)