use syntastica_core::ts_runtime::Node;
use syntastica_core::{
language_set::{LanguageSet, SupportedLanguage},
theme::THEME_KEYS,
Result,
};
use syntastica_highlight::{Highlight, HighlightEvent, Highlighter};
use crate::Highlights;
pub struct Processor<'set, Set: LanguageSet<'set>> {
set: &'set Set,
highlighter: Highlighter,
}
impl<'set, Set: LanguageSet<'set>> Processor<'set, Set> {
pub fn new(set: &'set Set) -> Self {
Self {
set,
highlighter: Highlighter::new(),
}
}
pub fn process_once<'src>(
code: &'src str,
language: impl Into<Set::Language>,
set: &'set Set,
) -> Result<Highlights<'src>> {
Self::new(set).process(code, language)
}
pub fn process<'src>(
&mut self,
code: &'src str,
language: impl Into<Set::Language>,
) -> Result<Highlights<'src>> {
self.process_impl(code, language.into(), None)
}
pub fn process_tree<'src>(
&mut self,
code: &'src str,
language: impl Into<Set::Language>,
tree: &Node<'_>,
) -> Result<Highlights<'src>> {
self.process_impl(code, language.into(), Some(tree))
}
fn process_impl<'src>(
&mut self,
code: &'src str,
language: Set::Language,
tree: Option<&Node<'_>>,
) -> Result<Highlights<'src>> {
let highlight_config = self.set.get_language(language)?;
let injection_callback = |lang_name: &str| {
let lang_name = lang_name.to_ascii_lowercase();
Set::Language::for_name(&lang_name, self.set)
.ok()
.or_else(|| Set::Language::for_injection(&lang_name, self.set))
.or_else(|| {
lang_name.rsplit_once('/').and_then(|(_, name)| {
Set::Language::for_name(name, self.set)
.ok()
.or_else(|| Set::Language::for_injection(name, self.set))
})
})
.and_then(|lang| self.set.get_language(lang).ok())
};
match tree {
Some(tree) => process_highlight_iter(
self.highlighter.highlight_existing_tree(
highlight_config,
code.as_bytes(),
None,
tree,
)?,
code,
),
None => process_highlight_iter(
self.highlighter.highlight(
highlight_config,
code.as_bytes(),
None,
injection_callback,
)?,
code,
),
}
}
}
fn process_highlight_iter(
iter: impl Iterator<Item = std::result::Result<HighlightEvent, syntastica_highlight::Error>>,
code: &str,
) -> Result<Highlights<'_>> {
let mut out = vec![vec![]];
let mut style_stack = vec![];
for event in iter {
match event? {
HighlightEvent::HighlightStart(Highlight(highlight)) => style_stack.push(highlight),
HighlightEvent::HighlightEnd => {
style_stack.pop();
}
HighlightEvent::Source { start, end } => {
let ends_with_newline = code[start..end].ends_with('\n');
let mut lines = code[start..end].lines().peekable();
while let Some(line) = lines.next() {
let style = style_stack.last().and_then(|idx| {
let key = THEME_KEYS[*idx];
match key {
"none" => None,
_ => Some(key),
}
});
out.last_mut()
.expect("`out` is initialized with one element and never shrinks in size")
.push((line, style));
if lines.peek().is_some() || ends_with_newline {
out.push(vec![]);
}
}
}
}
}
Ok(out)
}