#![forbid(unsafe_code)]
use std::sync::Arc;
use escriba_ts::{GrammarRegistry, Semantic, TsError};
use hikari_core::{
HighlightSpan, Highlighter, HlClass, Language, LanguagePlugin, Selector, SpanSink,
};
use hikari_token::Semantic as HikariSemantic;
fn semantic_to_hikari(s: Semantic) -> HikariSemantic {
match s {
Semantic::Keyword => HikariSemantic::Keyword,
Semantic::Symbol => HikariSemantic::Symbol,
Semantic::KeywordArg => HikariSemantic::KeywordArg,
Semantic::String => HikariSemantic::String,
Semantic::Number => HikariSemantic::Number,
Semantic::Literal => HikariSemantic::Literal,
Semantic::Comment => HikariSemantic::Comment,
Semantic::Accent => HikariSemantic::Accent,
Semantic::Muted => HikariSemantic::Muted,
Semantic::Error => HikariSemantic::Error,
Semantic::Warning => HikariSemantic::Warning,
Semantic::Info => HikariSemantic::Info,
Semantic::Hint => HikariSemantic::Hint,
Semantic::Added => HikariSemantic::Added,
Semantic::Removed => HikariSemantic::Removed,
Semantic::Unchanged => HikariSemantic::Unchanged,
}
}
#[derive(Clone)]
pub struct TreeSitterHost {
registry: Arc<GrammarRegistry>,
}
impl TreeSitterHost {
pub fn builtin() -> Result<Self, TsError> {
Ok(Self {
registry: Arc::new(GrammarRegistry::builtin()?),
})
}
pub fn languages(&self) -> impl Iterator<Item = &str> {
self.registry.languages()
}
#[must_use]
pub fn plugins(&self) -> Vec<Box<dyn LanguagePlugin>> {
let mut out: Vec<Box<dyn LanguagePlugin>> = Vec::new();
for name in self.registry.languages() {
let lang: &'static str = Box::leak(name.to_string().into_boxed_str());
let selectors: Vec<Selector> = self
.registry
.get(name)
.map(|g| {
g.extensions
.iter()
.map(|e| Selector::Extension(Box::leak(e.clone().into_boxed_str())))
.collect()
})
.unwrap_or_default();
out.push(Box::new(TreeSitterPlugin {
language: Language(lang),
selectors: selectors.leak(),
registry: self.registry.clone(),
grammar: lang,
}));
}
out
}
#[must_use]
pub fn highlighter(&self, grammar: &'static str) -> TreeSitterHighlighter {
TreeSitterHighlighter {
registry: self.registry.clone(),
grammar,
}
}
}
pub struct TreeSitterPlugin {
language: Language,
selectors: &'static [Selector],
registry: Arc<GrammarRegistry>,
grammar: &'static str,
}
impl LanguagePlugin for TreeSitterPlugin {
fn language(&self) -> Language {
self.language
}
fn selectors(&self) -> &'static [Selector] {
self.selectors
}
fn make_highlighter(&self) -> Box<dyn Highlighter> {
Box::new(TreeSitterHighlighter {
registry: self.registry.clone(),
grammar: self.grammar,
})
}
}
pub struct TreeSitterHighlighter {
registry: Arc<GrammarRegistry>,
grammar: &'static str,
}
impl Highlighter for TreeSitterHighlighter {
fn highlight(&self, text: &str) -> Vec<HighlightSpan> {
let len = u32::try_from(text.len()).unwrap_or(u32::MAX);
let mut sink = SpanSink::for_document(len);
if let Some(grammar) = self.registry.get(self.grammar)
&& let Ok(spans) = escriba_ts::highlight(text, grammar, &self.registry)
{
for s in spans {
let class: HlClass = semantic_to_hikari(s.semantic).into();
sink.push(
u32::try_from(s.start).unwrap_or(u32::MAX),
u32::try_from(s.end).unwrap_or(u32::MAX),
class,
);
}
}
sink.finish()
}
}
#[cfg(test)]
mod tests {
use super::TreeSitterHost;
use hikari_core::{Highlighter, HlClass};
#[test]
fn builtin_host_highlights_rust_with_coverage() {
let host = TreeSitterHost::builtin().expect("builtin grammars");
let hl = host.highlighter("rust");
let src = "fn main() {\n let x = 42;\n}\n";
let spans = hl.highlight(src);
let mut cursor = 0u32;
for s in &spans {
assert_eq!(s.span.start, cursor, "gap/overlap at {cursor}");
cursor = s.span.end;
}
assert_eq!(cursor as usize, src.len(), "partition must cover the text");
assert!(
spans.iter().any(|s| s.class != HlClass::Plain),
"tree-sitter should classify something in real Rust",
);
}
#[test]
fn rust_grammar_is_registered() {
let host = TreeSitterHost::builtin().expect("builtin grammars");
assert!(host.languages().any(|l| l == "rust"));
assert!(!host.plugins().is_empty());
}
}