use std::collections::HashMap;
use std::path::Path;
use std::sync::{Arc, Mutex};
use anyhow::Result;
use hjkl_bonsai::runtime::{Grammar, GrammarLoader, GrammarRegistry};
pub struct LanguageDirectory {
registry: GrammarRegistry,
loader: GrammarLoader,
cache: Mutex<HashMap<String, Arc<Grammar>>>,
}
impl LanguageDirectory {
pub fn new() -> Result<Self> {
let registry = GrammarRegistry::embedded()?;
let loader = GrammarLoader::user_default(registry.meta())?;
Ok(Self {
registry,
loader,
cache: Mutex::new(HashMap::new()),
})
}
pub fn by_name(&self, name: &str) -> Option<Arc<Grammar>> {
if let Some(g) = self.cache_get(name) {
return Some(g);
}
let spec = self.registry.by_name(name)?;
let grammar = Grammar::load(name, spec, &self.loader, self.registry.meta()).ok()?;
Some(self.cache_insert(name, grammar))
}
pub fn for_path(&self, path: &Path) -> Option<Arc<Grammar>> {
let name = self.registry.name_for_path(path)?.to_string();
self.by_name(&name)
}
fn cache_get(&self, name: &str) -> Option<Arc<Grammar>> {
let g = self.cache.lock().ok()?;
g.get(name).cloned()
}
fn cache_insert(&self, name: &str, grammar: Grammar) -> Arc<Grammar> {
let arc = Arc::new(grammar);
if let Ok(mut g) = self.cache.lock() {
if let Some(existing) = g.get(name) {
return existing.clone();
}
g.insert(name.to_string(), arc.clone());
}
arc
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
#[ignore = "network + compiler: clones + builds tree-sitter-rust"]
fn for_path_returns_grammar_for_known_extension() {
let dir = LanguageDirectory::new().unwrap();
let g = dir.for_path(&PathBuf::from("foo.rs")).unwrap();
assert_eq!(g.name(), "rust");
}
#[test]
fn for_path_returns_none_for_unknown_extension() {
let dir = LanguageDirectory::new().unwrap();
assert!(dir.for_path(&PathBuf::from("foo.zzznope")).is_none());
}
}