use std::collections::HashMap;
use crate::highlight::Span;
pub trait InjectionLexer: Send + Sync {
fn lex(&self, inner: &str) -> Vec<Span>;
}
#[derive(Default)]
pub struct InjectionRegistry {
lexers: HashMap<String, Box<dyn InjectionLexer>>,
}
impl InjectionRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, label: impl Into<String>, lexer: Box<dyn InjectionLexer>) {
self.lexers.insert(label.into().to_ascii_lowercase(), lexer);
}
pub fn has(&self, label: &str) -> bool {
self.lexers.contains_key(&label.to_ascii_lowercase())
}
pub fn lex(&self, label: &str, inner: &str) -> Option<Vec<Span>> {
self.lexers
.get(&label.to_ascii_lowercase())
.map(|lexer| lexer.lex(inner))
}
pub fn lex_at(&self, label: &str, inner: &str, base: usize) -> Option<Vec<Span>> {
self.lex(label, inner).map(|spans| {
spans
.into_iter()
.map(|s| Span {
range: (s.range.start + base)..(s.range.end + base),
kind: s.kind,
})
.collect()
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::highlight::SyntaxKind;
struct WholeAsComment;
impl InjectionLexer for WholeAsComment {
fn lex(&self, inner: &str) -> Vec<Span> {
vec![Span {
range: 0..inner.len(),
kind: SyntaxKind::Comment,
}]
}
}
#[test]
fn register_and_dispatch_case_insensitively() {
let mut reg = InjectionRegistry::new();
assert!(!reg.has("toy"));
reg.register("Toy", Box::new(WholeAsComment));
assert!(reg.has("toy"));
assert!(reg.has("TOY"));
let spans = reg.lex("toy", "abcd").unwrap();
assert_eq!(
spans,
vec![Span {
range: 0..4,
kind: SyntaxKind::Comment
}]
);
}
#[test]
fn unregistered_label_renders_plain() {
let reg = InjectionRegistry::new();
assert!(reg.lex("nope", "x").is_none());
assert!(reg.lex_at("nope", "x", 5).is_none());
}
#[test]
fn lex_at_offsets_spans_into_the_document() {
let mut reg = InjectionRegistry::new();
reg.register("toy", Box::new(WholeAsComment));
let spans = reg.lex_at("toy", "abcd", 10).unwrap();
assert_eq!(
spans,
vec![Span {
range: 10..14,
kind: SyntaxKind::Comment
}]
);
}
#[test]
fn a_mod_overrides_a_builtin() {
struct WholeAsString;
impl InjectionLexer for WholeAsString {
fn lex(&self, inner: &str) -> Vec<Span> {
vec![Span {
range: 0..inner.len(),
kind: SyntaxKind::StringLit,
}]
}
}
let mut reg = InjectionRegistry::new();
reg.register("x", Box::new(WholeAsComment));
reg.register("x", Box::new(WholeAsString));
assert_eq!(reg.lex("x", "ab").unwrap()[0].kind, SyntaxKind::StringLit);
}
}