use std::cell::{Cell, RefCell};
use std::ops::Range;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{LazyLock, Mutex};
use std::time::{Duration, Instant};
use syntect::parsing::{ParseState, Scope, ScopeStack, SyntaxReference, SyntaxSet};
pub const MAX_HIGHLIGHT_SOURCE_BYTES: usize = 64 * 1024;
pub const MAX_HIGHLIGHT_LINE_CHARS: usize = 2_000;
pub const MAX_HIGHLIGHT_GRAMMARS: usize = 24;
const GRAMMAR_BUDGET_REFILL: Duration = Duration::from_secs(1);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Admission {
Warm,
Queue,
Wait,
}
struct GrammarBudget {
warm: Vec<usize>,
pending: Vec<usize>,
budget: usize,
last_refill: Option<Instant>,
refused: bool,
}
impl GrammarBudget {
fn refill(&mut self, now: Instant, cap: usize, interval: Duration) {
let Some(last) = self.last_refill else {
self.last_refill = Some(now);
return;
};
let elapsed = now.saturating_duration_since(last);
let Some(earned) = elapsed.as_nanos().checked_div(interval.as_nanos()) else {
return;
};
if earned == 0 {
return;
}
let room = cap.saturating_sub(self.budget);
if earned >= room as u128 {
self.budget = cap;
self.last_refill = Some(now);
} else {
let earned = earned as u32;
self.budget += earned as usize;
self.last_refill = Some(last + interval * earned);
}
}
fn admit(&mut self, key: usize, now: Instant, cap: usize, interval: Duration) -> Admission {
if self.warm.contains(&key) {
return Admission::Warm;
}
if self.pending.contains(&key) {
return Admission::Wait;
}
self.refill(now, cap, interval);
if self.budget == 0 {
self.refused = true;
return Admission::Wait;
}
self.budget -= 1;
self.pending.push(key);
Admission::Queue
}
fn take_retry(&mut self, now: Instant, cap: usize, interval: Duration) -> bool {
if !self.refused {
return false;
}
self.refill(now, cap, interval);
if self.budget == 0 {
return false;
}
self.refused = false;
true
}
fn mark_warm(&mut self, key: usize) {
self.pending.retain(|k| *k != key);
if !self.warm.contains(&key) {
self.warm.push(key);
}
}
}
static GRAMMARS: Mutex<GrammarBudget> = Mutex::new(GrammarBudget {
warm: Vec::new(),
pending: Vec::new(),
budget: MAX_HIGHLIGHT_GRAMMARS,
last_refill: None,
refused: false,
});
static WARM_GENERATION: AtomicU64 = AtomicU64::new(0);
pub fn warm_generation() -> u64 {
WARM_GENERATION.load(Ordering::Relaxed)
}
static RETRY_EPOCH: AtomicU64 = AtomicU64::new(0);
pub fn retry_epoch() -> u64 {
RETRY_EPOCH.load(Ordering::Relaxed)
}
struct WarmRequest {
syntax: &'static SyntaxReference,
lines: Vec<String>,
}
static WARM_TX: LazyLock<std::sync::mpsc::Sender<WarmRequest>> = LazyLock::new(|| {
let (tx, rx) = std::sync::mpsc::channel::<WarmRequest>();
std::thread::spawn(move || warm_worker(&rx));
tx
});
fn warm_worker(rx: &std::sync::mpsc::Receiver<WarmRequest>) {
LazyLock::force(&SYNTAXES);
while let Ok(req) = rx.recv() {
let key = std::ptr::from_ref(req.syntax) as usize;
let ok = {
let _expected = crate::terminal::ExpectedPanic::new();
catch_unwind(AssertUnwindSafe(|| compile_grammar(&req))).is_ok()
};
if !ok {
tracing::warn!(syntax = %req.syntax.name, "panic while warming a grammar");
continue;
}
if let Ok(mut grammars) = GRAMMARS.lock() {
grammars.mark_warm(key);
}
WARM_GENERATION.fetch_add(1, Ordering::Relaxed);
}
}
fn compile_grammar(req: &WarmRequest) {
let mut state = ParseState::new(req.syntax);
let mut stack = ScopeStack::new();
let mut buf = String::new();
for line in &req.lines {
if line.chars().count() > MAX_HIGHLIGHT_LINE_CHARS {
continue;
}
if tokenize_line(line, &mut state, &mut stack, &mut buf).is_none() {
break;
}
}
}
pub fn spawn_warm_worker() {
LazyLock::force(&WARM_TX);
}
pub fn warm_inline(language: Option<&str>) -> bool {
let Some(syntax) = lookup_syntax(language) else {
return false;
};
let key = std::ptr::from_ref(syntax) as usize;
if let Ok(mut grammars) = GRAMMARS.lock() {
grammars.mark_warm(key);
}
true
}
fn admit(syntax: &'static SyntaxReference) -> Admission {
let key = std::ptr::from_ref(syntax) as usize;
let Ok(mut grammars) = GRAMMARS.lock() else {
return Admission::Wait;
};
grammars.admit(
key,
Instant::now(),
MAX_HIGHLIGHT_GRAMMARS,
GRAMMAR_BUDGET_REFILL,
)
}
pub fn refused_grammar_retry_due() -> bool {
let Ok(mut grammars) = GRAMMARS.lock() else {
return false;
};
let due = grammars.take_retry(
Instant::now(),
MAX_HIGHLIGHT_GRAMMARS,
GRAMMAR_BUDGET_REFILL,
);
if due {
RETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
}
due
}
const CACHE_ENTRIES: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TokenClass {
Keyword,
String,
Comment,
Number,
Type,
Function,
Attribute,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token {
pub range: Range<usize>,
pub class: TokenClass,
}
pub type HighlightedLine = Vec<Token>;
pub fn style_for(theme: &crate::config::Theme, class: TokenClass) -> ratatui::style::Style {
match class {
TokenClass::Keyword => theme.syntax_keyword,
TokenClass::String => theme.syntax_string,
TokenClass::Comment => theme.syntax_comment,
TokenClass::Number => theme.syntax_number,
TokenClass::Type => theme.syntax_type,
TokenClass::Function => theme.syntax_function,
TokenClass::Attribute => theme.syntax_attribute,
}
}
pub fn slice_tokens(tokens: &[Token], start: usize, end: usize) -> Vec<Token> {
tokens
.iter()
.filter_map(|t| {
let s = t.range.start.max(start);
let e = t.range.end.min(end);
(s < e).then(|| Token {
range: (s - start)..(e - start),
class: t.class,
})
})
.collect()
}
const SCOPE_RULES: &[(&str, TokenClass)] = &[
("comment", TokenClass::Comment),
("string", TokenClass::String),
("constant.numeric", TokenClass::Number),
("constant.character", TokenClass::Number),
("constant.language", TokenClass::Number),
("storage", TokenClass::Keyword),
("keyword", TokenClass::Keyword),
("entity.name.function", TokenClass::Function),
("support.function", TokenClass::Function),
("entity.name.type", TokenClass::Type),
("entity.name.class", TokenClass::Type),
("entity.name.struct", TokenClass::Type),
("entity.name.enum", TokenClass::Type),
("entity.name.interface", TokenClass::Type),
("support.type", TokenClass::Type),
("support.class", TokenClass::Type),
("entity.name.tag", TokenClass::Attribute),
("entity.other.attribute-name", TokenClass::Attribute),
("meta.preprocessor", TokenClass::Attribute),
];
static RULES: LazyLock<Vec<(Scope, TokenClass)>> = LazyLock::new(|| {
SCOPE_RULES
.iter()
.filter_map(|(text, class)| Scope::new(text).ok().map(|scope| (scope, *class)))
.collect()
});
static SYNTAXES: LazyLock<SyntaxSet> = LazyLock::new(two_face::syntax::extra_newlines);
fn classify(stack: &ScopeStack) -> Option<TokenClass> {
stack.as_slice().iter().rev().find_map(|scope| {
RULES
.iter()
.find(|(prefix, _)| prefix.is_prefix_of(*scope))
.map(|(_, class)| *class)
})
}
fn language_token(info: &str) -> &str {
info.split([',', ' ', '\t', '{'])
.next()
.unwrap_or("")
.trim()
}
fn lookup_syntax(language: Option<&str>) -> Option<&'static SyntaxReference> {
let token = language_token(language?);
if token.is_empty() {
return None;
}
SYNTAXES.find_syntax_by_token(token)
}
struct CacheEntry {
syntax: &'static SyntaxReference,
lines: Vec<String>,
states: Vec<(ParseState, ScopeStack)>,
tokens: Vec<HighlightedLine>,
}
thread_local! {
static CACHE: RefCell<Vec<CacheEntry>> = const { RefCell::new(Vec::new()) };
}
fn common_prefix(a: &[String], b: &[&str]) -> usize {
a.iter()
.zip(b)
.take_while(|(x, y)| x.as_str() == **y)
.count()
}
fn common_suffix(a: &[String], b: &[&str], floor: usize) -> usize {
let max = a.len().min(b.len()).saturating_sub(floor);
a.iter()
.rev()
.zip(b.iter().rev())
.take(max)
.take_while(|(x, y)| x.as_str() == **y)
.count()
}
fn tokenize_line(
line: &str,
state: &mut ParseState,
stack: &mut ScopeStack,
buf: &mut String,
) -> Option<HighlightedLine> {
buf.clear();
buf.push_str(line);
buf.push('\n');
let ops = state.parse_line(buf, &SYNTAXES).ok()?;
let ascii = line.is_ascii();
let cursor = Cell::new((0usize, 0usize));
let to_char = |byte: usize| -> usize {
let byte = byte.min(line.len());
if ascii {
return byte;
}
let (mut b, mut c) = cursor.get();
if byte < b {
(b, c) = (0, 0);
}
let mut chars = line[b..].chars();
while b < byte {
let Some(ch) = chars.next() else { break };
b += ch.len_utf8();
c += 1;
}
cursor.set((b, c));
c
};
let mut out: HighlightedLine = Vec::new();
let mut run_start = 0usize;
let push = |from: usize, to: usize, stack: &ScopeStack, out: &mut HighlightedLine| {
if from >= to {
return;
}
let Some(class) = classify(stack) else {
return;
};
let (s, e) = (to_char(from), to_char(to));
match out.last_mut() {
Some(prev) if prev.class == class && prev.range.end == s => prev.range.end = e,
_ if s < e => out.push(Token { range: s..e, class }),
_ => {}
}
};
for (offset, op) in ops {
let offset = offset.min(line.len());
push(run_start, offset, stack, &mut out);
run_start = run_start.max(offset);
stack.apply(&op).ok()?;
}
push(run_start, line.len(), stack, &mut out);
Some(out)
}
fn tokenize_incremental(
syntax: &'static SyntaxReference,
raw_lines: &[&str],
) -> Option<Vec<HighlightedLine>> {
let n = raw_lines.len();
let fresh = || (ParseState::new(syntax), ScopeStack::new());
CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
let hit = cache
.iter()
.position(|e| std::ptr::eq(e.syntax, syntax))
.filter(|&i| !cache[i].lines.is_empty());
let (prefix, suffix, d) = match hit {
Some(i) => {
let e = &cache[i];
let p = common_prefix(&e.lines, raw_lines);
let s = common_suffix(&e.lines, raw_lines, p);
(p, s, e.lines.len() as isize - n as isize)
}
None => (0, 0, 0),
};
let mut tokens: Vec<HighlightedLine> = Vec::with_capacity(n);
let mut states: Vec<(ParseState, ScopeStack)> = Vec::with_capacity(n + 1);
let mut cur = fresh();
if let Some(i) = hit {
let e = &cache[i];
tokens.extend_from_slice(&e.tokens[..prefix]);
states.extend_from_slice(&e.states[..prefix]);
cur = e.states[prefix].clone();
}
let mut buf = String::new();
let mut line_idx = prefix;
while line_idx < n {
if line_idx >= n - suffix {
if let Some(i) = hit {
let e = &cache[i];
let j = (line_idx as isize + d) as usize;
if j < e.lines.len() && e.states[j] == cur {
tokens.extend_from_slice(&e.tokens[j..]);
states.extend_from_slice(&e.states[j..]);
break;
}
}
}
states.push(cur.clone());
let line = raw_lines[line_idx];
if line.chars().count() > MAX_HIGHLIGHT_LINE_CHARS {
tokens.push(Vec::new());
} else {
let (state, stack) = &mut cur;
tokens.push(tokenize_line(line, state, stack, &mut buf)?);
}
line_idx += 1;
}
if states.len() == n {
states.push(cur);
}
debug_assert_eq!(tokens.len(), n);
if raw_lines.is_empty() {
return Some(tokens);
}
let entry = CacheEntry {
syntax,
lines: raw_lines.iter().map(|l| (*l).to_owned()).collect(),
states,
tokens: tokens.clone(),
};
match hit {
Some(i) => {
cache[i] = entry;
cache[..=i].rotate_right(1);
}
None => {
cache.insert(0, entry);
cache.truncate(CACHE_ENTRIES);
}
}
Some(tokens)
})
}
pub fn highlight_block(language: Option<&str>, raw_lines: &[&str]) -> Vec<HighlightedLine> {
let Some(syntax) = lookup_syntax(language) else {
return Vec::new();
};
let bytes: usize = raw_lines.iter().map(|l| l.len() + 1).sum();
if bytes > MAX_HIGHLIGHT_SOURCE_BYTES {
return Vec::new();
}
match admit(syntax) {
Admission::Warm => {}
Admission::Queue => {
let request = WarmRequest {
syntax,
lines: raw_lines.iter().map(|l| (*l).to_owned()).collect(),
};
if WARM_TX.send(request).is_err() {
tracing::warn!("syntax-highlighting warm worker is unavailable");
}
return Vec::new();
}
Admission::Wait => return Vec::new(),
}
let parsed = {
let _expected = crate::terminal::ExpectedPanic::new();
catch_unwind(AssertUnwindSafe(|| tokenize_incremental(syntax, raw_lines)))
};
parsed.ok().flatten().unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
fn class_at(line: &HighlightedLine, col: usize) -> Option<TokenClass> {
line.iter()
.find(|t| t.range.contains(&col))
.map(|t| t.class)
}
fn text_at(src: &str, line: &HighlightedLine, col: usize) -> String {
line.iter()
.find(|t| t.range.contains(&col))
.map(|t| src.chars().take(t.range.end).skip(t.range.start).collect())
.unwrap_or_default()
}
fn clear_cache() {
CACHE.with(|c| c.borrow_mut().clear());
}
fn hl(language: &str, lines: &[&str]) -> Vec<HighlightedLine> {
assert!(warm_inline(Some(language)), "{language} should resolve");
highlight_block(Some(language), lines)
}
#[test]
fn info_string_keeps_only_the_language_token() {
assert_eq!(language_token("rust"), "rust");
assert_eq!(language_token("rust,ignore"), "rust");
assert_eq!(language_token("js {1,3-4}"), "js");
assert_eq!(language_token("python title=x"), "python");
assert_eq!(language_token(""), "");
}
#[test]
fn common_languages_and_aliases_resolve() {
for token in [
"rust",
"rs",
"python",
"py",
"javascript",
"js",
"json",
"yaml",
] {
assert!(
lookup_syntax(Some(token)).is_some(),
"{token} should resolve"
);
}
}
#[test]
fn two_face_supplies_the_languages_syntect_alone_lacks() {
for token in ["typescript", "ts", "dockerfile", "swift", "kotlin"] {
assert!(
lookup_syntax(Some(token)).is_some(),
"{token} should resolve"
);
}
}
#[test]
fn language_lookup_is_case_insensitive() {
assert!(lookup_syntax(Some("RUST")).is_some());
assert!(lookup_syntax(Some("Python")).is_some());
}
#[test]
fn unknown_and_absent_languages_resolve_to_nothing() {
assert!(lookup_syntax(None).is_none());
assert!(lookup_syntax(Some("")).is_none());
assert!(lookup_syntax(Some(" ")).is_none());
assert!(lookup_syntax(Some("not-a-real-language")).is_none());
}
#[test]
fn mermaid_has_no_grammar_so_that_surface_stays_out_of_scope() {
assert!(lookup_syntax(Some("mermaid")).is_none());
}
#[test]
fn rust_keywords_and_functions_are_classified() {
clear_cache();
let src = "fn main() {}";
let out = hl("rust", &[src]);
assert_eq!(out.len(), 1);
assert_eq!(class_at(&out[0], 0), Some(TokenClass::Keyword));
assert_eq!(text_at(src, &out[0], 0), "fn");
assert_eq!(class_at(&out[0], 3), Some(TokenClass::Function));
assert_eq!(text_at(src, &out[0], 3), "main");
}
#[test]
fn strings_and_comments_are_classified() {
clear_cache();
let src = r#"let s = "hi"; // note"#;
let out = hl("rust", &[src]);
let quote = src.find('"').unwrap();
let comment = src.find("//").unwrap();
assert_eq!(class_at(&out[0], quote), Some(TokenClass::String));
assert_eq!(class_at(&out[0], comment), Some(TokenClass::Comment));
}
#[test]
fn a_string_delimiter_takes_the_colour_of_the_literal_it_opens() {
clear_cache();
let src = r#""hi""#;
let out = hl("rust", &[src]);
assert_eq!(class_at(&out[0], 0), Some(TokenClass::String));
}
#[test]
fn numbers_are_classified() {
clear_cache();
let src = "x = 42";
let out = hl("python", &[src]);
assert_eq!(class_at(&out[0], 4), Some(TokenClass::Number));
}
#[test]
fn a_block_comment_spans_lines() {
clear_cache();
let lines = ["/* one", "still comment", "done */"];
let out = hl("rust", &lines);
for (i, line) in out.iter().enumerate() {
assert_eq!(
class_at(line, 1),
Some(TokenClass::Comment),
"line {i} should be inside the comment"
);
}
}
#[test]
fn ranges_are_char_indices_not_byte_offsets() {
clear_cache();
let src = r#"let s = "héllo";"#;
let out = hl("rust", &[src]);
let chars: Vec<char> = src.chars().collect();
for token in &out[0] {
assert!(
token.range.end <= chars.len(),
"range {:?} escapes the line's {} chars — byte offsets leaked",
token.range,
chars.len()
);
}
let quote = chars.iter().position(|c| *c == '"').unwrap();
assert_eq!(class_at(&out[0], quote), Some(TokenClass::String));
assert_eq!(text_at(src, &out[0], quote), r#""héllo""#);
}
#[test]
fn multibyte_content_keeps_tokens_ordered_and_disjoint() {
clear_cache();
let lines = [r#"// 🎉 párty"#, r#"let x = "日本語";"#];
let out = hl("rust", &lines);
for (i, line) in out.iter().enumerate() {
let chars = lines[i].chars().count();
let mut prev_end = 0;
for token in line {
assert!(token.range.start >= prev_end, "overlap on line {i}");
assert!(token.range.start < token.range.end, "empty run on line {i}");
assert!(token.range.end <= chars, "past end of line {i}");
prev_end = token.range.end;
}
}
}
#[test]
fn a_block_over_the_byte_cap_is_not_highlighted() {
clear_cache();
let line = "fn main() {}";
let count = MAX_HIGHLIGHT_SOURCE_BYTES / (line.len() + 1) + 2;
let lines = vec![line; count];
assert!(hl("rust", &lines).is_empty());
}
#[test]
fn a_block_just_under_the_byte_cap_is_still_highlighted() {
clear_cache();
let line = "fn main() {}";
let count = MAX_HIGHLIGHT_SOURCE_BYTES / (line.len() + 1) - 2;
let lines = vec![line; count];
assert!(!hl("rust", &lines).is_empty());
}
#[test]
fn an_over_long_line_is_skipped_but_its_neighbours_are_not() {
clear_cache();
let long = "x".repeat(MAX_HIGHLIGHT_LINE_CHARS + 1);
let lines = ["fn a() {}", long.as_str(), "fn b() {}"];
let out = hl("rust", &lines);
assert_eq!(out.len(), 3);
assert_eq!(class_at(&out[0], 0), Some(TokenClass::Keyword));
assert!(out[1].is_empty(), "the over-long line should not be parsed");
assert_eq!(class_at(&out[2], 0), Some(TokenClass::Keyword));
}
fn budget() -> GrammarBudget {
GrammarBudget {
warm: Vec::new(),
pending: Vec::new(),
budget: MAX_HIGHLIGHT_GRAMMARS,
last_refill: None,
refused: false,
}
}
const SEC: Duration = Duration::from_secs(1);
fn fill(b: &mut GrammarBudget, keys: std::ops::Range<usize>, t: Instant) {
for key in keys {
assert_eq!(
b.admit(key, t, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Queue,
"grammar {key} is inside the burst"
);
}
}
#[test]
fn the_grammar_burst_is_capped() {
let mut b = budget();
let t = Instant::now();
fill(&mut b, 0..MAX_HIGHLIGHT_GRAMMARS, t);
assert_eq!(
b.admit(999, t, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
assert_eq!(b.pending.len(), MAX_HIGHLIGHT_GRAMMARS);
}
#[test]
fn a_queued_grammar_is_never_queued_twice() {
let mut b = budget();
let t = Instant::now();
assert_eq!(b.admit(7, t, MAX_HIGHLIGHT_GRAMMARS, SEC), Admission::Queue);
for _ in 0..100 {
assert_eq!(b.admit(7, t, MAX_HIGHLIGHT_GRAMMARS, SEC), Admission::Wait);
}
assert_eq!(b.budget, MAX_HIGHLIGHT_GRAMMARS - 1, "one slot, not 101");
}
#[test]
fn a_warm_grammar_is_free_and_never_evicted() {
let mut b = budget();
let t = Instant::now();
assert_eq!(b.admit(7, t, MAX_HIGHLIGHT_GRAMMARS, SEC), Admission::Queue);
b.mark_warm(7);
assert!(b.pending.is_empty(), "warming clears the pending record");
fill(&mut b, 100..(100 + MAX_HIGHLIGHT_GRAMMARS - 1), t);
assert_eq!(
b.admit(999, t, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
assert_eq!(b.admit(7, t, MAX_HIGHLIGHT_GRAMMARS, SEC), Admission::Warm);
assert_eq!(
b.admit(998, t, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
}
#[test]
fn the_budget_refills_so_a_long_session_is_not_locked_out() {
let mut b = budget();
let t = Instant::now();
fill(&mut b, 0..MAX_HIGHLIGHT_GRAMMARS, t);
assert_eq!(
b.admit(999, t, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
assert_eq!(
b.admit(999, t + SEC, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Queue
);
assert_eq!(
b.admit(1000, t + SEC, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
assert_eq!(
b.admit(1000, t + SEC * 2, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Queue
);
}
#[test]
fn the_budget_saturates_at_the_cap() {
let mut b = budget();
let t = Instant::now();
fill(&mut b, 0..MAX_HIGHLIGHT_GRAMMARS, t);
let later = t + SEC * 10_000;
fill(&mut b, 100..(100 + MAX_HIGHLIGHT_GRAMMARS), later);
assert_eq!(
b.admit(999, later, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
}
#[test]
fn a_refusal_asks_for_one_reparse_per_refilled_slot() {
let mut b = budget();
let t = Instant::now();
assert!(!b.take_retry(t + SEC * 10, MAX_HIGHLIGHT_GRAMMARS, SEC));
fill(&mut b, 0..MAX_HIGHLIGHT_GRAMMARS, t);
assert_eq!(
b.admit(999, t, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
assert!(!b.take_retry(t, MAX_HIGHLIGHT_GRAMMARS, SEC));
let later = t + SEC;
assert!(b.take_retry(later, MAX_HIGHLIGHT_GRAMMARS, SEC));
assert!(!b.take_retry(later, MAX_HIGHLIGHT_GRAMMARS, SEC));
assert_eq!(
b.admit(999, later, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Queue
);
}
#[test]
fn a_granted_retry_moves_the_render_fingerprint() {
{
let mut grammars = GRAMMARS.lock().expect("budget lock");
grammars.refused = true;
grammars.budget = grammars.budget.max(1);
}
let before = retry_epoch();
assert!(refused_grammar_retry_due(), "a refusal with a free slot");
assert!(
retry_epoch() > before,
"a granted retry must invalidate the render cache, or the \
reparse it triggers cannot reach the highlighter"
);
}
#[test]
fn a_still_refused_grammar_earns_the_next_slot_too() {
let mut b = budget();
let t = Instant::now();
fill(&mut b, 0..MAX_HIGHLIGHT_GRAMMARS, t);
for key in [900, 901] {
assert_eq!(
b.admit(key, t, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
}
let mut queued = Vec::new();
for step in 1..=2 {
let now = t + SEC * step;
assert!(
b.take_retry(now, MAX_HIGHLIGHT_GRAMMARS, SEC),
"step {step}"
);
for key in [900, 901] {
if b.admit(key, now, MAX_HIGHLIGHT_GRAMMARS, SEC) == Admission::Queue {
queued.push(key);
}
}
}
assert_eq!(queued, vec![900, 901]);
}
#[test]
fn a_fractional_interval_is_not_thrown_away() {
let mut b = budget();
let t = Instant::now();
fill(&mut b, 0..MAX_HIGHLIGHT_GRAMMARS, t);
let ms = Duration::from_millis(300);
for step in 1..=3 {
assert_eq!(
b.admit(999, t + ms * step, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
}
assert_eq!(
b.admit(999, t + ms * 4, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Queue
);
assert_eq!(
b.admit(
1000,
t + Duration::from_millis(1_800),
MAX_HIGHLIGHT_GRAMMARS,
SEC
),
Admission::Wait
);
assert_eq!(
b.admit(1000, t + SEC * 2, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Queue
);
}
#[test]
fn a_cold_grammar_renders_plain_and_then_colours_once_warmed() {
clear_cache();
let lines = ["-- a comment", "local x = 1"];
let before = warm_generation();
assert!(
highlight_block(Some("lua"), &lines).is_empty(),
"a cold grammar must not block the render thread"
);
let deadline = Instant::now() + Duration::from_secs(10);
let mut out = highlight_block(Some("lua"), &lines);
while out.is_empty() && Instant::now() < deadline {
std::thread::yield_now();
out = highlight_block(Some("lua"), &lines);
}
assert!(
warm_generation() > before,
"the warm worker should have compiled the grammar"
);
assert_eq!(out.len(), 2);
assert_eq!(class_at(&out[0], 0), Some(TokenClass::Comment));
}
#[test]
fn the_warm_generation_only_moves_when_a_grammar_lands() {
assert!(lookup_syntax(Some("not-a-real-language")).is_none());
assert!(lookup_syntax(None).is_none());
assert!(highlight_block(Some("not-a-real-language"), &["x"]).is_empty());
assert!(highlight_block(None, &["x"]).is_empty());
let mut b = budget();
let t = Instant::now();
assert_eq!(b.admit(1, t, MAX_HIGHLIGHT_GRAMMARS, SEC), Admission::Queue);
assert_eq!(b.budget, MAX_HIGHLIGHT_GRAMMARS - 1);
}
#[test]
fn warm_inline_makes_a_grammar_usable_without_the_worker() {
clear_cache();
assert!(warm_inline(Some("rust")));
assert!(!warm_inline(Some("not-a-real-language")));
let out = highlight_block(Some("rust"), &["fn main() {}"]);
assert_eq!(class_at(&out[0], 0), Some(TokenClass::Keyword));
}
#[test]
fn a_refused_grammar_degrades_to_plain_like_an_unknown_one() {
clear_cache();
let mut b = budget();
let t = Instant::now();
fill(&mut b, 0..MAX_HIGHLIGHT_GRAMMARS, t);
assert_eq!(
b.admit(999, t, MAX_HIGHLIGHT_GRAMMARS, SEC),
Admission::Wait
);
assert!(highlight_block(Some("not-a-real-language"), &["x"]).is_empty());
}
#[test]
fn an_unknown_language_yields_nothing_rather_than_empty_lines() {
clear_cache();
assert!(highlight_block(Some("frobnicate"), &["some text"]).is_empty());
assert!(highlight_block(None, &["some text"]).is_empty());
}
fn assert_matches_cold(language: &str, lines: &[&str]) {
let warm = highlight_block(Some(language), lines);
clear_cache();
let cold = highlight_block(Some(language), lines);
assert_eq!(warm, cold, "incremental result diverged from a cold parse");
}
#[test]
fn editing_one_line_matches_a_cold_parse() {
clear_cache();
let before = ["fn a() {}", "let x = 1;", "fn b() {}"];
hl("rust", &before);
assert_matches_cold("rust", &["fn a() {}", "let x = 12;", "fn b() {}"]);
}
#[test]
fn inserting_a_line_matches_a_cold_parse() {
clear_cache();
hl("rust", &["fn a() {}", "fn b() {}"]);
assert_matches_cold("rust", &["fn a() {}", "let y = 2;", "fn b() {}"]);
}
#[test]
fn deleting_a_line_matches_a_cold_parse() {
clear_cache();
hl("rust", &["fn a() {}", "let y = 2;", "fn b() {}"]);
assert_matches_cold("rust", &["fn a() {}", "fn b() {}"]);
}
#[test]
fn appending_to_the_end_matches_a_cold_parse() {
clear_cache();
hl("rust", &["fn a() {}"]);
assert_matches_cold("rust", &["fn a() {}", "fn b() {}"]);
}
#[test]
fn opening_a_string_cascades_then_reconverges() {
clear_cache();
hl("rust", &["let a = 1;", "let b = 2;", "let c = 3;"]);
assert_matches_cold("rust", &[r#"let a = ";"#, "let b = 2;", "let c = 3;"]);
}
#[test]
fn a_block_comment_opened_mid_block_matches_a_cold_parse() {
clear_cache();
hl("rust", &["let a = 1;", "let b = 2;", "let c = 3;"]);
assert_matches_cold("rust", &["let a = 1;", "/* x", "let c = 3;"]);
}
#[test]
fn reuse_is_actually_happening() {
clear_cache();
let before = ["fn a() {}", "fn b() {}", "let x = 1;"];
let first = hl("rust", &before);
let after = ["fn a() {}", "fn b() {}", "let x = 2;"];
let second = hl("rust", &after);
assert_eq!(first[..2], second[..2]);
CACHE.with(|c| {
let cache = c.borrow();
let entry = cache.first().expect("an entry should be cached");
assert_eq!(entry.lines, after, "cache should hold the latest content");
assert_eq!(
entry.states.len(),
after.len() + 1,
"states carries one entry per line plus the tail"
);
});
}
#[test]
fn different_languages_do_not_evict_each_other() {
clear_cache();
hl("rust", &["fn a() {}"]);
hl("python", &["def a(): pass"]);
CACHE.with(|c| assert_eq!(c.borrow().len(), 2));
hl("rust", &["fn a() {}"]);
CACHE.with(|c| assert_eq!(c.borrow().len(), 2));
}
#[test]
fn the_cache_is_bounded() {
clear_cache();
for lang in ["rust", "python", "javascript", "json", "yaml", "go"] {
highlight_block(Some(lang), &["x"]);
}
CACHE.with(|c| assert!(c.borrow().len() <= CACHE_ENTRIES));
}
#[test]
#[ignore = "measurement, not an assertion; run manually"]
fn throughput() {
use std::time::Instant;
let cases: Vec<(&str, &str, Vec<String>)> = vec![
(
"minified js (one long line)",
"javascript",
vec!["var a=1;".repeat(400)],
),
(
"deep nesting",
"json",
vec![format!("{}1{}", "[".repeat(500), "]".repeat(500))],
),
(
"near-cap rust block",
"rust",
(0..4_000)
.map(|i| format!(" let x{i} = compute(\"value {i}\");"))
.collect(),
),
(
"unterminated string",
"rust",
(0..2_000).map(|i| format!("let s{i} = \"open;")).collect(),
),
];
for (name, lang, lines) in cases {
clear_cache();
let refs: Vec<&str> = lines.iter().map(String::as_str).collect();
let bytes: usize = refs.iter().map(|l| l.len() + 1).sum();
let t = Instant::now();
let out = highlight_block(Some(lang), &refs);
let cold = t.elapsed();
let mut edited = lines.clone();
if let Some(last) = edited.last_mut() {
last.push(' ');
}
let edited_refs: Vec<&str> = edited.iter().map(String::as_str).collect();
let t = Instant::now();
highlight_block(Some(lang), &edited_refs);
let warm = t.elapsed();
println!(
"{name}: {} lines / {bytes} bytes — cold {cold:?}, one-keystroke {warm:?}{}",
refs.len(),
if out.is_empty() {
" (over cap, not parsed)"
} else {
""
},
);
}
println!("\n-- single-line cost by length (minified JS) --");
for chars in [250usize, 500, 1_000, 2_000, 4_000, 8_000] {
let line = "var a=1;".repeat(chars / 8);
clear_cache();
let t = Instant::now();
hl("javascript", &[line.as_str()]);
println!(" {chars:>5} chars: {:?}", t.elapsed());
}
let big: Vec<String> = (0..1_500)
.map(|i| format!(" let x{i} = compute(\"value {i}\");"))
.collect();
let refs: Vec<&str> = big.iter().map(String::as_str).collect();
let bytes: usize = refs.iter().map(|l| l.len() + 1).sum();
clear_cache();
let t = Instant::now();
let out = hl("rust", &refs);
println!(
"\nlarge in-cap rust block: 1500 lines / {bytes} bytes — cold {:?} (parsed: {})",
t.elapsed(),
!out.is_empty()
);
}
}