pub struct HighlightCache { /* private fields */ }Expand description
The document-owned incremental, virtualized highlight cache.
The correctness model is end-state convergence over DirtyRanges:
a line is re-tokenized only while its computed end state differs from the
stored one, so an edit repaints just the lines whose state actually
changed. It is driven lazily and budgeted by
HighlightCache::tokenize_until. Retention is what keeps memory
bounded — two facts make it work:
- Spans are draw output — nothing but visible rows reads them, so they
are retained only inside a dense window (the viewport ±
HIGHLIGHT_WINDOW_SLACK, set viaHighlightCache::set_window), together with per-line end states there (so a keystroke at the caret still converges per-line). - States exist to be resumed from — and any row’s state is
re-derivable by tokenizing forward, so outside the window only sparse
checkpoints survive: one per
HIGHLIGHT_CHECKPOINT_STRIDEtokenized rows plus each budget-stop resume point. A cold jump refills the window from the nearest checkpoint (≤ a stride of warm-up, budgeted; the rows render in the fallback style until filled).
Convergence verification is per-line inside the window and
checkpoint-grain outside (an out-of-window cascade may run up to one
stride past where a fully-dense cache would have stopped — bounded, and
such edits are rare: you edit what you see). Memory for a fully swept
document: O(window + lines / stride) instead of O(lines).
Implementations§
Source§impl HighlightCache
impl HighlightCache
Sourcepub fn new(syntax: SyntaxDef, theme: TokenTheme, n_lines: u32) -> Self
pub fn new(syntax: SyntaxDef, theme: TokenTheme, n_lines: u32) -> Self
A cache for an n_lines document over an injected grammar + theme:
every line invalid, nothing retained. The retention window defaults to
the document top (2 × HIGHLIGHT_WINDOW_SLACK rows) — right for a
freshly opened view; the app re-aims it per viewport report via
HighlightCache::set_window.
Sourcepub fn first_dirty(&self) -> Option<u32>
pub fn first_dirty(&self) -> Option<u32>
The first dirty row, if any — None means every line’s state has
converged (spans may still be evicted outside the window; see
HighlightCache::pending, which the sweep polls instead).
Sourcepub fn pending(&self) -> Option<u32>
pub fn pending(&self) -> Option<u32>
The next row a budgeted HighlightCache::tokenize_until call would
work on: the first dirty row, or — once states have converged — the
first window row whose spans await a refill (a window move over
already-swept rows). None = nothing to do (the idle-zero-work probe;
the app’s sweep keys its subscription off this).
Sourcepub fn line_spans(&self, row: u32) -> Option<&[HighlightSpan]>
pub fn line_spans(&self, row: u32) -> Option<&[HighlightSpan]>
The cached spans for a line, or None if the row is outside the
retention window or not yet tokenized/refilled (the caller renders
that line in the default style — never an error, never a stall).
Sourcepub fn line_count(&self) -> u32
pub fn line_count(&self) -> u32
How many lines the cache is sized for — must track the buffer’s line count (the commit path keeps them equal).
Sourcepub fn window_aim(&self) -> Range<u32>
pub fn window_aim(&self) -> Range<u32>
The last rows handed to HighlightCache::set_window (pre-padding) —
Document::set_syntax re-aims a replacement cache with this so a
grammar swap keeps retention at the viewport instead of silently
resetting to the document top.
Sourcepub fn set_window(&mut self, rows: Range<u32>)
pub fn set_window(&mut self, rows: Range<u32>)
Aim the retention window at rows (the viewport, in buffer rows) —
the cache retains rows ± HIGHLIGHT_WINDOW_SLACK and evicts
outside. Repositioning is O(window); rows entering the window refill
on the next HighlightCache::tokenize_until (see
HighlightCache::pending).
Sourcepub fn on_commit(&mut self, start: u32, old: u32, new: u32)
pub fn on_commit(&mut self, start: u32, old: u32, new: u32)
The commit hook: buffer lines [start, start+old) became new lines.
Splices the window (keeping the old end state of the last edited
line as the convergence probe in the new last slot), drops replaced
checkpoints and shifts the rest, and marks [start, start+new) dirty.
O(edit + window + #checkpoints); the cascade waits for tokenize_until.
Sourcepub fn on_commit_patch(&mut self, spans: &[(u32, u32, u32)])
pub fn on_commit_patch(&mut self, spans: &[(u32, u32, u32)])
The commit hook, given the per-edit pre-edit line spans (pre_start, old_lines, new_lines) (ascending, disjoint). Checkpoints, the dense
window, and the dirty runs all ride the per-edit spans — NOT the whole
transaction’s first-to-last covering range. A scattered multi-caret
transaction has a document-scale covering range but O(carets) tiny spans,
so this keeps the commit (and the sweep it schedules) O(carets + window +
checkpoints), and — critically — leaves the retention window aimed at the
viewport: only the actually-edited in-window rows are invalidated, so the
visible rows recolor on the next tokenize instead of going stale until a
scroll re-aims the window.
Sourcepub fn tokenize_until<S: AsRef<str>>(
&mut self,
target: u32,
max_lines: u32,
line: impl FnMut(u32) -> S,
) -> u32
pub fn tokenize_until<S: AsRef<str>>( &mut self, target: u32, max_lines: u32, line: impl FnMut(u32) -> S, ) -> u32
Tokenize toward target (inclusive), lazily and budgeted, and return
how many lines were tokenized (warm-up included) — the op count
the perf canaries assert on. Two phases share the max_lines budget:
- The dirty walk — dense convergence semantics: each first-dirty line tokenizes from its start state (stored, or warmed up from the nearest checkpoint); if its new end state differs from the stored one the cascade continues, otherwise it stops (convergence). Where no state is stored to compare against (outside the window, between checkpoints) the cascade keeps going until one is — at most a stride more than a fully-dense cache would have done.
- The window refill — rows inside the retention window whose spans were evicted (a window move over already-swept text) are re-derived from the nearest checkpoint. Only rows below the dirty frontier are fillable (states past dirt are unverified).
line(i) hands back line i’s text — an ACCESSOR, never an all-lines
Vec, so this never materializes the whole document. A budget stop
records a resume checkpoint so the next call continues without re-warming.
Sourcepub fn set_theme(&mut self, theme: TokenTheme)
pub fn set_theme(&mut self, theme: TokenTheme)
Swap the theme and invalidate the whole cache: a theme change is a full
invalidation because syntect resolves colors at tokenize time, so every
line’s styles change and must be re-tokenized. States and checkpoints
are dropped and every line marked dirty; the old window spans are kept
as a stale fallback so the view keeps showing the outgoing theme’s
colors (not a flash to default) until the frontier repaints them on the
next tokenize_until.
Sourcepub fn engine(&self) -> HighlightEngine
pub fn engine(&self) -> HighlightEngine
A cheap-to-clone, Send + Sync handle to this cache’s grammar + theme,
for off-thread bulk tokenization (the parallel/speculative sweep).
Shares the Arc-held grammar/theme — no SyntaxSet deep clone.