Large files: bound highlighter cache, scan search once, row ranges as one edit

- HighlighterEngine caches spans only for rows near the requested row
  and trims the cache around it past 16k entries; per-row end states are
  still kept to resume lexing. Jumping to the end of a 78 MB C++ file
  cached every row's spans (854 MB RSS, now 197 MB), and the first edit
  near the top spent 270 ms freeing them.
- Plain and regex search read lines as views into the materialized
  buffer instead of Buffer::Rows(), which built (and kept) a string per
  line, O(lines x pieces) after edits. Plain search scans the whole text
  once and tracks rows by newline.
- transform_rows() applies a per-row rewrite of a row range as one edit
  (replace_rows_text). Indent/unindent region and visual-line insert,
  backspace, delete, newline and yank used one piece-table edit per row,
  each linear in the buffer: 1.1 s per keystroke with 5,000 rows selected
  in a 26 MB file (now 4 ms), and minutes to indent a large region.

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 b9dffd8645
commit bba7d34539
5 changed files with 244 additions and 168 deletions
+16 -1
View File
@@ -65,6 +65,7 @@ HighlighterEngine::GetLine(const Buffer &buf, int row, std::uint64_t buf_version
if (!stateful) {
hl_->HighlightLine(buf, row, result.spans);
cache_[row] = result;
trim_cache_locked(row);
return result;
}
@@ -86,14 +87,28 @@ HighlighterEngine::GetLine(const Buffer &buf, int row, std::uint64_t buf_version
states_[static_cast<std::size_t>(r)] = state;
if (r == row)
result = lh;
else
else if (row - r <= kCacheNear)
cache_.insert_or_assign(r, std::move(lh));
// Rows further above only needed their end state (kept in states_):
// caching their spans too held about 10x the file size in memory
// after jumping to the end of a large file.
}
cache_[row] = result;
trim_cache_locked(row);
return result;
}
void
HighlighterEngine::trim_cache_locked(const int row) const
{
if (cache_.size() <= kCacheMax)
return;
cache_.erase(cache_.begin(), cache_.lower_bound(row - kCacheNear));
cache_.erase(cache_.upper_bound(row + kCacheNear), cache_.end());
}
void
HighlighterEngine::InvalidateFrom(int row)
{