use core::ops::Range;
use std::io::Cursor;
use std::sync::Arc;
use crate::buffer::Snapshot;
use crate::sum_tree::{Dimension, Item, Summary, SumTree};
use syntect::highlighting::{
FontStyle, HighlightState, Highlighter as SyntectHighlighter, RangedHighlightIterator, Style,
Theme, ThemeSet,
};
use syntect::parsing::{ParseState, ScopeStack, SyntaxDefinition, SyntaxReference, SyntaxSet, SyntaxSetBuilder};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Rgba {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct SpanStyle {
pub fg: Rgba,
pub bold: bool,
pub italic: bool,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct HighlightSpan {
pub range: Range<u32>,
pub style: SpanStyle,
}
#[derive(Debug, thiserror::Error)]
#[error("invalid .sublime-syntax grammar: {0}")]
pub struct SyntaxError(String);
#[derive(Debug, thiserror::Error)]
#[error("invalid .tmTheme: {0}")]
pub struct ThemeError(String);
pub struct SyntaxDef {
set: SyntaxSet,
name: String,
}
impl SyntaxDef {
pub fn from_sublime_syntax(s: &str) -> Result<Self, SyntaxError> {
let syntax =
SyntaxDefinition::load_from_str(s, false, None).map_err(|e| SyntaxError(e.to_string()))?;
let name = syntax.name.clone();
let mut builder = SyntaxSetBuilder::new();
builder.add(syntax);
Ok(Self { set: builder.build(), name })
}
fn reference(&self) -> &SyntaxReference {
self.set
.find_syntax_by_name(&self.name)
.or_else(|| self.set.syntaxes().first())
.expect("the injected grammar is in its own set")
}
}
fn span_from(style: Style, range: Range<usize>) -> HighlightSpan {
HighlightSpan {
range: range.start as u32..range.end as u32,
style: SpanStyle {
fg: Rgba {
r: style.foreground.r,
g: style.foreground.g,
b: style.foreground.b,
a: style.foreground.a,
},
bold: style.font_style.contains(FontStyle::BOLD),
italic: style.font_style.contains(FontStyle::ITALIC),
},
}
}
#[derive(Clone)]
pub struct TokenTheme(Theme);
impl TokenTheme {
pub fn from_tm_theme(s: &str) -> Result<Self, ThemeError> {
ThemeSet::load_from_reader(&mut Cursor::new(s))
.map(TokenTheme)
.map_err(|e| ThemeError(e.to_string()))
}
}
pub struct Highlighter {
syntax: SyntaxDef,
theme: TokenTheme,
}
impl Highlighter {
#[must_use]
pub fn new(syntax: SyntaxDef, theme: TokenTheme) -> Self {
Self { syntax, theme }
}
#[must_use]
pub fn highlight(&self, text: &str) -> Vec<Vec<HighlightSpan>> {
let syntect = SyntectHighlighter::new(&self.theme.0);
let mut state = LineState {
parse: ParseState::new(self.syntax.reference()),
highlight: HighlightState::new(&syntect, ScopeStack::new()),
};
text.split('\n')
.map(|line| {
let (spans, next) = tokenize_line(&syntect, &self.syntax.set, &state, line);
state = next;
spans
})
.collect()
}
}
#[derive(Clone, PartialEq)]
struct LineState {
parse: ParseState,
highlight: HighlightState,
}
fn tokenize_line(
syntect: &SyntectHighlighter<'_>,
set: &SyntaxSet,
start: &LineState,
line: &str,
) -> (Vec<HighlightSpan>, LineState) {
let mut end = start.clone();
let ops = end.parse.parse_line(line, set).unwrap_or_default();
let spans = RangedHighlightIterator::new(&mut end.highlight, &ops, line, syntect)
.map(|(style, _text, range)| span_from(style, range))
.collect();
(spans, end)
}
pub const HIGHLIGHT_MAX_LINES_PER_CALL: u32 = 256;
pub const HIGHLIGHT_CHECKPOINT_STRIDE: u32 = 256;
pub const HIGHLIGHT_WINDOW_SLACK: u32 = 512;
pub const HIGHLIGHT_MAX_WINDOW_ROWS: u32 = 4096;
#[must_use]
pub fn padded_highlight_window(viewport: Range<u32>, n_lines: u32) -> Range<u32> {
let start = viewport.start.saturating_sub(HIGHLIGHT_WINDOW_SLACK);
let end = viewport
.end
.saturating_add(HIGHLIGHT_WINDOW_SLACK)
.min(start.saturating_add(HIGHLIGHT_MAX_WINDOW_ROWS))
.min(n_lines)
.max(start);
start..end
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct DirtyRanges(Vec<Range<u32>>);
impl DirtyRanges {
fn all(n: u32) -> Self {
Self((n > 0).then_some(0..n).into_iter().collect())
}
fn first(&self) -> Option<u32> {
self.0.first().map(|r| r.start)
}
fn remove_first(&mut self, row: u32) {
debug_assert_eq!(self.first(), Some(row), "consumption is front-only");
let r = &mut self.0[0];
r.start += 1;
if r.start >= r.end {
self.0.remove(0);
}
}
fn insert(&mut self, row: u32) {
let i = self.0.partition_point(|r| r.end < row);
if i < self.0.len() {
let r = &mut self.0[i];
if r.start <= row && row < r.end {
return; }
if r.end == row {
r.end += 1; if i + 1 < self.0.len() && self.0[i].end == self.0[i + 1].start {
let next_end = self.0[i + 1].end;
self.0[i].end = next_end; self.0.remove(i + 1);
}
return;
}
if r.start == row + 1 {
r.start = row; return;
}
}
self.0.insert(i, row..row + 1);
}
fn apply_splices(&mut self, spans: &[(u32, u32, u32)]) {
if spans.is_empty() {
return;
}
let mut pref: Vec<i64> = Vec::with_capacity(spans.len() + 1);
pref.push(0);
for &(_, o, n) in spans {
pref.push(pref[pref.len() - 1] + i64::from(n) - i64::from(o));
}
let shift_at = |pre: u32| -> i64 {
let i = spans.partition_point(|s| s.0 + s.1 <= pre);
pref[i]
};
let old_runs = std::mem::take(&mut self.0);
let mut out: Vec<Range<u32>> = Vec::with_capacity(old_runs.len() + spans.len());
for r in &old_runs {
let a = (i64::from(r.start) + shift_at(r.start)) as u32;
let b = (i64::from(r.end) + shift_at(r.end)) as u32;
if a < b {
out.push(a..b);
}
}
for (j, &(ps, _o, n)) in spans.iter().enumerate() {
if n > 0 {
let post_start = (i64::from(ps) + pref[j]) as u32;
out.push(post_start..post_start + n);
}
}
out.sort_unstable_by_key(|r| r.start);
let mut merged: Vec<Range<u32>> = Vec::with_capacity(out.len());
for r in out {
match merged.last_mut() {
Some(last) if last.end >= r.start => last.end = last.end.max(r.end),
_ => merged.push(r),
}
}
self.0 = merged;
}
#[cfg(test)]
fn total_rows(&self) -> u32 {
self.0.iter().map(|r| r.end - r.start).sum()
}
fn contains(&self, row: u32) -> bool {
let i = self.0.partition_point(|r| r.end <= row);
i < self.0.len() && self.0[i].start <= row
}
fn clear_range(&mut self, range: Range<u32>) {
if range.start >= range.end {
return;
}
let old = std::mem::take(&mut self.0);
let mut out: Vec<Range<u32>> = Vec::with_capacity(old.len() + 1);
for r in old {
let lo = r.start..r.end.min(range.start); let hi = r.start.max(range.end)..r.end; if lo.start < lo.end {
out.push(lo);
}
if hi.start < hi.end {
out.push(hi);
}
}
self.0 = out;
}
#[cfg(test)]
fn runs(&self) -> usize {
self.0.len()
}
}
pub struct HighlightCache {
syntax: Arc<SyntaxDef>,
theme: Arc<TokenTheme>,
ret: Retention,
}
struct Retention {
n_lines: u32,
invalid: DirtyRanges,
aim: Range<u32>,
win: Range<u32>,
win_spans: Vec<Option<Vec<HighlightSpan>>>,
win_states: Vec<Option<Box<LineState>>>,
checkpoints: Checkpoints,
}
impl core::fmt::Debug for HighlightCache {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HighlightCache")
.field("lines", &self.ret.n_lines)
.field("window", &self.ret.win)
.field("checkpoints", &self.ret.checkpoints.len())
.field("dirty", &self.ret.invalid)
.finish_non_exhaustive()
}
}
struct CkptItem {
row_gap: u32,
state: Box<LineState>,
}
impl Clone for CkptItem {
fn clone(&self) -> Self {
Self { row_gap: self.row_gap, state: self.state.clone() }
}
}
impl core::fmt::Debug for CkptItem {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CkptItem").field("row_gap", &self.row_gap).finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, Default)]
struct CkptSummary {
rows: u32,
count: u32,
}
impl Summary for CkptSummary {
fn add_summary(&mut self, o: &Self) {
self.rows += o.rows;
self.count += o.count;
}
}
impl Item for CkptItem {
type Summary = CkptSummary;
fn summary(&self) -> CkptSummary {
CkptSummary { rows: self.row_gap, count: 1 }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct RowDim(u32);
impl Dimension<CkptSummary> for RowDim {
fn add_summary(&mut self, s: &CkptSummary) {
self.0 += s.rows;
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct CkptIx(u32);
impl Dimension<CkptSummary> for CkptIx {
fn add_summary(&mut self, s: &CkptSummary) {
self.0 += s.count;
}
}
fn reanchor(
suffix: &SumTree<CkptItem>,
k_hi: u32,
orig: &SumTree<CkptItem>,
delta: i64,
prev_row: u32,
) -> SumTree<CkptItem> {
if suffix.is_empty() {
return suffix.clone();
}
let (item, _c, RowDim(r)) = orig
.seek::<CkptIx, RowDim>(&CkptIx(k_hi))
.expect("suffix non-empty ⇒ a k_hi-th item exists");
let first_old_row = r + item.row_gap;
let new_gap = (i64::from(first_old_row) + delta - i64::from(prev_row)) as u32;
let fixed = CkptItem { row_gap: new_gap, state: item.state.clone() };
suffix.replace(CkptIx(0)..CkptIx(1), std::iter::once(fixed))
}
struct Checkpoints {
tree: SumTree<CkptItem>,
}
impl Checkpoints {
fn new() -> Self {
Self { tree: SumTree::new() }
}
fn len(&self) -> usize {
self.tree.summary().count as usize
}
#[cfg(test)]
fn is_empty(&self) -> bool {
self.tree.is_empty()
}
fn clear(&mut self) {
self.tree = SumTree::new();
}
fn state_at(&self, row: u32) -> Option<&LineState> {
let n_le = self.tree.summary_before(&RowDim(row.saturating_add(1))).count; let n_lt = self.tree.summary_before(&RowDim(row)).count; if n_le == n_lt {
return None; }
let (item, _ix, RowDim(before)) = self.tree.seek::<CkptIx, RowDim>(&CkptIx(n_lt))?;
debug_assert_eq!(before + item.row_gap, row, "state_at must land exactly on `row`");
Some(&*item.state)
}
fn floor(&self, row: u32) -> Option<(u32, &LineState)> {
let n_le = self.tree.summary_before(&RowDim(row.saturating_add(1))).count; let idx = n_le.checked_sub(1)?; let (item, _ix, RowDim(before)) = self.tree.seek::<CkptIx, RowDim>(&CkptIx(idx))?;
Some((before + item.row_gap, &*item.state))
}
fn replace_state_at_index(&mut self, i: u32, state: LineState) {
let gap = {
let (item, _ix, _rd) =
self.tree.seek::<CkptIx, RowDim>(&CkptIx(i)).expect("index in range");
item.row_gap
};
self.tree = self
.tree
.replace(CkptIx(i)..CkptIx(i + 1), std::iter::once(CkptItem { row_gap: gap, state: Box::new(state) }));
}
fn insert_at(&mut self, i: u32, row: u32, state: LineState) {
let (left, right) = self.tree.split_at(&CkptIx(i));
let pred = left.extent::<RowDim>().0; let mid = SumTree::from_items(std::iter::once(CkptItem {
row_gap: row - pred,
state: Box::new(state),
}));
let right_fixed = reanchor(&right, i, &self.tree, 0, row);
self.tree = left.append(&mid).append(&right_fixed);
}
fn upsert(&mut self, row: u32, state: LineState) {
let n_le = self.tree.summary_before(&RowDim(row.saturating_add(1))).count; let n_lt = self.tree.summary_before(&RowDim(row)).count; if n_le > n_lt {
self.replace_state_at_index(n_lt, state); } else {
self.insert_at(n_lt, row, state);
}
}
fn refresh_if_present(&mut self, row: u32, state: LineState) {
let n_le = self.tree.summary_before(&RowDim(row.saturating_add(1))).count;
let n_lt = self.tree.summary_before(&RowDim(row)).count;
if n_le > n_lt {
self.replace_state_at_index(n_lt, state);
}
}
fn set_stride(&mut self, row: u32, state: LineState) {
self.upsert(row, state);
self.prune_behind(row);
}
fn prune_behind(&mut self, row: u32) {
let floor = row.saturating_sub(HIGHLIGHT_CHECKPOINT_STRIDE);
let k_lo = self.tree.summary_before(&RowDim(floor.saturating_add(1))).count; let k_hi = self.tree.summary_before(&RowDim(row)).count; if k_hi <= k_lo {
return; }
let (left, rest) = self.tree.split_at(&CkptIx(k_lo));
let (_dropped, right) = rest.split_at(&CkptIx(k_hi - k_lo));
let right_fixed = reanchor(&right, k_hi, &self.tree, 0, left.extent::<RowDim>().0);
self.tree = left.append(&right_fixed);
}
fn shift(&mut self, spans: &[(u32, u32, u32)]) {
if spans.is_empty() {
return;
}
if spans.len() == 1 {
self.shift_single(spans[0]);
} else {
self.shift_walk(spans);
}
}
fn shift_single(&mut self, (s, o, n): (u32, u32, u32)) {
let d = i64::from(n) - i64::from(o);
let k_lo = self.tree.summary_before(&RowDim(s)).count; let k_hi = self.tree.summary_before(&RowDim(s + o)).count; let dropped = k_hi - k_lo;
crate::perf::charge(u64::from(dropped) + 1);
if dropped == 0 && d == 0 {
return; }
let (left, rest) = self.tree.split_at(&CkptIx(k_lo));
let (_dropped, suffix) = rest.split_at(&CkptIx(k_hi - k_lo));
let pred = left.extent::<RowDim>().0;
let suffix_fixed = reanchor(&suffix, k_hi, &self.tree, d, pred);
self.tree = left.append(&suffix_fixed);
}
fn shift_walk(&mut self, spans: &[(u32, u32, u32)]) {
let old = self.tree.items();
let mut out: Vec<CkptItem> = Vec::with_capacity(old.len());
let mut si = 0usize;
let mut acc = 0i64;
let mut abs = 0u32; let mut prev_out = 0u32; for item in old {
crate::perf::charge(1); abs += item.row_gap;
while si < spans.len() && spans[si].0 + spans[si].1 <= abs {
acc += i64::from(spans[si].2) - i64::from(spans[si].1);
si += 1;
}
if si < spans.len() && abs >= spans[si].0 {
continue; }
let new_abs = (i64::from(abs) + acc) as u32;
out.push(CkptItem { row_gap: new_abs - prev_out, state: item.state });
prev_out = new_abs;
}
self.tree = SumTree::from_items(out);
}
#[cfg(test)]
fn from_rows(rows: &[u32], state: &LineState) -> Self {
let mut prev = 0u32;
let items: Vec<CkptItem> = rows
.iter()
.map(|&r| {
let gap = r - prev;
prev = r;
CkptItem { row_gap: gap, state: Box::new(state.clone()) }
})
.collect();
Self { tree: SumTree::from_items(items) }
}
#[cfg(test)]
fn rows(&self) -> Vec<u32> {
let mut abs = 0u32;
self.tree
.items()
.iter()
.map(|it| {
abs += it.row_gap;
abs
})
.collect()
}
}
enum StartState {
Ready(LineState),
Fresh,
WarmupStored(u32, LineState),
WarmupFresh,
}
impl HighlightCache {
#[must_use]
pub fn new(syntax: SyntaxDef, theme: TokenTheme, n_lines: u32) -> Self {
let win = 0..(2 * HIGHLIGHT_WINDOW_SLACK).min(n_lines);
let len = win.len();
Self {
syntax: Arc::new(syntax),
theme: Arc::new(theme),
ret: Retention {
n_lines,
invalid: DirtyRanges::all(n_lines),
aim: win.clone(),
win,
win_spans: vec![None; len],
win_states: vec![None; len],
checkpoints: Checkpoints::new(),
},
}
}
#[must_use]
pub fn first_dirty(&self) -> Option<u32> {
self.ret.invalid.first()
}
#[must_use]
pub fn pending(&self) -> Option<u32> {
let dirt = self.ret.invalid.first();
let gap = self.ret.first_window_gap(dirt.unwrap_or(u32::MAX));
match (dirt, gap) {
(Some(d), Some(g)) => Some(d.min(g)),
(d, g) => d.or(g),
}
}
#[must_use]
pub fn line_spans(&self, row: u32) -> Option<&[HighlightSpan]> {
self.ret.win_spans[self.ret.win_idx(row)?].as_deref()
}
#[must_use]
pub fn line_count(&self) -> u32 {
self.ret.n_lines
}
#[must_use]
pub fn window_aim(&self) -> Range<u32> {
self.ret.aim.clone()
}
pub fn set_window(&mut self, rows: Range<u32>) {
let r = &mut self.ret;
r.aim = rows.clone();
let win = padded_highlight_window(rows, r.n_lines);
let (start, end) = (win.start, win.end);
if (start..end) == r.win {
return;
}
let len = (end - start) as usize;
let mut spans = vec![None; len];
let mut states = vec![None; len];
for row in start.max(r.win.start)..end.min(r.win.end) {
let old_i = (row - r.win.start) as usize;
let new_i = (row - start) as usize;
spans[new_i] = r.win_spans[old_i].take();
states[new_i] = r.win_states[old_i].take();
}
r.win = start..end;
r.win_spans = spans;
r.win_states = states;
}
pub fn on_commit(&mut self, start: u32, old: u32, new: u32) {
self.on_commit_patch(&[(start, old, new)]);
}
pub fn on_commit_patch(&mut self, spans: &[(u32, u32, u32)]) {
if spans.is_empty() {
return;
}
let r = &mut self.ret;
let delta: i64 = spans.iter().map(|&(_, o, n)| i64::from(n) - i64::from(o)).sum();
r.checkpoints.shift(spans);
r.shift_window(spans);
r.invalid.apply_splices(spans);
r.n_lines = (r.n_lines as i64 + delta) as u32;
}
pub fn tokenize_until<S: AsRef<str>>(
&mut self,
target: u32,
max_lines: u32,
mut line: impl FnMut(u32) -> S,
) -> u32 {
let syntect = SyntectHighlighter::new(&self.theme.0);
let syntax = &*self.syntax;
let fresh = || LineState {
parse: ParseState::new(syntax.reference()),
highlight: HighlightState::new(&syntect, ScopeStack::new()),
};
let ret = &mut self.ret;
let mut work = 0u32;
'walk: while work < max_lines {
let Some(first) = ret.invalid.first() else { break };
if first > target {
break;
}
let started = match ret.start_state_for(first) {
StartState::Ready(s) => Some(s),
StartState::Fresh => Some(fresh()),
StartState::WarmupStored(r, s) => ret
.warm_up(Some(r), first, s, &mut work, max_lines, &syntect, &syntax.set, &mut line),
StartState::WarmupFresh => ret
.warm_up(None, first, fresh(), &mut work, max_lines, &syntect, &syntax.set, &mut line),
};
let Some(mut state) = started else { break 'walk }; let mut row = first;
loop {
if work >= max_lines {
ret.keep_resume_checkpoint(row.checked_sub(1), &state);
break 'walk;
}
let (spans, end) = tokenize_line(&syntect, &syntax.set, &state, line(row).as_ref());
let converged = ret.stored_state_after(row).is_some_and(|old| *old == end);
ret.retain(row, spans, &end);
ret.invalid.remove_first(row);
work += 1;
state = end;
if converged || row + 1 >= ret.n_lines {
break;
}
ret.invalid.insert(row + 1);
if row + 1 > target {
break; }
row += 1;
}
}
'fill: while work < max_lines {
let dirt = ret.invalid.first().unwrap_or(u32::MAX);
let Some(gap) = ret.first_window_gap(dirt) else { break };
let started = match ret.start_state_for(gap) {
StartState::Ready(s) => Some(s),
StartState::Fresh => Some(fresh()),
StartState::WarmupStored(r, s) => ret
.warm_up(Some(r), gap, s, &mut work, max_lines, &syntect, &syntax.set, &mut line),
StartState::WarmupFresh => ret
.warm_up(None, gap, fresh(), &mut work, max_lines, &syntect, &syntax.set, &mut line),
};
let Some(mut state) = started else { break 'fill };
let end_row = ret.win.end.min(ret.n_lines).min(dirt);
let mut row = gap;
while row < end_row {
if work >= max_lines {
ret.keep_resume_checkpoint(row.checked_sub(1), &state);
break 'fill;
}
if ret.win_spans[(row - ret.win.start) as usize].is_some() {
if let Some(s) = ret.stored_state_after(row) {
state = s.clone(); row += 1;
continue;
}
}
let (spans, end) = tokenize_line(&syntect, &syntax.set, &state, line(row).as_ref());
ret.retain(row, spans, &end);
work += 1;
state = end;
row += 1;
}
}
work
}
pub fn set_theme(&mut self, theme: TokenTheme) {
self.theme = Arc::new(theme);
for s in &mut self.ret.win_states {
*s = None;
}
self.ret.checkpoints.clear();
self.ret.invalid = DirtyRanges::all(self.ret.n_lines);
}
#[must_use]
pub fn engine(&self) -> HighlightEngine {
HighlightEngine { syntax: self.syntax.clone(), theme: self.theme.clone() }
}
pub(crate) fn absorb(&mut self, seg: SegmentTokens, verified: bool) {
let r = &mut self.ret;
let SegmentTokens { rows, checkpoints, win, win_spans, win_states, .. } = seg;
if verified {
for (row, state) in checkpoints {
r.checkpoints.set_stride(row, *state);
}
let cover = rows.start.max(r.win.start)..rows.end.min(r.win.end);
for row in cover {
if !win.contains(&row) {
let i = (row - r.win.start) as usize;
r.win_spans[i] = None;
r.win_states[i] = None;
}
}
for ((row, spans), state) in win.zip(win_spans).zip(win_states) {
if let Some(i) = r.win_idx(row) {
r.win_spans[i] = Some(spans);
r.win_states[i] = Some(Box::new(state));
}
}
r.invalid.clear_range(rows);
} else {
for ((row, spans), state) in win.zip(win_spans).zip(win_states) {
if r.invalid.contains(row) {
if let Some(i) = r.win_idx(row) {
r.win_spans[i] = Some(spans);
r.win_states[i] = Some(Box::new(state));
}
}
}
}
}
#[cfg(test)]
fn retained_span_rows(&self) -> usize {
self.ret.win_spans.iter().filter(|s| s.is_some()).count()
}
#[cfg(test)]
fn window_len(&self) -> usize {
debug_assert_eq!(self.ret.win_spans.len(), self.ret.win_states.len());
self.ret.win_spans.len()
}
#[cfg(test)]
fn dirty_row_count(&self) -> u32 {
self.ret.invalid.total_rows()
}
#[cfg(test)]
fn retained_state_rows(&self) -> usize {
self.ret.win_states.iter().filter(|s| s.is_some()).count() + self.ret.checkpoints.len()
}
}
#[derive(Clone)]
pub struct HighlightEngine {
syntax: Arc<SyntaxDef>,
theme: Arc<TokenTheme>,
}
impl HighlightEngine {
#[must_use]
pub fn new(syntax: SyntaxDef, theme: TokenTheme) -> Self {
Self { syntax: Arc::new(syntax), theme: Arc::new(theme) }
}
#[must_use]
pub fn fresh_boundary(&self) -> SegmentBoundary {
SegmentBoundary(Box::new(self.fresh_state()))
}
fn fresh_state(&self) -> LineState {
let syntect = SyntectHighlighter::new(&self.theme.0);
LineState {
parse: ParseState::new(self.syntax.reference()),
highlight: HighlightState::new(&syntect, ScopeStack::new()),
}
}
}
#[derive(Clone, PartialEq)]
pub struct SegmentBoundary(Box<LineState>);
pub enum SegmentStart {
Fresh,
After(SegmentBoundary),
}
pub struct SegmentTokens {
rows: Range<u32>,
started_fresh: bool,
checkpoints: Vec<(u32, Box<LineState>)>,
end: SegmentBoundary,
win: Range<u32>,
win_spans: Vec<Vec<HighlightSpan>>,
win_states: Vec<LineState>,
tokenized: u32,
}
impl SegmentTokens {
#[must_use]
pub fn rows(&self) -> Range<u32> {
self.rows.clone()
}
#[must_use]
pub fn tokenized_rows(&self) -> u32 {
self.tokenized
}
#[must_use]
pub fn end_boundary(&self) -> &SegmentBoundary {
&self.end
}
#[must_use]
pub fn started_fresh(&self) -> bool {
self.started_fresh
}
fn checkpoint_at(&self, row: u32) -> Option<&LineState> {
self.checkpoints.binary_search_by_key(&row, |(r, _)| *r).ok().map(|i| &*self.checkpoints[i].1)
}
}
#[must_use]
pub fn tokenize_segment(
engine: &HighlightEngine,
snapshot: &Snapshot,
rows: Range<u32>,
start: SegmentStart,
spans_for: Option<Range<u32>>,
converge_against: Option<&SegmentTokens>,
) -> SegmentTokens {
let syntect = SyntectHighlighter::new(&engine.theme.0);
let set = &engine.syntax.set;
let started_fresh = matches!(start, SegmentStart::Fresh);
let mut state = match start {
SegmentStart::Fresh => engine.fresh_state(),
SegmentStart::After(b) => *b.0,
};
let n = snapshot.line_count();
let lo = rows.start.min(n);
let hi = rows.end.min(n);
let win = match converge_against {
Some(old) => old.win.clone(),
None => spans_for
.map(|w| w.start.max(lo)..w.end.min(hi))
.filter(|w| w.start < w.end)
.unwrap_or(lo..lo),
};
let mut checkpoints: Vec<(u32, Box<LineState>)> = Vec::new();
let mut win_spans: Vec<Vec<HighlightSpan>> = Vec::with_capacity(win.len());
let mut win_states: Vec<LineState> = Vec::with_capacity(win.len());
let mut row = lo;
while row < hi {
let line = snapshot.line(row);
let (spans, end) = tokenize_line(&syntect, set, &state, line.as_ref());
if win.contains(&row) {
win_spans.push(spans);
win_states.push(end.clone());
}
state = end;
if (row + 1).is_multiple_of(HIGHLIGHT_CHECKPOINT_STRIDE) {
checkpoints.push((row, Box::new(state.clone())));
if let Some(old) = converge_against {
if old.checkpoint_at(row).is_some_and(|s| *s == state) {
debug_assert_eq!(win, old.win, "the window was forced to old.win above");
for (r, s) in &old.checkpoints {
if *r > row {
checkpoints.push((*r, s.clone()));
}
}
for (k, wr) in old.win.clone().enumerate() {
if wr > row {
win_spans.push(old.win_spans[k].clone());
win_states.push(old.win_states[k].clone());
}
}
return SegmentTokens {
rows,
started_fresh,
checkpoints,
end: old.end.clone(),
win,
win_spans,
win_states,
tokenized: row - lo + 1, };
}
}
}
row += 1;
}
SegmentTokens {
rows,
started_fresh,
checkpoints,
end: SegmentBoundary(Box::new(state)),
win,
win_spans,
win_states,
tokenized: hi - lo,
}
}
impl Retention {
fn win_idx(&self, row: u32) -> Option<usize> {
(self.win.contains(&row) && row < self.n_lines).then(|| (row - self.win.start) as usize)
}
fn shift_window(&mut self, spans: &[(u32, u32, u32)]) {
if spans.is_empty() {
return;
}
let b = HIGHLIGHT_MAX_WINDOW_ROWS as usize;
let mut pref: Vec<i64> = Vec::with_capacity(spans.len() + 1);
pref.push(0);
for &(_, o, n) in spans {
pref.push(pref[pref.len() - 1] + i64::from(n) - i64::from(o));
}
let shift_at = |pre: u32| -> i64 { pref[spans.partition_point(|s| s.0 + s.1 <= pre)] };
let edited = |pre: u32| -> bool {
let i = spans.partition_point(|s| s.0 + s.1 <= pre);
i < spans.len() && pre >= spans[i].0
};
let mut probes: Vec<(u32, Box<LineState>)> = Vec::new();
for (j, &(ps, o, n)) in spans.iter().enumerate() {
if n == 0 {
continue;
}
if let Some(st) = self.stored_state_after(ps + o - 1) {
let post = (i64::from(ps) + pref[j]) as u32 + n - 1;
probes.push((post, Box::new(st.clone())));
}
}
let old_start = self.win.start;
let new_start = (i64::from(old_start) + shift_at(old_start)) as u32;
let old_spans = std::mem::take(&mut self.win_spans);
let old_states = std::mem::take(&mut self.win_states);
type Survivor = (u32, Option<Vec<HighlightSpan>>, Option<Box<LineState>>);
let mut max_post = new_start;
let mut survivors: Vec<Survivor> = Vec::with_capacity(old_spans.len());
for (idx, (sp, st)) in old_spans.into_iter().zip(old_states).enumerate() {
let pre = old_start + idx as u32;
if edited(pre) {
continue; }
let post = (i64::from(pre) + shift_at(pre)) as u32;
max_post = max_post.max(post);
survivors.push((post, sp, st));
}
for (r, _) in &probes {
max_post = max_post.max(*r);
}
let len = ((max_post + 1).saturating_sub(new_start) as usize).min(b);
let mut spans_v: Vec<Option<Vec<HighlightSpan>>> = vec![None; len];
let mut states_v: Vec<Option<Box<LineState>>> = vec![None; len];
for (post, sp, st) in survivors {
if let Some(i) = post.checked_sub(new_start).map(|d| d as usize) {
if i < len {
spans_v[i] = sp;
states_v[i] = st;
}
}
}
for (post, probe) in probes {
if let Some(i) = post.checked_sub(new_start).map(|d| d as usize) {
if i < len && states_v[i].is_none() {
states_v[i] = Some(probe);
}
}
}
self.win = new_start..new_start + len as u32;
self.win_spans = spans_v;
self.win_states = states_v;
}
fn stored_state_after(&self, row: u32) -> Option<&LineState> {
if let Some(i) = self.win_idx(row) {
if let Some(s) = &self.win_states[i] {
return Some(s);
}
}
self.checkpoints.state_at(row)
}
fn nearest_state_at_or_before(&self, row: u32) -> Option<(u32, &LineState)> {
let cp = self.checkpoints.floor(row);
let mut ws = None;
if !self.win.is_empty() && self.win.start <= row {
let hi = row.min(self.win.end - 1);
let lo = cp.map_or(self.win.start, |(r, _)| (r + 1).max(self.win.start));
let mut r = hi;
while r >= lo {
if let Some(s) = &self.win_states[(r - self.win.start) as usize] {
ws = Some((r, &**s));
break;
}
if r == lo {
break;
}
r -= 1;
}
}
match (cp, ws) {
(Some(c), Some(w)) => Some(if w.0 >= c.0 { w } else { c }),
(c, w) => w.or(c),
}
}
fn start_state_for(&self, row: u32) -> StartState {
if row == 0 {
return StartState::Fresh;
}
if let Some(s) = self.stored_state_after(row - 1) {
return StartState::Ready(s.clone());
}
match self.nearest_state_at_or_before(row - 1) {
Some((r, s)) => StartState::WarmupStored(r, s.clone()),
None => StartState::WarmupFresh,
}
}
#[allow(clippy::too_many_arguments)]
fn warm_up<S: AsRef<str>>(
&mut self,
from: Option<u32>,
to: u32,
mut state: LineState,
work: &mut u32,
max_lines: u32,
syntect: &SyntectHighlighter<'_>,
set: &SyntaxSet,
line: &mut impl FnMut(u32) -> S,
) -> Option<LineState> {
let mut r = from;
loop {
let next = r.map_or(0, |r| r + 1);
if next >= to {
return Some(state);
}
if *work >= max_lines {
if let Some(r) = r {
self.keep_resume_checkpoint(Some(r), &state);
}
return None;
}
let (spans, end) = tokenize_line(syntect, set, &state, line(next).as_ref());
self.retain(next, spans, &end);
*work += 1;
r = Some(next);
state = end;
}
}
fn retain(&mut self, row: u32, spans: Vec<HighlightSpan>, end: &LineState) {
if let Some(i) = self.win_idx(row) {
self.win_spans[i] = Some(spans);
self.win_states[i] = Some(Box::new(end.clone()));
}
if (row + 1).is_multiple_of(HIGHLIGHT_CHECKPOINT_STRIDE) {
self.checkpoints.set_stride(row, end.clone());
} else {
self.checkpoints.refresh_if_present(row, end.clone());
}
}
fn keep_resume_checkpoint(&mut self, row: Option<u32>, state: &LineState) {
let Some(row) = row else { return }; if self.stored_state_after(row).is_none() {
self.checkpoints.upsert(row, state.clone());
}
}
fn first_window_gap(&self, before: u32) -> Option<u32> {
let end = self.win.end.min(self.n_lines).min(before);
(self.win.start..end).find(|&r| self.win_spans[(r - self.win.start) as usize].is_none())
}
}
#[cfg(test)]
mod tests {
use super::*;
const GRAMMAR: &str = "%YAML 1.2\n\
---\n\
name: Test\n\
scope: source.test\n\
contexts:\n\
\x20 main:\n\
\x20 - match: '\\bkw\\b'\n\
\x20 scope: keyword.control.test\n";
const THEME: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>name</key><string>Test</string>
<key>settings</key><array>
<dict><key>settings</key><dict><key>background</key><string>#000000</string><key>foreground</key><string>#ffffff</string></dict></dict>
<dict><key>scope</key><string>keyword</string><key>settings</key><dict><key>foreground</key><string>#ff0000</string></dict></dict>
</array></dict></plist>"#;
fn highlighter() -> Highlighter {
Highlighter::new(
SyntaxDef::from_sublime_syntax(GRAMMAR).expect("grammar parses"),
TokenTheme::from_tm_theme(THEME).expect("theme parses"),
)
}
#[test]
fn keyword_gets_the_keyword_color() {
let lines = highlighter().highlight("kw x");
assert_eq!(lines.len(), 1);
let red = Rgba { r: 0xff, g: 0, b: 0, a: 0xff };
let white = Rgba { r: 0xff, g: 0xff, b: 0xff, a: 0xff };
let kw = lines[0].iter().find(|s| s.range == (0..2)).expect("a span at 0..2");
assert_eq!(kw.style.fg, red);
assert!(lines[0].iter().any(|s| s.style.fg == white), "non-keyword text is default white");
}
#[test]
fn per_line_spans_and_a_trailing_empty_line() {
let lines = highlighter().highlight("kw\nx\n");
assert_eq!(lines.len(), 3); assert!(lines[0].iter().any(|s| s.range == (0..2))); assert!(lines[2].is_empty()); }
fn cache(n: u32) -> HighlightCache {
HighlightCache::new(
SyntaxDef::from_sublime_syntax(GRAMMAR).unwrap(),
TokenTheme::from_tm_theme(THEME).unwrap(),
n,
)
}
fn lines(text: &str) -> Vec<&str> {
text.split('\n').collect()
}
fn assert_matches_full(cache: &HighlightCache, text: &str) {
let full = highlighter().highlight(text);
for (row, expected) in full.iter().enumerate() {
assert_eq!(cache.line_spans(row as u32), Some(expected.as_slice()), "line {row}");
}
}
#[test]
fn fresh_cache_matches_whole_document_highlight() {
let text = "kw a\nb kw\nc";
let ls = lines(text);
let mut c = cache(ls.len() as u32);
c.tokenize_until(ls.len() as u32, u32::MAX, |r| ls[r as usize]);
assert_matches_full(&c, text);
}
#[test]
fn in_place_edit_reconverges() {
let before = "kw a\nb b\nkw c";
let ls0 = lines(before);
let mut c = cache(ls0.len() as u32);
c.tokenize_until(ls0.len() as u32, u32::MAX, |r| ls0[r as usize]);
let after = "kw a\nb kw\nkw c";
let ls1 = lines(after);
c.on_commit(1, 1, 1);
c.tokenize_until(ls1.len() as u32, u32::MAX, |r| ls1[r as usize]);
assert_matches_full(&c, after);
}
#[test]
fn insert_line_splices_and_reconverges() {
let before = "kw\nx";
let ls0 = lines(before);
let mut c = cache(ls0.len() as u32);
c.tokenize_until(ls0.len() as u32, u32::MAX, |r| ls0[r as usize]);
let after = "kw\ny\nx";
let ls1 = lines(after);
c.on_commit(0, 1, 2);
c.tokenize_until(ls1.len() as u32, u32::MAX, |r| ls1[r as usize]);
assert_matches_full(&c, after);
}
#[test]
fn theme_swap_invalidates_all_keeps_stale_colors_then_recolors() {
const THEME_BLUE: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>name</key><string>Blue</string>
<key>settings</key><array>
<dict><key>settings</key><dict><key>background</key><string>#000000</string><key>foreground</key><string>#ffffff</string></dict></dict>
<dict><key>scope</key><string>keyword</string><key>settings</key><dict><key>foreground</key><string>#0000ff</string></dict></dict>
</array></dict></plist>"#;
let red = Rgba { r: 0xff, g: 0, b: 0, a: 0xff };
let blue = Rgba { r: 0, g: 0, b: 0xff, a: 0xff };
let has = |c: &HighlightCache, row: u32, col: Rgba| {
c.line_spans(row).unwrap().iter().any(|s| s.style.fg == col)
};
let ls = lines("kw a\nb kw");
let mut c = cache(ls.len() as u32);
c.tokenize_until(ls.len() as u32, u32::MAX, |r| ls[r as usize]);
assert!(has(&c, 0, red) && has(&c, 1, red));
c.set_theme(TokenTheme::from_tm_theme(THEME_BLUE).unwrap());
assert!(has(&c, 0, red) && has(&c, 1, red), "stale colors until repainted");
c.tokenize_until(ls.len() as u32, u32::MAX, |r| ls[r as usize]);
assert!(has(&c, 0, blue) && has(&c, 1, blue), "both lines recolored");
assert!(!has(&c, 0, red) && !has(&c, 1, red), "no stale red survives repaint");
}
#[test]
fn convergence_stops_early_below_an_edit() {
let ls0 = lines("kw\nkw\nkw");
let mut c = cache(3);
c.tokenize_until(3, u32::MAX, |r| ls0[r as usize]);
c.on_commit(0, 1, 1); let probe_lines = ["kw", "kw", "SHOULD-NOT-RETOKENIZE"];
c.tokenize_until(3, u32::MAX, |r| probe_lines[r as usize]);
assert_eq!(c.line_spans(2), Some(highlighter().highlight("kw").pop().unwrap().as_slice()));
}
#[test]
fn perf_canary_state_neutral_keystroke_is_o1_regardless_of_size() {
let big = vec!["kw x"; 2000].join("\n");
let ls = lines(&big);
let mut c = cache(ls.len() as u32);
c.tokenize_until(ls.len() as u32, u32::MAX, |r| ls[r as usize]); c.on_commit(1000, 1, 1); let n = c.tokenize_until(ls.len() as u32, u32::MAX, |r| ls[r as usize]);
assert!(n <= 2, "state-neutral keystroke re-tokenized {n} lines in a 2000-line doc (expected O(1))");
}
#[test]
fn perf_canary_target_bounds_work_to_the_viewport() {
let big = vec!["kw x"; 2000].join("\n");
let ls = lines(&big);
let mut c = cache(ls.len() as u32); let n = c.tokenize_until(50, u32::MAX, |r| ls[r as usize]);
assert_eq!(n, 51, "target must bound tokenization to the viewport (rows 0..=50)");
assert!(c.line_spans(50).is_some(), "the viewport is tokenized");
assert!(c.line_spans(51).is_none(), "nothing past the viewport target is tokenized");
}
#[test]
fn perf_canary_budget_caps_a_call_and_resumes() {
let big = vec!["kw x"; 300].join("\n");
let ls = lines(&big);
let mut c = cache(ls.len() as u32);
let n = c.tokenize_until(u32::MAX, 100, |r| ls[r as usize]);
assert_eq!(n, 100, "the budget caps the call");
assert_eq!(c.first_dirty(), Some(100), "the frontier records the resume point");
let n = c.tokenize_until(u32::MAX, u32::MAX, |r| ls[r as usize]);
assert_eq!(n, 200, "the next call finishes the remainder");
assert_eq!(c.first_dirty(), None, "converged");
}
#[test]
fn perf_canary_deep_dirty_tail_commit_is_cheap() {
let mut c = cache(1_000_000); let t0 = std::time::Instant::now();
for _ in 0..1_000 {
c.on_commit(500_000, 1, 1);
}
assert!(
t0.elapsed() < std::time::Duration::from_secs(1),
"1000 deep-dirty-tail commits took {:?} — the dirty set is being rebuilt per commit",
t0.elapsed()
);
assert!(c.ret.invalid.runs() <= 3, "the run list stays compact: {}", c.ret.invalid.runs());
}
#[test]
fn dirty_runs_stay_small_under_a_typing_run() {
let mut c = cache(2_000);
let big = vec!["kw x"; 2000].join("\n");
let ls = lines(&big);
c.tokenize_until(u32::MAX, u32::MAX, |r| ls[r as usize]);
for _ in 0..50 {
c.on_commit(700, 1, 1);
}
c.on_commit(100, 1, 2);
c.on_commit(1500, 2, 1);
assert!(c.ret.invalid.runs() <= 4, "scattered typing produced {} runs", c.ret.invalid.runs());
}
const GRAMMAR_STR: &str = "%YAML 1.2\n\
---\n\
name: TestStr\n\
scope: source.tstr\n\
contexts:\n\
\x20 main:\n\
\x20 - match: '\"'\n\
\x20 scope: punctuation.definition.string.begin\n\
\x20 push: string\n\
\x20 - match: '\\bkw\\b'\n\
\x20 scope: keyword.control.test\n\
\x20 string:\n\
\x20 - match: '\"'\n\
\x20 scope: punctuation.definition.string.end\n\
\x20 pop: true\n\
\x20 - match: '.'\n\
\x20 scope: string.quoted.test\n";
const GRAMMAR_CMT: &str = "%YAML 1.2\n\
---\n\
name: TestCmt\n\
scope: source.tcmt\n\
contexts:\n\
\x20 main:\n\
\x20 - match: '/\\*'\n\
\x20 scope: punctuation.definition.comment.begin\n\
\x20 push: comment\n\
\x20 - match: '\\bkw\\b'\n\
\x20 scope: keyword.control.test\n\
\x20 comment:\n\
\x20 - match: '\\*/'\n\
\x20 scope: punctuation.definition.comment.end\n\
\x20 pop: true\n\
\x20 - match: '.'\n\
\x20 scope: comment.block.test\n";
fn engine_cmt() -> HighlightEngine {
HighlightEngine::new(
SyntaxDef::from_sublime_syntax(GRAMMAR_CMT).unwrap(),
TokenTheme::from_tm_theme(THEME).unwrap(),
)
}
fn cache_cmt(n: u32) -> HighlightCache {
HighlightCache::new(
SyntaxDef::from_sublime_syntax(GRAMMAR_CMT).unwrap(),
TokenTheme::from_tm_theme(THEME).unwrap(),
n,
)
}
fn highlighter_cmt() -> Highlighter {
Highlighter::new(
SyntaxDef::from_sublime_syntax(GRAMMAR_CMT).unwrap(),
TokenTheme::from_tm_theme(THEME).unwrap(),
)
}
fn cache_str(n: u32) -> HighlightCache {
HighlightCache::new(
SyntaxDef::from_sublime_syntax(GRAMMAR_STR).unwrap(),
TokenTheme::from_tm_theme(THEME).unwrap(),
n,
)
}
fn highlighter_str() -> Highlighter {
Highlighter::new(
SyntaxDef::from_sublime_syntax(GRAMMAR_STR).unwrap(),
TokenTheme::from_tm_theme(THEME).unwrap(),
)
}
fn drive_all(c: &mut HighlightCache, lines: &[String]) -> u32 {
let mut work = 0;
while c.pending().is_some() {
work += c
.tokenize_until(u32::MAX, HIGHLIGHT_MAX_LINES_PER_CALL, |r| lines[r as usize].as_str());
}
work
}
#[test]
fn virtualized_sweep_retains_bounded_rows() {
let lines: Vec<String> = (0..10_000).map(|i| format!("kw line {i}")).collect();
let mut c = cache(10_000);
c.set_window(0..64);
drive_all(&mut c, &lines);
let window_bound = (64 + 2 * HIGHLIGHT_WINDOW_SLACK) as usize;
assert!(
c.retained_span_rows() <= window_bound,
"span retention must be windowed: {} rows kept (bound {window_bound})",
c.retained_span_rows()
);
let state_bound = window_bound + 3 * (10_000 / HIGHLIGHT_CHECKPOINT_STRIDE as usize);
assert!(
c.retained_state_rows() <= state_bound,
"state retention must be window + sparse checkpoints: {} kept (bound {state_bound})",
c.retained_state_rows()
);
let full = highlighter().highlight(&lines.join("\n"));
assert_eq!(c.line_spans(10), Some(full[10].as_slice()));
assert_eq!(c.line_spans(5_000), None);
}
#[test]
fn window_move_refills_from_checkpoints() {
let mut lines: Vec<String> = (0..3_000).map(|i| format!("x {i} kw")).collect();
lines[1_000] = "\"open".into(); lines[2_000] = "close\"".into(); let full = highlighter_str().highlight(&lines.join("\n"));
let mut c = cache_str(3_000);
c.set_window(0..40);
drive_all(&mut c, &lines);
assert!(c.line_spans(1_600).is_none(), "deep rows evicted after the sweep");
c.set_window(1_500..1_540);
let fill_work = drive_all(&mut c, &lines);
for r in 1_500..1_540 {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "row {r}");
}
assert_eq!(c.line_spans(10), None, "the old window was evicted");
let bound = HIGHLIGHT_CHECKPOINT_STRIDE + 40 + 2 * HIGHLIGHT_WINDOW_SLACK + 8;
assert!(fill_work <= bound, "cold-jump refill did {fill_work} lines (bound {bound})");
}
#[test]
fn out_of_window_edit_reconverges_within_a_stride() {
let mut lines: Vec<String> = (0..2_000).map(|i| format!("kw {i}")).collect();
let mut c = cache_str(2_000);
c.set_window(1_500..1_540);
drive_all(&mut c, &lines);
lines[100] = "kw edited".into(); c.on_commit(100, 1, 1);
let work = drive_all(&mut c, &lines);
let bound = 2 * HIGHLIGHT_CHECKPOINT_STRIDE + 2;
assert!(work <= bound, "out-of-window keystroke re-tokenized {work} lines (bound {bound})");
let full = highlighter_str().highlight(&lines.join("\n"));
assert_eq!(c.line_spans(1_520), Some(full[1_520].as_slice()));
}
#[test]
fn in_window_keystroke_stays_o1_with_stateful_grammar() {
let mut lines: Vec<String> = (0..2_000).map(|i| format!("kw {i}")).collect();
lines[300] = "\"open".into();
lines[600] = "shut\"".into();
let mut c = cache_str(2_000);
c.set_window(900..940);
drive_all(&mut c, &lines);
lines[920] = "kw retyped".into();
c.on_commit(920, 1, 1);
let work = drive_all(&mut c, &lines);
assert!(work <= 2, "in-window keystroke re-tokenized {work} lines (expected O(1))");
}
#[test]
fn randomized_edits_and_window_moves_match_the_oracle() {
let mut lines: Vec<String> = (0..1_200)
.map(|i| match i % 97 {
13 => "\"".to_string(),
51 => "shut\" kw".to_string(),
_ => format!("kw word {i}"),
})
.collect();
let mut c = cache_str(1_200);
let mut state = 0xDEADBEEF_u64;
let mut rand = move |bound: usize| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
((state >> 33) as usize) % bound.max(1)
};
for _step in 0..120 {
match rand(3) {
0 => {
let s = rand(lines.len());
let old = (1 + rand(3)).min(lines.len() - s);
let n = 1 + rand(3);
let repl: Vec<String> = (0..n)
.map(|k| match rand(5) {
0 => "\"".to_string(),
1 => "q\" kw".to_string(),
_ => format!("e{k} kw"),
})
.collect();
lines.splice(s..s + old, repl);
c.on_commit(s as u32, old as u32, n as u32);
}
1 => {
let s = rand(lines.len());
c.set_window(s as u32..(s + 40).min(lines.len()) as u32);
}
_ => {
let target = rand(lines.len()) as u32;
let budget = 32 + rand(200) as u32;
c.tokenize_until(target, budget, |r| lines[r as usize].as_str());
}
}
assert_eq!(c.line_count() as usize, lines.len(), "line count in step");
}
let full = highlighter_str().highlight(&lines.join("\n"));
drive_all(&mut c, &lines);
let mut checked = 0usize;
for r in 0..lines.len() as u32 {
if let Some(spans) = c.line_spans(r) {
assert_eq!(spans, full[r as usize].as_slice(), "row {r}");
checked += 1;
}
}
assert!(checked >= 40, "the window retained rows to check ({checked})");
for &probe in &[0usize, 400, 800, lines.len().saturating_sub(45)] {
c.set_window(probe as u32..(probe + 40).min(lines.len()) as u32);
drive_all(&mut c, &lines);
for (r, expected) in
full.iter().enumerate().take((probe + 40).min(lines.len())).skip(probe)
{
assert_eq!(c.line_spans(r as u32), Some(expected.as_slice()), "probe {probe} row {r}");
}
}
}
#[test]
fn scattered_multi_caret_marks_only_edited_lines_dirty() {
let mut lines: Vec<String> = (0..2_000).map(|i| format!("kw {i}")).collect();
let mut c = cache_str(2_000);
c.set_window(0..40);
drive_all(&mut c, &lines);
assert_eq!(c.dirty_row_count(), 0, "swept clean");
lines[10] = "kw a".into();
lines[1_500] = "kw b".into();
c.on_commit_patch(&[(10, 1, 1), (1_500, 1, 1)]);
assert_eq!(
c.dirty_row_count(),
2,
"a scattered 2-caret edit must mark 2 lines dirty, not the ~1491-row covering span"
);
drive_all(&mut c, &lines);
let full = highlighter_str().highlight(&lines.join("\n"));
for r in 0..40 {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "row {r}");
}
}
#[test]
fn scattered_edit_keeps_the_window_aimed_at_the_viewport() {
let mut lines: Vec<String> = (0..2_000).map(|i| format!("kw word {i}")).collect();
let mut c = cache_str(2_000);
c.set_window(940..980);
drive_all(&mut c, &lines);
assert!(c.line_spans(960).is_some(), "the viewport window is filled");
for r in [10usize, 960, 1_500] {
lines[r] = "kw w".into();
}
c.on_commit_patch(&[(10, 1, 1), (960, 1, 1), (1_500, 1, 1)]);
drive_all(&mut c, &lines);
let full = highlighter_str().highlight(&lines.join("\n"));
for r in 940..980u32 {
assert_eq!(
c.line_spans(r),
Some(full[r as usize].as_slice()),
"viewport row {r} did not recolor without a scroll to re-aim the window",
);
}
}
#[test]
fn randomized_multi_edit_commits_match_the_oracle() {
let mut lines: Vec<String> = (0..1_200)
.map(|i| match i % 97 {
13 => "\"".to_string(),
51 => "shut\" kw".to_string(),
_ => format!("kw word {i}"),
})
.collect();
let mut c = cache_str(1_200);
let mut state = 0x1234_5678_9ABC_DEF0_u64;
let mut rand = move |bound: usize| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
((state >> 33) as usize) % bound.max(1)
};
for _step in 0..80 {
if rand(3) == 2 {
let target = rand(lines.len()) as u32;
c.tokenize_until(target, 32 + rand(200) as u32, |r| lines[r as usize].as_str());
assert_eq!(c.line_count() as usize, lines.len());
continue;
}
let k = 1 + rand(4);
let mut sites: Vec<(usize, usize, Vec<String>)> = Vec::new();
let mut cursor = rand(20);
for _ in 0..k {
let s = cursor + rand(30);
if s >= lines.len() {
break;
}
let old = (1 + rand(3)).min(lines.len() - s);
let n = 1 + rand(3);
let repl: Vec<String> = (0..n)
.map(|j| match rand(5) {
0 => "\"".to_string(),
1 => "q\" kw".to_string(),
_ => format!("m{j} kw"),
})
.collect();
sites.push((s, old, repl));
cursor = s + old + 1; }
if sites.is_empty() {
continue;
}
let spans: Vec<(u32, u32, u32)> =
sites.iter().map(|(s, o, r)| (*s as u32, *o as u32, r.len() as u32)).collect();
c.on_commit_patch(&spans);
for (s, o, repl) in sites.iter().rev() {
lines.splice(*s..*s + *o, repl.clone());
}
assert_eq!(c.line_count() as usize, lines.len(), "line count after multi-edit");
}
let full = highlighter_str().highlight(&lines.join("\n"));
drive_all(&mut c, &lines);
let mut checked = 0usize;
for r in 0..lines.len() as u32 {
if let Some(spans) = c.line_spans(r) {
assert_eq!(spans, full[r as usize].as_slice(), "row {r}");
checked += 1;
}
}
assert!(checked >= 40, "retained rows to check ({checked})");
for &probe in &[0usize, 300, 700, lines.len().saturating_sub(45)] {
c.set_window(probe as u32..(probe + 40).min(lines.len()) as u32);
drive_all(&mut c, &lines);
for (r, expected) in
full.iter().enumerate().take((probe + 40).min(lines.len())).skip(probe)
{
assert_eq!(c.line_spans(r as u32), Some(expected.as_slice()), "probe {probe} row {r}");
}
}
}
#[test]
fn shifted_checkpoints_are_refreshed_by_a_crossing_cascade() {
let mut lines: Vec<String> = (0..2_000).map(|i| format!("kw {i}")).collect();
let mut c = cache_str(2_000);
c.set_window(0..40);
drive_all(&mut c, &lines);
lines.insert(10, "kw split".to_string());
c.on_commit(10, 1, 2);
drive_all(&mut c, &lines);
lines[12] = "\"open".to_string(); c.on_commit(12, 1, 1);
drive_all(&mut c, &lines);
c.set_window(1_285..1_325);
drive_all(&mut c, &lines);
let full = highlighter_str().highlight(&lines.join("\n"));
for r in 1_285..1_325u32 {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "row {r}");
}
}
#[test]
fn checkpoints_do_not_accumulate_across_edit_cycles() {
let mut lines: Vec<String> = (0..4_000).map(|i| format!("kw {i}")).collect();
let mut c = cache_str(4_000);
c.set_window(0..40);
drive_all(&mut c, &lines);
for cycle in 0..16 {
lines.insert(5, format!("kw cycle {cycle}"));
c.on_commit(5, 1, 2);
lines[7] = "\"open".to_string();
c.on_commit(7, 1, 1);
drive_all(&mut c, &lines);
lines[7] = "kw closed".to_string();
c.on_commit(7, 1, 1);
drive_all(&mut c, &lines);
}
let bound = 2 * (c.line_count() / HIGHLIGHT_CHECKPOINT_STRIDE) as usize + 8;
assert!(
c.ret.checkpoints.len() <= bound,
"checkpoints accumulate: {} after 16 cycles (bound {bound})",
c.ret.checkpoints.len()
);
}
fn fresh_state() -> LineState {
engine_str().fresh_state()
}
fn str_state() -> LineState {
let eng = engine_str();
let syntect = SyntectHighlighter::new(&eng.theme.0);
let (_spans, end) = tokenize_line(&syntect, &eng.syntax.set, &eng.fresh_state(), "\"");
end
}
fn vec_shift_reference(cps: &mut Vec<(u32, Box<LineState>)>, spans: &[(u32, u32, u32)]) {
if spans.is_empty() {
return;
}
let old = std::mem::take(cps);
let mut si = 0;
let mut acc = 0i64;
for (row, state) in old {
while si < spans.len() && spans[si].0 + spans[si].1 <= row {
acc += i64::from(spans[si].2) - i64::from(spans[si].1);
si += 1;
}
if si < spans.len() && row >= spans[si].0 {
continue; }
cps.push(((row as i64 + acc) as u32, state));
}
}
#[test]
fn checkpoint_tree_equals_the_vec_reference_under_random_edits() {
let (st_a, st_b) = (fresh_state(), str_state());
assert!(st_a != st_b, "seed states must differ for the state check to bite");
let mut rng = 0x1234_5678u32;
let mut next = || {
rng ^= rng << 13;
rng ^= rng >> 17;
rng ^= rng << 5;
rng
};
for trial in 0..200 {
let mut n_lines = 400u32 + next() % 4_000;
let k = 4 + next() % 60;
let mut rows: Vec<u32> = Vec::new();
let mut r = next() % 40;
for _ in 0..k {
if r >= n_lines {
break;
}
rows.push(r);
r += 1 + next() % 90;
}
let seed = |i: usize| if i.is_multiple_of(2) { st_a.clone() } else { st_b.clone() };
let init: Vec<(u32, Box<LineState>)> =
rows.iter().enumerate().map(|(i, &r)| (r, Box::new(seed(i)))).collect();
let mut tree = {
let items = init.iter().scan(0u32, |prev, (r, s)| {
let gap = r - *prev;
*prev = *r;
Some(CkptItem { row_gap: gap, state: s.clone() })
});
Checkpoints { tree: SumTree::from_items(items) }
};
let mut reference = init;
for step in 0..12 {
let n_edits = 1 + (next() % 3) as usize;
let mut spans: Vec<(u32, u32, u32)> = Vec::new();
let mut cursor = 0u32;
for _ in 0..n_edits {
if cursor >= n_lines {
break;
}
let span = (n_lines - cursor).clamp(1, 120);
let s = cursor + next() % span;
if s >= n_lines {
break;
}
let o = (next() % 5).min(n_lines - s);
let nw = next() % 5;
spans.push((s, o, nw));
cursor = s + o + 1; }
if spans.is_empty() {
continue;
}
let delta: i64 = spans.iter().map(|&(_, o, n)| i64::from(n) - i64::from(o)).sum();
tree.shift(&spans);
vec_shift_reference(&mut reference, &spans);
n_lines = (n_lines as i64 + delta) as u32;
let got = tree.rows();
let want: Vec<u32> = reference.iter().map(|(r, _)| *r).collect();
assert_eq!(got, want, "trial {trial} step {step}: rows diverge for {spans:?}");
let states_ok = tree
.tree
.items()
.iter()
.zip(&reference)
.all(|(it, (_, s))| *it.state == **s);
assert!(states_ok, "trial {trial} step {step}: a checkpoint state was mismapped");
}
}
}
#[test]
fn checkpoint_shift_is_checkpoint_count_independent() {
let st = fresh_state();
let meter = |k: u32| -> u64 {
let rows: Vec<u32> =
(0..k).map(|i| (i + 1) * HIGHLIGHT_CHECKPOINT_STRIDE - 1).collect();
let mut cps = Checkpoints::from_rows(&rows, &st);
crate::perf::reset();
cps.shift(&[(5, 1, 1)]);
crate::perf::meter()
};
let (small, big) = (meter(1000), meter(2000));
eprintln!("[perf_gate] checkpoint shift charge {small:>7} -> {big:>7}");
assert!(
big <= small + small / 4 + 256,
"checkpoint shift charged {small} -> {big}: it walked every checkpoint, not the seam",
);
}
#[test]
fn window_length_is_capped() {
let lines: Vec<String> = (0..200_000).map(|i| format!("kw {i}")).collect();
let mut c = cache(200_000);
c.set_window(0..150_000); drive_all(&mut c, &lines);
assert!(
c.retained_span_rows() <= HIGHLIGHT_MAX_WINDOW_ROWS as usize,
"the window must be capped: {} rows retained",
c.retained_span_rows()
);
}
#[test]
fn in_window_commit_with_a_document_scale_span_stays_bounded() {
let mut c = cache(200_000);
c.set_window(100..140); assert!(c.window_len() <= HIGHLIGHT_MAX_WINDOW_ROWS as usize);
c.on_commit(100, 1, 150_000);
assert!(
c.window_len() <= HIGHLIGHT_MAX_WINDOW_ROWS as usize,
"in-window covering splice regrew the window to {} rows",
c.window_len()
);
assert_eq!(c.line_count(), 200_000 + 150_000 - 1);
}
fn engine_str() -> HighlightEngine {
HighlightEngine::new(
SyntaxDef::from_sublime_syntax(GRAMMAR_STR).unwrap(),
TokenTheme::from_tm_theme(THEME).unwrap(),
)
}
fn snap(lines: &[String]) -> Snapshot {
crate::buffer::Buffer::new(&lines.join("\n")).unwrap().snapshot()
}
fn drive_all_snap(c: &mut HighlightCache, s: &Snapshot) {
while c.pending().is_some() {
c.tokenize_until(u32::MAX, HIGHLIGHT_MAX_LINES_PER_CALL, |r| s.line(r));
}
}
fn parallel_verify(
engine: &HighlightEngine,
snapshot: &Snapshot,
cuts: &[u32],
spans_for: Option<Range<u32>>,
) -> (Vec<SegmentTokens>, usize) {
let n = snapshot.line_count();
let mut bounds = vec![0u32];
bounds.extend(cuts.iter().copied().filter(|&c| c > 0 && c < n));
bounds.sort_unstable();
bounds.dedup();
bounds.push(n);
let segs: Vec<SegmentTokens> = bounds
.windows(2)
.map(|w| {
tokenize_segment(engine, snapshot, w[0]..w[1], SegmentStart::Fresh, spans_for.clone(), None)
})
.collect();
let fresh = engine.fresh_boundary();
let mut verified: Vec<SegmentTokens> = Vec::with_capacity(segs.len());
let mut prev_end: Option<SegmentBoundary> = None;
let mut reruns = 0;
for (idx, seg) in segs.into_iter().enumerate() {
let corrected = if idx == 0 {
seg } else {
let true_start = prev_end.clone().unwrap();
if true_start == fresh {
seg } else {
reruns += 1;
let rows = seg.rows();
tokenize_segment(
engine,
snapshot,
rows,
SegmentStart::After(true_start),
spans_for.clone(),
Some(&seg),
)
}
};
prev_end = Some(corrected.end_boundary().clone());
verified.push(corrected);
}
(verified, reruns)
}
#[test]
fn parallel_stitch_equals_the_whole_document_oracle() {
let engine = engine_str();
let mut state = 0x1234_5678_9ABC_DEF0_u64;
let mut rng = move |bound: usize| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
((state >> 33) as usize) % bound.max(1)
};
for round in 0..24 {
let n = 200 + rng(1_200);
let lines: Vec<String> = (0..n)
.map(|i| match rng(11) {
0 => "\"".to_string(), 1 => "text\" kw".to_string(), 2 => "\" and \" kw".to_string(),
_ => format!("kw word {i}"),
})
.collect();
let s = snap(&lines);
let ln = s.line_count();
let cuts: Vec<u32> = (0..1 + rng(6)).map(|_| rng(ln as usize) as u32).collect();
let win_start = rng(ln as usize) as u32;
let win = win_start..(win_start + 40).min(ln);
let (verified, _reruns) = parallel_verify(&engine, &s, &cuts, None);
let mut c = cache_str(ln);
c.set_window(win.clone());
for seg in verified {
c.absorb(seg, true);
}
assert_eq!(c.first_dirty(), None, "round {round}: verified sweep must clear all dirt");
drive_all_snap(&mut c, &s);
let full = highlighter_str().highlight(&lines.join("\n"));
for r in win.clone() {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "round {round} row {r}");
}
let mut sync = cache_str(ln);
sync.set_window(win.clone());
drive_all(&mut sync, &lines);
let got: Vec<u32> = c.ret.checkpoints.rows();
let want: Vec<u32> = sync.ret.checkpoints.rows();
assert_eq!(got, want, "round {round}: checkpoint rows diverge from a sync sweep");
}
}
#[test]
fn wrong_guess_repair_stops_within_a_stride() {
let engine = engine_cmt();
let mut lines: Vec<String> = (0..8_000).map(|i| format!("kw word {i}")).collect();
lines[100] = "/*".to_string();
lines[300] = "*/ kw".to_string();
let s = snap(&lines);
let seg_rows = 200u32..8_000;
let guessed = tokenize_segment(&engine, &s, seg_rows.clone(), SegmentStart::Fresh, None, None);
let truth_prefix = tokenize_segment(&engine, &s, 0..200, SegmentStart::Fresh, None, None);
let true_start = SegmentStart::After(truth_prefix.end_boundary().clone());
let repaired = tokenize_segment(&engine, &s, seg_rows, true_start, None, Some(&guessed));
let bound = (300 - 200) + HIGHLIGHT_CHECKPOINT_STRIDE;
assert!(
repaired.tokenized_rows() <= bound,
"repair tokenized {} rows (bound {bound}) — early-stop did not fire",
repaired.tokenized_rows()
);
assert!(repaired.tokenized_rows() < 7_800, "a full re-run would tokenize the whole segment");
let full_rerun =
tokenize_segment(&engine, &s, 200..8_000, SegmentStart::After(truth_prefix.end_boundary().clone()), None, None);
assert!(repaired.end_boundary() == full_rerun.end_boundary(), "spliced tail must be correct");
let (verified, reruns) = parallel_verify(&engine, &s, &[200], None);
assert_eq!(reruns, 1, "the in-comment cut must trigger exactly one re-run");
let mut c = cache_cmt(8_000);
c.set_window(190..600);
for v in verified {
c.absorb(v, true);
}
drive_all_snap(&mut c, &s);
let full = highlighter_cmt().highlight(&lines.join("\n"));
for r in 190..600u32 {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "row {r}");
}
}
#[test]
fn converging_rerun_ignores_a_moved_spans_for() {
let engine = engine_cmt();
let mut lines: Vec<String> = (0..4_000).map(|i| format!("kw word {i}")).collect();
lines[100] = "/*".to_string();
lines[300] = "*/ kw".to_string();
let s = snap(&lines);
let seg_rows = 200u32..2_000;
let old_window = Some(210u32..250); let guessed =
tokenize_segment(&engine, &s, seg_rows.clone(), SegmentStart::Fresh, old_window.clone(), None);
let prefix = tokenize_segment(&engine, &s, 0..200, SegmentStart::Fresh, None, None);
let after = || SegmentStart::After(prefix.end_boundary().clone());
let moved = tokenize_segment(&engine, &s, seg_rows.clone(), after(), Some(1_500..1_540), Some(&guessed));
let matched = tokenize_segment(&engine, &s, seg_rows, after(), old_window, Some(&guessed));
assert!(moved.end_boundary() == matched.end_boundary(), "same converged end");
assert_eq!(moved.rows, matched.rows);
assert_eq!(moved.win, matched.win, "window forced to the old segment's");
assert_eq!(moved.win_spans.len(), moved.win.len(), "spans line up with the window");
assert_eq!(moved.win_states.len(), moved.win.len());
let full = highlighter_cmt().highlight(&lines.join("\n"));
let mut c = cache_cmt(4_000);
c.set_window(210..250);
c.absorb(prefix, true);
c.absorb(moved, true);
drive_all_snap(&mut c, &s);
for r in 210..250u32 {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "row {r}");
}
}
#[test]
fn one_giant_string_falls_back_to_sequential_but_stays_correct() {
let engine = engine_str();
let mut lines: Vec<String> = (0..2_000).map(|i| format!("body {i}")).collect();
lines[0] = "\"".to_string(); let s = snap(&lines);
let (verified, reruns) = parallel_verify(&engine, &s, &[400, 800, 1_200, 1_600], None);
assert_eq!(reruns, 4, "every mid-string boundary mismatches");
let mut c = cache_str(2_000);
c.set_window(900..980);
for v in verified {
c.absorb(v, true);
}
drive_all_snap(&mut c, &s);
let full = highlighter_str().highlight(&lines.join("\n"));
for r in 900..980u32 {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "row {r}");
}
}
#[test]
fn verified_absorb_clears_dirt_and_shows_spans() {
let engine = engine_str();
let lines: Vec<String> = (0..2_000).map(|i| format!("kw word {i}")).collect();
let s = snap(&lines);
let win = 900u32..940;
let seg = tokenize_segment(&engine, &s, 0..2_000, SegmentStart::Fresh, Some(win.clone()), None);
let mut c = cache_str(2_000);
c.set_window(win.clone());
c.absorb(seg, true);
assert_eq!(c.first_dirty(), None, "verified absorb clears the whole frontier");
let full = highlighter_str().highlight(&lines.join("\n"));
for r in win {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "row {r}");
}
}
#[test]
fn speculative_absorb_shows_spans_without_checkpoints_or_clearing_dirt() {
let engine = engine_str();
let lines: Vec<String> = (0..2_000).map(|i| format!("kw word {i}")).collect();
let s = snap(&lines);
let win = 900u32..940;
let seg = tokenize_segment(&engine, &s, 772..940, SegmentStart::Fresh, Some(win.clone()), None);
let mut c = cache_str(2_000);
c.set_window(win.clone());
c.absorb(seg, false);
let full = highlighter_str().highlight(&lines.join("\n"));
for r in win.clone() {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "speculative row {r}");
}
assert_eq!(c.first_dirty(), Some(0), "speculation must not clear dirt");
assert!(c.ret.checkpoints.is_empty(), "speculation must not plant checkpoints");
}
#[test]
fn speculative_then_frontier_converges_o1_when_the_guess_was_right() {
let engine = engine_str();
let lines: Vec<String> = (0..2_000).map(|i| format!("kw word {i}")).collect();
let s = snap(&lines);
let win = 900u32..940;
let seg = tokenize_segment(&engine, &s, 772..940, SegmentStart::Fresh, Some(win.clone()), None);
let mut c = cache_str(2_000);
c.set_window(win.clone());
c.absorb(seg, false);
drive_all_snap(&mut c, &s);
assert_eq!(c.first_dirty(), None);
let full = highlighter_str().highlight(&lines.join("\n"));
for r in win {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "row {r}");
}
}
#[test]
fn verified_absorb_evicts_wrong_speculative_spans_no_poison() {
let engine = engine_cmt();
let mut lines: Vec<String> = (0..4_000).map(|i| format!("kw word {i}")).collect();
lines[100] = "/*".to_string(); lines[3_000] = "*/ kw".to_string(); let s = snap(&lines);
let win = 1_500u32..1_540; let full = highlighter_cmt().highlight(&lines.join("\n"));
let mut c = cache_cmt(4_000);
c.set_window(win.clone());
let spec = tokenize_segment(&engine, &s, 1_400..1_540, SegmentStart::Fresh, Some(win.clone()), None);
c.absorb(spec, false);
assert_ne!(
c.line_spans(1_520),
Some(full[1_520].as_slice()),
"the speculative guess must actually be wrong here (else the test proves nothing)"
);
let (verified, _) = parallel_verify(&engine, &s, &[], None);
for v in verified {
c.absorb(v, true);
}
assert_eq!(c.line_spans(1_520), None, "wrong speculative spans must be evicted, never trusted");
drive_all_snap(&mut c, &s);
for r in win {
assert_eq!(c.line_spans(r), Some(full[r as usize].as_slice()), "row {r} corrected");
}
}
#[test]
fn dirty_ranges_shift_merge_unit() {
let mut d = DirtyRanges::default();
d.insert(5);
d.insert(7);
d.insert(6); assert_eq!(d.0, vec![5..8]);
d.insert(4); assert_eq!(d.0, vec![4..8]);
d.apply_splices(&[(5, 2, 3)]);
assert_eq!(d.0, vec![4..9], "clip + shift + new-block merge into one run");
d.apply_splices(&[(0, 2, 0)]);
assert_eq!(d.0, vec![2..7]);
assert_eq!(d.first(), Some(2));
d.remove_first(2);
assert_eq!(d.0, vec![3..7]);
}
}