use std::borrow::Cow;
use std::path::Path;
use compact_str::CompactString;
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct LanguageId(u16);
impl LanguageId {
#[must_use]
pub const fn index(self) -> usize {
self.0 as usize
}
}
#[non_exhaustive]
pub enum ImportSpec {
Query {
source: Cow<'static, str>,
kind_map: fn(&str) -> crate::imports::ImportKind,
},
Custom(fn(&str, &tree_sitter::Tree) -> Vec<crate::imports::RawImport>),
}
#[non_exhaustive]
pub struct LanguageSpec {
pub name: CompactString,
pub language: tree_sitter::Language,
pub extensions: SmallVec<[CompactString; 4]>,
pub tags_query: Option<Cow<'static, str>>,
pub merge_adjacent_same_name_definitions: bool,
pub imports: Option<ImportSpec>,
}
impl LanguageSpec {
#[must_use]
pub fn new<I, E>(
name: impl Into<CompactString>,
language: tree_sitter::Language,
extensions: I,
) -> Self
where
I: IntoIterator<Item = E>,
E: AsRef<str>,
{
Self {
name: name.into(),
language,
extensions: extensions
.into_iter()
.map(|extension| CompactString::new(extension.as_ref()))
.collect(),
tags_query: None,
merge_adjacent_same_name_definitions: false,
imports: None,
}
}
#[must_use]
pub fn with_tags_query(mut self, tags_query: impl Into<Cow<'static, str>>) -> Self {
self.tags_query = Some(tags_query.into());
self
}
#[must_use]
pub const fn with_merge_adjacent_same_name_definitions(mut self, enabled: bool) -> Self {
self.merge_adjacent_same_name_definitions = enabled;
self
}
#[must_use]
pub fn with_imports(mut self, imports: ImportSpec) -> Self {
self.imports = Some(imports);
self
}
}
pub struct LanguageRegistry {
specs: Vec<LanguageSpec>,
by_extension: FxHashMap<CompactString, LanguageId>,
generation: u64,
}
impl LanguageRegistry {
#[must_use]
pub fn empty() -> Self {
Self {
specs: Vec::new(),
by_extension: FxHashMap::default(),
generation: 0,
}
}
pub fn register(&mut self, spec: LanguageSpec) -> LanguageId {
let id = LanguageId(
u16::try_from(self.specs.len()).expect("language registry exhausted its u16 id space"),
);
for extension in &spec.extensions {
self.by_extension.insert(extension.clone(), id);
}
self.specs.push(spec);
self.generation += 1;
id
}
#[must_use]
pub const fn generation(&self) -> u64 {
self.generation
}
#[must_use]
pub fn for_path(&self, path: &Path) -> Option<LanguageId> {
let extension = path.extension()?.to_str()?;
self.by_extension.get(extension).copied()
}
#[must_use]
pub fn get(&self, id: LanguageId) -> Option<&LanguageSpec> {
self.specs.get(id.index())
}
#[must_use]
pub fn supports_symbols(&self, path: &Path) -> bool {
self.for_path(path)
.and_then(|id| self.get(id))
.is_some_and(|spec| spec.tags_query.is_some())
}
#[must_use]
pub fn supports_imports(&self, path: &Path) -> bool {
self.for_path(path)
.and_then(|id| self.get(id))
.is_some_and(|spec| spec.imports.is_some())
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (LanguageId, &LanguageSpec)> {
self.specs.iter().enumerate().map(|(index, spec)| {
let id = LanguageId(
u16::try_from(index).expect("registered language index must fit in LanguageId"),
);
(id, spec)
})
}
}
impl Default for LanguageRegistry {
fn default() -> Self {
Self::empty()
}
}