reference_query/lang/
mod.rs1use tree_sitter::{Language, Node, Parser};
8
9use crate::core::{Kind, Symbol};
10
11pub mod go;
12pub mod python;
13pub mod ruby;
14pub mod rust;
15
16pub(crate) struct Ctx<'a> {
19 src: &'a [u8],
20 file: &'a str,
21 language: &'static str,
22}
23
24impl Ctx<'_> {
25 pub(crate) fn field_text(&self, node: Node, field: &str) -> Option<String> {
27 node.child_by_field_name(field)
28 .and_then(|n| n.utf8_text(self.src).ok())
29 .map(str::to_string)
30 }
31
32 pub(crate) fn node_text(&self, node: Node) -> Option<String> {
34 node.utf8_text(self.src).ok().map(str::to_string)
35 }
36
37 pub(crate) fn symbol(
39 &self,
40 name: &str,
41 kind: Kind,
42 node: Node,
43 parent: Option<&str>,
44 ) -> Symbol {
45 Symbol {
46 name: name.to_string(),
47 kind,
48 language: self.language.to_string(),
49 file: self.file.to_string(),
50 line: node.start_position().row as u32 + 1,
51 end_line: node.end_position().row as u32 + 1,
52 parent: parent.map(str::to_string),
53 visibility: None, }
55 }
56}
57
58pub(crate) fn qualify(parent: Option<&str>, name: &str, sep: &str) -> String {
60 match parent {
61 Some(p) => format!("{p}{sep}{name}"),
62 None => name.to_string(),
63 }
64}
65
66thread_local! {
67 static PARSERS: std::cell::RefCell<std::collections::HashMap<&'static str, Parser>> =
71 std::cell::RefCell::new(std::collections::HashMap::new());
72}
73
74pub(crate) fn extract_with(
79 language: &'static str,
80 grammar: Language,
81 file: &str,
82 source: &str,
83 walk: impl FnOnce(&Ctx, Node, &mut Vec<Symbol>),
84) -> Vec<Symbol> {
85 PARSERS.with(|cell| {
86 let mut parsers = cell.borrow_mut();
87 let parser = match parsers.entry(language) {
88 std::collections::hash_map::Entry::Occupied(e) => e.into_mut(),
89 std::collections::hash_map::Entry::Vacant(v) => {
90 let mut p = Parser::new();
91 if p.set_language(&grammar).is_err() {
92 return Vec::new();
93 }
94 v.insert(p)
95 }
96 };
97 let Some(tree) = parser.parse(source, None) else {
98 return Vec::new();
99 };
100 let mut out = Vec::new();
101 let ctx = Ctx {
102 src: source.as_bytes(),
103 file,
104 language,
105 };
106 walk(&ctx, tree.root_node(), &mut out);
107 out
108 })
109}
110
111pub trait LanguagePlugin {
113 fn language(&self) -> &'static str;
116
117 fn extensions(&self) -> &[&str];
119
120 fn extract(&self, file: &str, source: &str) -> Vec<Symbol>;
123}
124
125static REGISTRY: [&(dyn LanguagePlugin + Sync); 4] =
127 [&ruby::Ruby, &rust::Rust, &go::Go, &python::Python];
128
129pub fn languages() -> Vec<&'static str> {
132 registry().iter().map(|p| p.language()).collect()
133}
134
135pub fn registry() -> &'static [&'static (dyn LanguagePlugin + Sync)] {
137 ®ISTRY
138}
139
140pub fn plugin_for_extension(ext: &str) -> Option<&'static (dyn LanguagePlugin + Sync)> {
142 REGISTRY
143 .iter()
144 .copied()
145 .find(|p| p.extensions().contains(&ext))
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn languages_are_registered_by_extension() {
154 for ext in ["rb", "rs", "go", "py"] {
155 assert!(plugin_for_extension(ext).is_some(), "{ext} should resolve");
156 }
157 assert!(plugin_for_extension("java").is_none());
158 }
159}