use std::{ops::Range, sync::Arc};
use crate::engine::checkpoint::CheckpointTable as EngineCheckpointTable;
use crate::{
Error, HighlightScopeTable, Result, ScopeStackRef, TokenizerOptions,
engine::tokenizer::{
GrammarSet as EngineGrammarSet, 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);
#[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(format!(
"grammar is {} bytes, exceeding the {} byte limit",
json.len(),
self.limits.max_grammar_bytes
)));
}
if self.inner.grammars().len() >= self.limits.max_grammars {
return Err(Error::Grammar(format!(
"grammar registry reached its {} grammar limit",
self.limits.max_grammars
)));
}
let id = self
.inner
.load_and_add(json)
.map_err(|error| Error::Grammar(error.to_string()))?;
Ok(GrammarId {
registry: self.id,
inner: id,
})
}
pub fn grammar_count(&self) -> usize {
self.inner.grammars().len()
}
pub fn validate(&self) -> Result<()> {
self.inner
.validate_include_graph()
.map_err(|error| Error::Grammar(error.to_string()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GrammarId {
registry: u64,
inner: crate::engine::state::GrammarId,
}
#[derive(Debug)]
pub struct Tokenizer {
id: u64,
inner: TextMateTokenizer,
}
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(
"root grammar does not belong to this registry".to_owned(),
));
}
let mut inner = TextMateTokenizer::new(registry.inner.clone(), root.inner);
inner.configure_options(options);
Ok(Self {
id: NEXT_TOKENIZER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
inner,
})
}
#[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)?;
let mut inner = TextMateTokenizer::new(grammars, root);
inner.configure_options(options);
Ok(Self {
id: NEXT_TOKENIZER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
inner,
})
}
pub fn initial_state(&self) -> TokenizerState {
TokenizerState {
owner: self.id,
inner: EngineTokenizerState::default(),
}
}
pub fn tokenize_line(
&mut self,
line: &str,
state: &mut TokenizerState,
) -> Result<TokenizedLine> {
if state.owner != self.id {
return Err(Error::StateMismatch);
}
if line.contains('\n') {
return Err(Error::InvalidLine);
}
let parse_text = format!("{line}\n");
let tokenized = self
.inner
.tokenize_line_scopes(&parse_text, state.inner.clone());
state.inner = tokenized.state;
let tokens = tokenized
.tokens
.iter()
.filter_map(|token| {
let start = token.range.start.min(line.len());
let end = token.range.end.min(line.len());
(start < end && line.is_char_boundary(start) && line.is_char_boundary(end)).then(
|| ScopedToken {
range: start..end,
scopes: token.scopes.clone(),
},
)
})
.collect();
let status = if self.inner.take_degraded() {
HighlightStatus::Degraded
} else {
HighlightStatus::Complete
};
Ok(TokenizedLine { tokens, status })
}
pub fn tokenize(&mut self, source: &str) -> TokenizedDocument {
let highlighted = self.inner.tokenize_source(source);
self.finish_document(highlighted)
}
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 highlighted =
self.inner
.highlight_viewport(source, visible_lines, &mut checkpoints.inner);
Ok(self.finish_document(highlighted))
}
fn finish_document(&mut self, highlighted: crate::HighlightedText) -> TokenizedDocument {
let status = if self.inner.take_degraded() {
HighlightStatus::Degraded
} else {
HighlightStatus::Complete
};
let lines = highlighted
.lines
.into_iter()
.map(|line| DocumentLine {
spans: line
.segments
.into_iter()
.map(|segment| TokenSpan {
range: segment.byte_start..segment.byte_end,
scope_stack: segment.scope_stack,
})
.collect(),
scopes: line.scope_table,
})
.collect();
TokenizedDocument { lines, status }
}
#[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)]
pub struct TokenizerState {
owner: u64,
inner: EngineTokenizerState,
}
impl TokenizerState {
pub fn is_initial(&self) -> bool {
self.inner.is_initial()
}
pub fn depth(&self) -> usize {
self.inner.depth()
}
}
#[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, PartialEq, Eq)]
pub struct ScopedToken {
range: Range<usize>,
scopes: Vec<String>,
}
impl ScopedToken {
pub fn range(&self) -> Range<usize> {
self.range.clone()
}
pub fn scopes(&self) -> impl ExactSizeIterator<Item = &str> {
self.scopes.iter().map(String::as_str)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenizedLine {
tokens: Vec<ScopedToken>,
status: HighlightStatus,
}
impl TokenizedLine {
pub fn tokens(&self) -> &[ScopedToken] {
&self.tokens
}
pub fn status(&self) -> HighlightStatus {
self.status
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenSpan {
range: Range<usize>,
scope_stack: ScopeStackRef,
}
impl TokenSpan {
pub fn range(&self) -> Range<usize> {
self.range.clone()
}
pub fn scope_stack(&self) -> ScopeStackRef {
self.scope_stack
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DocumentLine {
spans: Vec<TokenSpan>,
scopes: Arc<HighlightScopeTable>,
}
impl DocumentLine {
pub fn spans(&self) -> &[TokenSpan] {
&self.spans
}
pub fn scope_names(&self, stack: ScopeStackRef) -> impl Iterator<Item = &str> {
self.scopes.stack_names(stack)
}
pub fn scope_table(&self) -> &Arc<HighlightScopeTable> {
&self.scopes
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenizedDocument {
lines: Vec<DocumentLine>,
status: HighlightStatus,
}
impl TokenizedDocument {
pub fn lines(&self) -> &[DocumentLine] {
&self.lines
}
pub fn status(&self) -> HighlightStatus {
self.status
}
}