Highlighting: invalidate from the edited row, not the whole buffer

Every edit bumped the buffer version (through SetDirty) and called
InvalidateFrom(0), so all cached spans and line states were discarded
and stateful highlighters re-lexed from row 0 to the viewport on each
keystroke.

Buffer mutators now report the row they changed (edited_at_), which
bumps the version and tells the engine to drop only rows from there
down; a row's highlighting depends only on rows above it. A version
change that arrives without that notice still clears everything, so a
missed path cannot show stale spans. The engine keeps states as a
contiguous vector and caches spans for every row it lexes. SetDirty no
longer touches the version: the edits themselves do, including undo
and redo back to the saved state (D9 still holds; its test now checks
the highlighting itself).

Typing near the end of a 100k-line C++ file: 46.3 ms -> 0.14 ms of
highlighting work per keystroke.

Audit finding P3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
Claude
2026-09-24 09:16:04 -07:00
committed by Kyle Isom
parent 4c42761fc0
commit f853fa60b1
5 changed files with 177 additions and 135 deletions
+14 -9
View File
@@ -390,21 +390,17 @@ public:
void SetDirty(bool d)
{
// The version tracks content, not the dirty flag: every text mutation
// bumps it (see edited_at_), including undo/redo back to the saved state.
dirty_ = d;
// Bump even when clearing: undo/redo back to the saved state changes the
// text and then calls SetDirty(false). Without a bump the highlighter
// keeps serving spans for the pre-undo text.
MarkContentChanged();
}
// Invalidate version-keyed caches (syntax highlighting) after a content change.
// Whole-buffer content change (load, reload, replace): bump the version
// and drop all cached highlighting.
void MarkContentChanged()
{
++version_;
if (highlighter_) {
highlighter_->InvalidateFrom(0);
}
edited_at_(0);
}
@@ -699,6 +695,15 @@ private:
// Syntax/highlighting state
std::uint64_t version_ = 0; // increment on edits
// Record a content change starting at `row`: bump the version and let the
// highlighter drop cached rows from `row` down (rows above are unaffected).
void edited_at_(int row)
{
++version_;
if (highlighter_)
highlighter_->OnEdit(row < 0 ? 0 : row, version_);
}
bool syntax_enabled_ = true;
bool syntax_user_override_ = false; // true once user explicitly set syntax state via a command
std::string filetype_;