use std::sync::{Arc, LazyLock, RwLock};
use ahash::AHashMap;
use tree_sitter::Query;
use crate::error::Error;
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum QueryKind {
Highlights,
Injections,
Locals,
Tags,
Indents,
Folds,
}
const QUERY_KIND_COUNT: usize = 6;
const fn cache_index(kind: QueryKind) -> usize {
match kind {
QueryKind::Highlights => 0,
QueryKind::Injections => 1,
QueryKind::Locals => 2,
QueryKind::Tags => 3,
QueryKind::Indents => 4,
QueryKind::Folds => 5,
}
}
type QueryMap = AHashMap<Box<str>, Option<Arc<Query>>>;
#[allow(clippy::type_complexity)]
static QUERY_CACHE: LazyLock<[RwLock<QueryMap>; QUERY_KIND_COUNT]> =
LazyLock::new(|| std::array::from_fn(|_| RwLock::new(AHashMap::new())));
fn source_for(language: &str, kind: QueryKind) -> Option<&'static str> {
match kind {
QueryKind::Highlights => crate::queries::get_highlights_query(language),
QueryKind::Injections => crate::queries::get_injections_query(language),
QueryKind::Locals => crate::queries::get_locals_query(language),
QueryKind::Tags => crate::queries::get_tags_query(language),
QueryKind::Indents => crate::queries::get_indents_query(language),
QueryKind::Folds => crate::queries::get_folds_query(language),
}
}
#[cfg_attr(alef, alef(skip))]
pub fn get_query(language: &str, kind: QueryKind) -> Result<Option<Arc<Query>>, Error> {
let lang = crate::registry::resolve_alias(language);
let cache = &QUERY_CACHE[cache_index(kind)];
{
let map = cache.read().map_err(|e| Error::LockPoisoned(e.to_string()))?;
if let Some(entry) = map.get(lang) {
return Ok(entry.clone());
}
}
let compiled: Option<Arc<Query>> = match source_for(lang, kind) {
None => None,
Some(src) => {
let language = crate::get_language(lang)?;
let query = Query::new(&language, src)
.map_err(|e| Error::QueryError(format!("failed to compile {kind:?} query for '{lang}': {e}")))?;
Some(Arc::new(query))
}
};
let mut map = cache.write().map_err(|e| Error::LockPoisoned(e.to_string()))?;
let entry = map.entry(Box::from(lang)).or_insert(compiled);
Ok(entry.clone())
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_KINDS: [QueryKind; 6] = [
QueryKind::Highlights,
QueryKind::Injections,
QueryKind::Locals,
QueryKind::Tags,
QueryKind::Indents,
QueryKind::Folds,
];
#[test]
fn every_kind_owns_a_distinct_cache_slot() {
assert_eq!(
ALL_KINDS.len(),
QUERY_KIND_COUNT,
"ALL_KINDS must cover every QueryKind"
);
let mut claimed = [false; QUERY_KIND_COUNT];
for kind in ALL_KINDS {
let index = cache_index(kind);
assert!(
index < QUERY_KIND_COUNT,
"cache index {index} out of range for {kind:?}"
);
assert!(!claimed[index], "two kinds share cache slot {index} ({kind:?})");
claimed[index] = true;
}
assert!(claimed.iter().all(|&slot| slot), "every cache slot must be claimed");
}
#[test]
fn missing_query_returns_none_and_is_negatively_cached() {
for _ in 0..2 {
for kind in ALL_KINDS {
assert!(get_query("definitely_not_a_language", kind).unwrap().is_none());
}
}
}
}