use std::{ops::Range, sync::Arc};
use crate::types::ScopeStorage;
use crate::engine::checkpoint::CheckpointTable as EngineCheckpointTable;
use crate::{
Error, HighlightScopeTable, Result, ScopeStackId, TokenizerOptions,
engine::state::ScopeStackId as EngineScopeStackId,
engine::tokenizer::{
GrammarSet as EngineGrammarSet, PreparedLanguage as EnginePreparedLanguage,
SharedScopeSink, TextMateTokenizer, TokenizerState as EngineTokenizerState,
},
};
static NEXT_REGISTRY_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
static NEXT_TOKENIZER_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
struct ScopedTokenVecSink<'a> {
line: &'a str,
tokens: &'a mut Vec<Token>,
}
impl SharedScopeSink for ScopedTokenVecSink<'_> {
fn reserve(&mut self, token_count: usize) {
if self.tokens.capacity() == 0 {
*self.tokens = Vec::with_capacity(token_count);
} else if self.tokens.capacity() < token_count {
self.tokens.reserve(token_count);
}
}
fn push(&mut self, range: Range<usize>, stack: EngineScopeStackId, scopes: Arc<ScopeStorage>) {
if let Some(token) = scoped_token(self.line, range, stack, scopes) {
self.tokens.push(token);
}
}
}
struct ScopedTokenCallbackSink<'a, F> {
line: &'a str,
callback: F,
}
impl<F: FnMut(Token)> SharedScopeSink for ScopedTokenCallbackSink<'_, F> {
fn reserve(&mut self, _token_count: usize) {}
fn push(&mut self, range: Range<usize>, stack: EngineScopeStackId, scopes: Arc<ScopeStorage>) {
if let Some(token) = scoped_token(self.line, range, stack, scopes) {
(self.callback)(token);
}
}
}
fn scoped_token(
line: &str,
range: Range<usize>,
stack: EngineScopeStackId,
scopes: Arc<ScopeStorage>,
) -> Option<Token> {
let start = range.start.min(line.len());
let end = range.end.min(line.len());
(start < end && line.is_char_boundary(start) && line.is_char_boundary(end)).then_some(Token {
range: start..end,
scopes: TokenScopes {
owner: Some(scopes),
stack: ScopeStackId(stack.0),
},
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GrammarLimits {
pub max_grammar_bytes: usize,
pub max_grammars: usize,
}
impl Default for GrammarLimits {
fn default() -> Self {
Self {
max_grammar_bytes: 4 * 1024 * 1024,
max_grammars: 4_096,
}
}
}
#[derive(Debug, Clone)]
pub struct GrammarRegistry {
id: u64,
limits: GrammarLimits,
inner: EngineGrammarSet,
}
impl Default for GrammarRegistry {
fn default() -> Self {
Self::new()
}
}
impl GrammarRegistry {
pub fn new() -> Self {
Self::with_limits(GrammarLimits::default())
}
pub fn with_limits(mut limits: GrammarLimits) -> Self {
limits.max_grammars = limits.max_grammars.min(u16::MAX as usize);
Self {
id: NEXT_REGISTRY_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
limits,
inner: EngineGrammarSet::new(),
}
}
pub fn add_json(&mut self, json: &str) -> Result<GrammarId> {
if json.len() > self.limits.max_grammar_bytes {
return Err(Error::Grammar(crate::GrammarError::new(
None,
crate::GrammarErrorKind::LimitExceeded(crate::LimitExceeded::new(
crate::GrammarResource::GrammarBytes,
self.limits.max_grammar_bytes,
json.len(),
)),
)));
}
if self.inner.len() >= self.limits.max_grammars {
return Err(Error::Grammar(crate::GrammarError::new(
None,
crate::GrammarErrorKind::LimitExceeded(crate::LimitExceeded::new(
crate::GrammarResource::GrammarCount,
self.limits.max_grammars,
self.inner.len() + 1,
)),
)));
}
let id = self
.inner
.load_and_add(json)
.map_err(crate::error::grammar_load_error)?;
Ok(GrammarId {
registry: self.id,
inner: id,
})
}
pub fn validate_regexes(&self) -> Result<()> {
for grammar in self.inner.iter() {
for pattern in &grammar.patterns {
let parsed = crate::engine::regex::parse(pattern);
if let Some(position) = parsed.first_diagnostic_position {
return Err(Error::Grammar(crate::GrammarError::new(
Some(grammar.scope_name.clone()),
crate::GrammarErrorKind::InvalidRegex(crate::RegexError::new(
pattern.to_string(),
position,
parsed.diagnostics[0].clone(),
)),
)));
}
}
}
Ok(())
}
pub fn grammar_count(&self) -> usize {
self.inner.len()
}
pub fn validate(&self) -> Result<()> {
self.inner
.validate_include_graph()
.map_err(crate::error::grammar_validation_error)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GrammarId {
registry: u64,
inner: crate::engine::state::GrammarId,
}
fn preparation_limit_error(scope: Option<String>, detail: &str) -> Error {
Error::Grammar(crate::GrammarError::new(
scope,
crate::GrammarErrorKind::PreparationLimit(detail.to_owned()),
))
}
#[derive(Debug, Clone)]
pub struct PreparedLanguage {
inner: Arc<EnginePreparedLanguage>,
}
impl PreparedLanguage {
pub fn new(registry: &GrammarRegistry, root: GrammarId) -> Result<Self> {
if root.registry != registry.id || registry.inner.grammar(root.inner).is_none() {
return Err(Error::Grammar(crate::GrammarError::new(
None,
crate::GrammarErrorKind::ForeignGrammarId,
)));
}
let inner = EnginePreparedLanguage::try_new(registry.inner.clone(), root.inner).map_err(
|detail| {
preparation_limit_error(
registry
.inner
.grammar(root.inner)
.map(|grammar| grammar.scope_name.clone()),
detail,
)
},
)?;
Ok(Self {
inner: Arc::new(inner),
})
}
#[cfg(feature = "bundled-grammars")]
pub fn for_bundled_language(language: &str) -> Result<Self> {
Self::from_catalog(&crate::Catalog::bundled(), language)
}
pub fn from_catalog(catalog: &crate::Catalog, language: &str) -> Result<Self> {
let canonical = catalog
.canonical_language(language)
.ok_or_else(|| Error::UnknownLanguage(language.to_owned()))?;
let (grammars, root) =
crate::engine::load_catalog_grammar_set(catalog.bundle(), canonical)?;
let scope = grammars
.grammar(root)
.map(|grammar| grammar.scope_name.clone());
let inner = EnginePreparedLanguage::try_new(grammars, root)
.map_err(|detail| preparation_limit_error(scope, detail))?;
Ok(Self {
inner: Arc::new(inner),
})
}
pub(crate) fn for_highlighter(catalog: &crate::Catalog, canonical: &str) -> Result<Self> {
let (grammars, root) =
crate::engine::load_catalog_grammar_set(catalog.bundle(), canonical)?;
Ok(Self {
inner: Arc::new(EnginePreparedLanguage::from_catalog(grammars, root)),
})
}
pub(crate) fn first_tokenizer(&self, options: TokenizerOptions) -> Tokenizer {
Tokenizer::from_engine(self.inner.first_tokenizer(), options)
}
pub fn tokenizer(&self, options: TokenizerOptions) -> Tokenizer {
Tokenizer::from_prepared(self, options)
}
pub fn stats(&self) -> PreparedLanguageStats {
PreparedLanguageStats {
grammar_count: self.inner.grammar_count(),
static_pattern_capacity: self.inner.static_pattern_capacity(),
compiled_pattern_count: self.inner.compiled_pattern_count(),
static_pattern_byte_capacity: self.inner.static_pattern_byte_capacity(),
static_pattern_retained_bytes: self.inner.static_pattern_retained_bytes(),
static_candidate_capacity: self.inner.static_blueprint_capacity(),
static_candidate_count: self.inner.static_blueprint_count(),
static_candidate_byte_capacity: self.inner.static_blueprint_byte_capacity(),
static_candidate_retained_bytes: self.inner.static_blueprint_retained_bytes(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PreparedLanguageStats {
grammar_count: usize,
static_pattern_capacity: usize,
compiled_pattern_count: usize,
static_pattern_byte_capacity: usize,
static_pattern_retained_bytes: usize,
static_candidate_capacity: usize,
static_candidate_count: usize,
static_candidate_byte_capacity: usize,
static_candidate_retained_bytes: usize,
}
impl PreparedLanguageStats {
pub fn grammar_count(&self) -> usize {
self.grammar_count
}
pub fn static_pattern_capacity(&self) -> usize {
self.static_pattern_capacity
}
pub fn compiled_pattern_count(&self) -> usize {
self.compiled_pattern_count
}
pub fn static_pattern_byte_capacity(&self) -> usize {
self.static_pattern_byte_capacity
}
pub fn static_pattern_retained_bytes(&self) -> usize {
self.static_pattern_retained_bytes
}
pub fn static_candidate_capacity(&self) -> usize {
self.static_candidate_capacity
}
pub fn static_candidate_count(&self) -> usize {
self.static_candidate_count
}
pub fn static_candidate_byte_capacity(&self) -> usize {
self.static_candidate_byte_capacity
}
pub fn static_candidate_retained_bytes(&self) -> usize {
self.static_candidate_retained_bytes
}
}
#[derive(Debug)]
pub struct Tokenizer {
id: u64,
inner: TextMateTokenizer,
parse_line_buffer: String,
}
impl Tokenizer {
pub fn new(
registry: &GrammarRegistry,
root: GrammarId,
options: TokenizerOptions,
) -> Result<Self> {
if root.registry != registry.id || registry.inner.grammar(root.inner).is_none() {
return Err(Error::Grammar(crate::GrammarError::new(
None,
crate::GrammarErrorKind::ForeignGrammarId,
)));
}
Ok(Self::from_engine(
TextMateTokenizer::new(registry.inner.clone(), root.inner),
options,
))
}
#[cfg(feature = "bundled-grammars")]
pub fn for_bundled_language(language: &str, options: TokenizerOptions) -> Result<Self> {
let canonical = crate::grammars::canonical_language(language)
.ok_or_else(|| Error::UnknownLanguage(language.to_owned()))?;
let (grammars, root) = crate::engine::load_grammar_set(&canonical)?;
Ok(Self::from_engine(
TextMateTokenizer::new(grammars, root),
options,
))
}
pub fn from_prepared(prepared: &PreparedLanguage, options: TokenizerOptions) -> Self {
Self::from_engine(prepared.inner.tokenizer(), options)
}
fn from_engine(mut inner: TextMateTokenizer, options: TokenizerOptions) -> Self {
inner.configure_options(options);
Self {
id: NEXT_TOKENIZER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
inner,
parse_line_buffer: String::new(),
}
}
pub fn initial_state(&self) -> TokenizerState {
TokenizerState {
owner: self.id,
inner: EngineTokenizerState::default(),
at_document_start: true,
}
}
pub fn tokenize_line(
&mut self,
line: &str,
state: &mut TokenizerState,
) -> Result<TokenizedLine> {
let mut tokens = Vec::new();
let status = self.tokenize_line_into(line, state, &mut tokens)?;
Ok(TokenizedLine { tokens, status })
}
pub fn tokenize_line_into(
&mut self,
line: &str,
state: &mut TokenizerState,
tokens: &mut Vec<Token>,
) -> Result<HighlightStatus> {
self.validate_line(line, state)?;
tokens.clear();
let mut sink = ScopedTokenVecSink { line, tokens };
Ok(self.tokenize_line_with_validated(line, state, &mut sink))
}
pub fn tokenize_line_with(
&mut self,
line: &str,
state: &mut TokenizerState,
sink: impl FnMut(Token),
) -> Result<HighlightStatus> {
self.validate_line(line, state)?;
let mut sink = ScopedTokenCallbackSink {
line,
callback: sink,
};
Ok(self.tokenize_line_with_validated(line, state, &mut sink))
}
pub(crate) fn tokenize_line_shared_with(
&mut self,
line: &str,
state: &mut TokenizerState,
sink: &mut impl SharedScopeSink,
) -> Result<HighlightStatus> {
self.validate_line(line, state)?;
Ok(self.tokenize_line_shared_with_validated(line, state, sink))
}
pub(crate) fn tokenize_line_shared_with_validated(
&mut self,
line: &str,
state: &mut TokenizerState,
sink: &mut impl SharedScopeSink,
) -> HighlightStatus {
self.tokenize_line_with_validated(line, state, sink)
}
pub(crate) fn validate_line(&self, line: &str, state: &TokenizerState) -> Result<()> {
if state.owner != self.id {
return Err(Error::StateMismatch);
}
if line.contains('\n') {
return Err(Error::InvalidLine);
}
Ok(())
}
fn tokenize_line_with_validated(
&mut self,
line: &str,
state: &mut TokenizerState,
sink: &mut impl SharedScopeSink,
) -> HighlightStatus {
let line_index = state.anchor_line_index();
let next_state = if self
.inner
.max_line_bytes()
.is_some_and(|max_line_bytes| line.len() >= max_line_bytes)
{
self.inner.tokenize_line_shared_scopes_skipped_with(
line,
state.inner.clone(),
line_index,
sink,
)
} else {
self.parse_line_buffer.clear();
self.parse_line_buffer.push_str(line);
self.parse_line_buffer.push('\n');
self.inner.tokenize_line_shared_scopes_with(
&self.parse_line_buffer,
state.inner.clone(),
line_index,
sink,
)
};
state.finish_line(next_state);
self.take_status()
}
pub fn tokenize(&mut self, source: &str) -> TokenizedDocument {
let lines = self.inner.tokenize_source_output(source);
TokenizedDocument {
lines,
status: self.take_status(),
}
}
#[cfg(all(feature = "bundled-themes", any(feature = "html", feature = "ansi")))]
pub(crate) fn tokenize_compact(
&mut self,
source: &str,
) -> (crate::HighlightedText, HighlightStatus) {
let highlighted = self.inner.tokenize_source(source);
(highlighted, self.take_status())
}
pub fn checkpoints(&self, interval: usize) -> CheckpointTable {
CheckpointTable {
owner: self.id,
inner: EngineCheckpointTable::new(interval),
}
}
pub fn tokenize_viewport(
&mut self,
source: &str,
visible_lines: Range<usize>,
checkpoints: &mut CheckpointTable,
) -> Result<TokenizedDocument> {
if checkpoints.owner != self.id {
return Err(Error::StateMismatch);
}
let lines =
self.inner
.highlight_viewport_output(source, visible_lines, &mut checkpoints.inner);
Ok(TokenizedDocument {
lines,
status: self.take_status(),
})
}
fn take_status(&mut self) -> HighlightStatus {
if self.inner.take_degraded() {
HighlightStatus::Degraded
} else {
HighlightStatus::Complete
}
}
#[cfg(feature = "diagnostics")]
pub fn set_diagnostics_enabled(&mut self, enabled: bool) {
self.inner.set_counters_enabled(enabled);
}
#[cfg(feature = "diagnostics")]
pub fn take_diagnostics(&mut self) -> crate::diagnostics::EngineCounters {
self.inner.take_counters()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TokenizerState {
owner: u64,
inner: EngineTokenizerState,
at_document_start: bool,
}
impl TokenizerState {
pub fn is_initial(&self) -> bool {
self.inner.is_initial()
}
pub fn depth(&self) -> usize {
self.inner.depth()
}
fn anchor_line_index(&self) -> usize {
usize::from(!self.at_document_start)
}
fn finish_line(&mut self, inner: EngineTokenizerState) {
self.inner = inner;
self.at_document_start = false;
}
}
#[derive(Debug, Clone)]
pub struct CheckpointTable {
owner: u64,
inner: EngineCheckpointTable,
}
impl CheckpointTable {
pub fn interval(&self) -> usize {
self.inner.interval()
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn invalidate_from(&mut self, line_index: usize) {
self.inner.invalidate_from(line_index);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HighlightStatus {
Complete,
Degraded,
}
impl HighlightStatus {
pub fn is_complete(self) -> bool {
self == Self::Complete
}
}
#[derive(Debug, Clone)]
pub(crate) struct TokenScopes {
pub(crate) owner: Option<Arc<ScopeStorage>>,
pub(crate) stack: ScopeStackId,
}
impl TokenScopes {
pub(crate) fn view(&self) -> Scopes<'_> {
Scopes {
storage: self,
position: 0,
}
}
}
impl PartialEq for TokenScopes {
fn eq(&self, other: &Self) -> bool {
self.view().eq(other.view())
}
}
impl Eq for TokenScopes {}
#[derive(Debug, Clone)]
pub struct Scopes<'a> {
pub(crate) storage: &'a TokenScopes,
position: usize,
}
impl<'a> Scopes<'a> {
pub(crate) fn name(&self, index: usize) -> Option<&'a str> {
let index = self.position + index;
match self
.storage
.owner
.as_deref()
.expect("finished scope storage")
{
ScopeStorage::Table(table) => table
.stack(self.storage.stack)
.and_then(|atoms| atoms.get(index))
.and_then(|atom| table.atom(*atom)),
ScopeStorage::Shared(scopes) => scopes.get(index).map(AsRef::as_ref),
}
}
}
impl<'a> Iterator for Scopes<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let name = self.name(0)?;
self.position += 1;
Some(name)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl ExactSizeIterator for Scopes<'_> {
fn len(&self) -> usize {
let total = match self
.storage
.owner
.as_deref()
.expect("finished scope storage")
{
ScopeStorage::Table(table) => table.stack(self.storage.stack).unwrap_or_default().len(),
ScopeStorage::Shared(scopes) => scopes.len(),
};
total - self.position
}
}
impl std::iter::FusedIterator for Scopes<'_> {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token {
pub(crate) range: Range<usize>,
pub(crate) scopes: TokenScopes,
}
impl Token {
pub fn range(&self) -> Range<usize> {
self.range.clone()
}
pub fn scopes(&self) -> Scopes<'_> {
self.scopes.view()
}
pub fn scope_stack(&self) -> Option<ScopeStackId> {
Some(self.scopes.stack)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenizedLine {
pub(crate) tokens: Vec<Token>,
pub(crate) status: HighlightStatus,
}
impl TokenizedLine {
pub fn tokens(&self) -> &[Token] {
&self.tokens
}
pub fn status(&self) -> HighlightStatus {
self.status
}
}
impl crate::engine::tokenizer::DocumentOutputLine for TokenizedLine {
type ScopeOwner = Arc<ScopeStorage>;
fn scope_owner(scopes: Arc<HighlightScopeTable>) -> Self::ScopeOwner {
Arc::new(ScopeStorage::Table(scopes))
}
fn new(_: crate::LineTextFingerprint, capacity: usize, degraded: bool) -> Self {
Self {
tokens: Vec::with_capacity(capacity),
status: if degraded {
HighlightStatus::Degraded
} else {
HighlightStatus::Complete
},
}
}
fn push(&mut self, range: Range<usize>, _: Option<crate::SyntaxClass>, stack: ScopeStackId) {
if let Some(last) = self.tokens.last_mut()
&& last.scope_stack() == Some(stack)
&& last.range.end == range.start
{
last.range.end = range.end;
return;
}
self.tokens.push(Token {
range,
scopes: TokenScopes { owner: None, stack },
});
}
fn finish(&mut self, scopes: &Self::ScopeOwner) {
for token in &mut self.tokens {
token.scopes.owner = Some(Arc::clone(scopes));
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenizedDocument {
pub(crate) lines: Vec<TokenizedLine>,
status: HighlightStatus,
}
impl TokenizedDocument {
pub fn lines(&self) -> &[TokenizedLine] {
&self.lines
}
pub fn status(&self) -> HighlightStatus {
self.status
}
}
#[cfg(test)]
mod tests {
use super::*;
fn equality_tokenizer(grammar: &str, line_cache_entries: usize) -> Tokenizer {
let mut registry = GrammarRegistry::new();
let root = registry.add_json(grammar).unwrap();
Tokenizer::new(
®istry,
root,
TokenizerOptions {
line_cache_entries,
..TokenizerOptions::default()
},
)
.unwrap()
}
fn state_hash(state: &TokenizerState) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
state.hash(&mut hasher);
hasher.finish()
}
#[test]
fn continuation_equality_includes_owner_and_document_start() {
let grammar = r#"{"scopeName":"source.test","patterns":[
{"match":"\\Afirst","name":"keyword.first"}
]}"#;
let mut tokenizer = equality_tokenizer(grammar, 0);
let other = equality_tokenizer(grammar, 0);
let initial = tokenizer.initial_state();
assert_eq!(initial, initial.clone());
assert_ne!(initial, other.initial_state());
let mut later = initial.clone();
tokenizer.tokenize_line("", &mut later).unwrap();
assert!(initial.is_initial() && later.is_initial());
assert_ne!(initial, later);
let mut first = initial.clone();
assert_ne!(
tokenizer.tokenize_line("first", &mut first).unwrap(),
tokenizer.tokenize_line("first", &mut later).unwrap()
);
assert_eq!(first, later);
assert_eq!(state_hash(&first), state_hash(&later));
}
#[test]
fn continuation_equality_tracks_dynamic_end_and_while_delimiters() {
for condition in ["end", "while"] {
let grammar = format!(
r#"{{"scopeName":"source.test","patterns":[{{
"begin":"^<<(.+)","{condition}":"^\\1$","name":"string.test"
}}]}}"#
);
for cache_entries in [0, 1] {
let mut tokenizer = equality_tokenizer(&grammar, cache_entries);
let mut first = tokenizer.initial_state();
let mut same = tokenizer.initial_state();
let mut different = tokenizer.initial_state();
tokenizer.tokenize_line("<<A.*[", &mut first).unwrap();
tokenizer.tokenize_line("<<B", &mut different).unwrap();
tokenizer.tokenize_line("<<A.*[", &mut same).unwrap();
assert_eq!(first.depth(), 1);
assert_eq!(first, same);
assert_eq!(state_hash(&first), state_hash(&same));
assert_ne!(first, different, "{condition}");
let a = tokenizer.tokenize_line("A.*[", &mut first).unwrap();
let b = tokenizer.tokenize_line("A.*[", &mut same).unwrap();
tokenizer.tokenize_line("A.*[", &mut different).unwrap();
assert_eq!(a, b);
assert_eq!(first, same);
assert_eq!(first.is_initial(), condition == "end");
assert_eq!(different.is_initial(), condition == "while");
assert_ne!(first, different);
assert_ne!(
tokenizer.tokenize_line("A.*[", &mut first).unwrap(),
tokenizer.tokenize_line("A.*[", &mut different).unwrap(),
"the captured delimiter is literal, not regex"
);
}
}
}
#[test]
fn continuation_equality_tracks_captured_scopes_and_injections() {
for scope_field in ["name", "contentName"] {
let grammar = format!(
r#"{{"scopeName":"source.test","patterns":[{{
"begin":"<(a|b)>","end":"!","{scope_field}":"meta.$1"
}}],"injections":{{"L:meta.a":{{"patterns":[{{
"match":"word","name":"keyword.injected"
}}]}}}}}}"#
);
let mut tokenizer = equality_tokenizer(&grammar, 0);
let mut a = tokenizer.initial_state();
let mut b = tokenizer.initial_state();
tokenizer.tokenize_line("<a>", &mut a).unwrap();
tokenizer.tokenize_line("<b>", &mut b).unwrap();
assert_ne!(a, b, "{scope_field}");
let a_line = tokenizer.tokenize_line("word", &mut a).unwrap();
let b_line = tokenizer.tokenize_line("word", &mut b).unwrap();
assert!(
a_line
.tokens()
.iter()
.any(|token| { token.scopes().any(|scope| scope == "keyword.injected") })
);
assert!(
b_line
.tokens()
.iter()
.all(|token| { token.scopes().all(|scope| scope != "keyword.injected") })
);
tokenizer.tokenize_line("!", &mut a).unwrap();
tokenizer.tokenize_line("!", &mut b).unwrap();
assert_eq!(a, b);
}
}
#[test]
fn edited_comment_converges_when_original_comment_closes() {
let mut tokenizer = equality_tokenizer(
r#"{"scopeName":"source.test","patterns":[
{"begin":"/\\*","end":"\\*/","name":"comment.block"}
]}"#,
0,
);
let lines = ["/* open", "body", "*/", "suffix"];
let mut old_state = tokenizer.initial_state();
let mut old_states = Vec::new();
let mut old_tokens = Vec::new();
for line in lines {
old_tokens.push(tokenizer.tokenize_line(line, &mut old_state).unwrap());
old_states.push(old_state.clone());
}
let mut edited = old_states[0].clone();
tokenizer.tokenize_line("body */", &mut edited).unwrap();
assert_ne!(edited, old_states[1]);
let closing = tokenizer.tokenize_line(lines[2], &mut edited).unwrap();
assert_ne!(
closing, old_tokens[2],
"replace tokens even on the convergence line"
);
assert_eq!(edited, old_states[2]);
assert_eq!(
tokenizer.tokenize_line(lines[3], &mut edited).unwrap(),
old_tokens[3]
);
assert_eq!(edited, old_states[3]);
assert_eq!(state_hash(&edited), state_hash(&old_states[3]));
}
#[test]
fn prepared_language_creates_independent_equivalent_tokenizers() {
let mut registry = GrammarRegistry::new();
let root = registry
.add_json(
r#"{"scopeName":"source.test","patterns":[{"match":"true","name":"constant.language.test"}]}"#,
)
.unwrap();
let prepared = PreparedLanguage::new(®istry, root).unwrap();
let initial_stats = prepared.stats();
assert_eq!(initial_stats.grammar_count(), 1);
assert_eq!(initial_stats.static_pattern_capacity(), 1);
assert_eq!(initial_stats.compiled_pattern_count(), 1);
assert!(initial_stats.static_pattern_retained_bytes() > 0);
assert!(
initial_stats.static_pattern_retained_bytes()
<= initial_stats.static_pattern_byte_capacity()
);
assert_eq!(initial_stats.static_candidate_capacity(), 1_024);
assert_eq!(initial_stats.static_candidate_count(), 1);
assert!(
initial_stats.static_candidate_retained_bytes()
<= initial_stats.static_candidate_byte_capacity()
);
registry
.add_json(r#"{"scopeName":"source.other","patterns":[]}"#)
.unwrap();
assert_eq!(prepared.stats().grammar_count(), 1);
let mut first = prepared.tokenizer(TokenizerOptions::default());
let mut second = Tokenizer::from_prepared(&prepared, TokenizerOptions::default());
assert_eq!(first.tokenize("true false"), second.tokenize("true false"));
let mut first_state = first.initial_state();
assert_ne!(first_state, second.initial_state());
assert_eq!(
second.tokenize_line("true", &mut first_state),
Err(Error::StateMismatch)
);
}
#[test]
fn prepared_language_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<PreparedLanguage>();
}
#[test]
fn prepared_language_handles_concurrent_first_use() {
let mut registry = GrammarRegistry::new();
let root = registry
.add_json(
r#"{
"scopeName":"source.concurrent-prepared",
"patterns":[{
"begin":"\"",
"end":"\"",
"name":"string.concurrent-prepared",
"patterns":[{"match":"[a-z]+","name":"word.concurrent-prepared"}]
}]
}"#,
)
.unwrap();
let prepared = PreparedLanguage::new(®istry, root).unwrap();
let barrier = Arc::new(std::sync::Barrier::new(4));
let outputs = std::thread::scope(|scope| {
(0..4)
.map(|_| {
let prepared = prepared.clone();
let barrier = Arc::clone(&barrier);
scope.spawn(move || {
let mut tokenizer = prepared.tokenizer(TokenizerOptions::default());
barrier.wait();
tokenizer.tokenize("\"word\"")
})
})
.collect::<Vec<_>>()
.into_iter()
.map(|thread| thread.join().unwrap())
.collect::<Vec<_>>()
});
assert!(outputs.windows(2).all(|pair| pair[0] == pair[1]));
let stats = prepared.stats();
assert!(stats.compiled_pattern_count() <= stats.static_pattern_capacity());
assert!(stats.static_pattern_retained_bytes() <= stats.static_pattern_byte_capacity());
assert!(stats.static_candidate_count() <= stats.static_candidate_capacity());
assert!(stats.static_candidate_retained_bytes() <= stats.static_candidate_byte_capacity());
}
#[test]
fn reusable_and_callback_line_apis_match_owned_output() {
let mut registry = GrammarRegistry::new();
let root = registry
.add_json(
r#"{
"scopeName":"source.sink-test",
"patterns":[{
"begin":"\"",
"end":"\"",
"name":"string.sink-test"
},{
"match":"\\btrue\\b",
"name":"constant.sink-test"
}]
}"#,
)
.unwrap();
let mut owned = Tokenizer::new(®istry, root, TokenizerOptions::default()).unwrap();
let mut reusable = Tokenizer::new(®istry, root, TokenizerOptions::default()).unwrap();
let mut callback = Tokenizer::new(®istry, root, TokenizerOptions::default()).unwrap();
let mut owned_state = owned.initial_state();
let mut reusable_state = reusable.initial_state();
let mut callback_state = callback.initial_state();
let mut buffer = Vec::new();
for line in ["true \"open", "inside\" true", "plain"] {
let expected = owned.tokenize_line(line, &mut owned_state).unwrap();
let status = reusable
.tokenize_line_into(line, &mut reusable_state, &mut buffer)
.unwrap();
let mut emitted = Vec::new();
let callback_status = callback
.tokenize_line_with(line, &mut callback_state, |token| emitted.push(token))
.unwrap();
assert_eq!(status, expected.status());
assert_eq!(callback_status, expected.status());
assert_eq!(buffer, expected.tokens());
assert_eq!(emitted, expected.tokens());
}
let capacity = buffer.capacity();
let snapshot = buffer.clone();
assert_eq!(
reusable
.tokenize_line_into("invalid\nline", &mut reusable_state, &mut buffer)
.unwrap_err(),
Error::InvalidLine
);
assert_eq!(
buffer, snapshot,
"validation errors leave the sink untouched"
);
assert_eq!(buffer.capacity(), capacity);
}
#[test]
fn rejected_incremental_lines_do_not_grow_parse_buffer() {
let mut registry = GrammarRegistry::new();
let root = registry
.add_json(r#"{"scopeName":"source.test","patterns":[]}"#)
.unwrap();
let options = TokenizerOptions {
max_line_bytes: 8,
..TokenizerOptions::default()
};
let mut tokenizer = Tokenizer::new(®istry, root, options).unwrap();
let mut state = tokenizer.initial_state();
let initial_capacity = tokenizer.parse_line_buffer.capacity();
for line in ["x".repeat(options.max_line_bytes), "x".repeat(64 * 1024)] {
let tokenized = tokenizer.tokenize_line(&line, &mut state).unwrap();
assert_eq!(tokenized.status(), HighlightStatus::Degraded);
assert!(state.is_initial());
assert_eq!(tokenizer.parse_line_buffer.capacity(), initial_capacity);
assert_eq!(tokenized.tokens()[0].range(), 0..line.len());
}
}
fn token_scopes(tokens: &[Token]) -> Vec<(std::ops::Range<usize>, Vec<String>)> {
tokens
.iter()
.map(|token| (token.range(), token.scopes().map(str::to_owned).collect()))
.collect()
}
fn document_line_scopes(line: &TokenizedLine) -> Vec<(std::ops::Range<usize>, Vec<String>)> {
line.tokens()
.iter()
.map(|span| (span.range(), span.scopes().map(str::to_owned).collect()))
.collect()
}
#[test]
fn incremental_text_start_anchor_matches_only_document_start() {
let grammar = r#"{
"scopeName": "source.seed",
"patterns": [
{"match": "\\A(let|fn)\\b", "name": "keyword.anchor"}
]
}"#;
let mut registry = GrammarRegistry::new();
let root = registry.add_json(grammar).unwrap();
let options = TokenizerOptions::default();
let mut complete = Tokenizer::new(®istry, root, options).unwrap();
let document = complete.tokenize("fn foo\nfn bar\n");
assert!(
document_line_scopes(&document.lines()[0])
.iter()
.any(|(_, scopes)| scopes.iter().any(|scope| scope == "keyword.anchor")),
"{:#?}",
document_line_scopes(&document.lines()[0])
);
assert!(
document_line_scopes(&document.lines()[1])
.iter()
.all(|(_, scopes)| scopes.iter().all(|scope| scope != "keyword.anchor")),
"{:#?}",
document_line_scopes(&document.lines()[1])
);
let mut incremental = Tokenizer::new(®istry, root, options).unwrap();
let mut state = incremental.initial_state();
let first = incremental.tokenize_line("fn foo", &mut state).unwrap();
let second = incremental.tokenize_line("fn bar", &mut state).unwrap();
assert_eq!(
token_scopes(first.tokens()),
document_line_scopes(&document.lines()[0])
);
assert_eq!(
token_scopes(second.tokens()),
document_line_scopes(&document.lines()[1])
);
let mut replay = Tokenizer::new(®istry, root, options).unwrap();
let mut replay_state = replay.initial_state();
assert_eq!(
replay
.tokenize_line("bad\nline", &mut replay_state)
.unwrap_err(),
Error::InvalidLine
);
let after_reject = replay.tokenize_line("fn foo", &mut replay_state).unwrap();
assert_eq!(
token_scopes(after_reject.tokens()),
token_scopes(first.tokens())
);
let mut skipped = Tokenizer::new(
®istry,
root,
TokenizerOptions {
max_line_bytes: 8,
..TokenizerOptions::default()
},
)
.unwrap();
let mut skipped_state = skipped.initial_state();
let long = skipped
.tokenize_line("too long!", &mut skipped_state)
.unwrap();
assert_eq!(long.status(), HighlightStatus::Degraded);
let after_skip = skipped.tokenize_line("fn x", &mut skipped_state).unwrap();
assert!(
token_scopes(after_skip.tokens())
.iter()
.all(|(_, scopes)| scopes.iter().all(|scope| scope != "keyword.anchor")),
"{:#?}",
token_scopes(after_skip.tokens())
);
}
}