use crate::{
lang::{Grammar, Lang},
registry,
};
use std::{cell::RefCell, collections::HashMap, ops::Range};
use theme::HighlightKind;
use tree_sitter::Language;
use tree_sitter_highlight::{HighlightEvent, Highlighter};
#[derive(Default)]
pub struct Session {
highlighter: Highlighter,
configs: HashMap<&'static str, Option<crate::lang::Compiled>>,
}
impl Session {
pub fn new() -> Self {
Self::default()
}
fn ensure(&mut self, lang: &'static Lang) {
if self.configs.contains_key(lang.name) {
return;
}
let compiled = self.compile(lang);
let injected = compiled
.as_ref()
.map(|compiled| compiled.injected.clone())
.unwrap_or_default();
self.configs.insert(lang.name, compiled);
for name in injected {
if let Some(lang) = registry::of_tag(&name).and_then(|known| known.lang()) {
self.ensure(lang);
}
}
}
fn compile(&mut self, lang: &'static Lang) -> Option<crate::lang::Compiled> {
let grammar = match &lang.grammar {
Grammar::Native(grammar) => (*grammar).into(),
Grammar::Wasm(bytes) => self.load_wasm(lang.name, &bytes.clone())?,
};
lang.compile_with(grammar)
}
#[cfg(feature = "wasm")]
fn load_wasm(&mut self, name: &str, bytes: &[u8]) -> Option<Language> {
use tree_sitter::WasmStore;
let mut store = match self.highlighter.parser().take_wasm_store() {
Some(store) => store,
None => WasmStore::new(crate::engine()?).ok()?,
};
let language = store.load_language(name, bytes).ok();
self.highlighter.parser().set_wasm_store(store).ok()?;
language
}
#[cfg(not(feature = "wasm"))]
fn load_wasm(&mut self, _name: &str, _bytes: &[u8]) -> Option<Language> {
None
}
pub fn highlight(
&mut self,
lang: &'static Lang,
source: &str,
) -> Option<Vec<(Range<usize>, HighlightKind)>> {
self.ensure(lang);
let Self {
highlighter,
configs,
} = self;
let config = &configs.get(lang.name)?.as_ref()?.config;
let mut spans = Vec::new();
let mut kinds: Vec<HighlightKind> = Vec::new();
for event in highlighter
.highlight(config, source.as_bytes(), None, None, |name| {
let key = registry::of_tag(name)?.lang()?.name;
configs.get(key)?.as_ref().map(|compiled| &compiled.config)
})
.ok()?
.flatten()
{
match event {
HighlightEvent::HighlightStart(hl) => {
kinds.push(crate::lang::kind_of(
crate::lang::NAMES.get(hl.0).copied().unwrap_or(""),
));
}
HighlightEvent::HighlightEnd => {
kinds.pop();
}
HighlightEvent::Source { start, end } => {
if let Some(&kind) = kinds.last() {
spans.push((start..end, kind));
}
}
}
}
Some(spans)
}
}
thread_local! {
static SESSION: RefCell<Session> = RefCell::new(Session::new());
}
pub fn with<T>(f: impl FnOnce(&mut Session) -> T) -> T {
SESSION.with(|session| f(&mut session.borrow_mut()))
}