Skip to main content

diffler_core/syntax/
registry.rs

1//! Language registry: maps a file path to its tree-sitter grammar, a configured
2//! highlight configuration, and (where the grammar ships one) a tags query used
3//! for scope/definition lookup. Built once and reused.
4
5use std::collections::HashMap;
6use std::path::Path;
7
8use tree_sitter::{Language, Query};
9use tree_sitter_highlight::HighlightConfiguration;
10
11/// Capture names recognized during highlighting. A grammar capture like
12/// `function.method` resolves to the longest matching prefix here (`function`),
13/// so listing the general categories is enough to color every grammar.
14pub const HIGHLIGHT_NAMES: &[&str] = &[
15    "attribute",
16    "comment",
17    "constant",
18    "constant.builtin",
19    "constructor",
20    "function",
21    "function.builtin",
22    "keyword",
23    "label",
24    "number",
25    "operator",
26    "property",
27    "punctuation",
28    "punctuation.bracket",
29    "punctuation.delimiter",
30    "string",
31    "string.escape",
32    "string.special",
33    "tag",
34    "type",
35    "type.builtin",
36    "variable",
37    "variable.builtin",
38    "variable.parameter",
39];
40
41pub struct LangEntry {
42    pub language: Language,
43    /// `None` when the grammar's highlight query failed to compile; the file
44    /// then renders plain instead of erroring.
45    pub config: Option<HighlightConfiguration>,
46    /// Definition/tags query for scope lookup; `None` when the grammar ships no
47    /// tags query.
48    pub tags: Option<Query>,
49}
50
51pub struct LanguageRegistry {
52    entries: Vec<LangEntry>,
53    by_ext: HashMap<&'static str, usize>,
54}
55
56impl LanguageRegistry {
57    /// Build the registry with every bundled grammar, reused for the session.
58    // flat per-language registration table
59    #[allow(clippy::too_many_lines)]
60    pub fn build() -> Self {
61        let mut r = Self {
62            entries: Vec::new(),
63            by_ext: HashMap::new(),
64        };
65
66        r.add(
67            "rust",
68            &["rs"],
69            tree_sitter_rust::LANGUAGE.into(),
70            tree_sitter_rust::HIGHLIGHTS_QUERY,
71            Some(tree_sitter_rust::TAGS_QUERY),
72        );
73        r.add(
74            "python",
75            &["py", "pyi"],
76            tree_sitter_python::LANGUAGE.into(),
77            tree_sitter_python::HIGHLIGHTS_QUERY,
78            Some(tree_sitter_python::TAGS_QUERY),
79        );
80        r.add(
81            "javascript",
82            &["js", "jsx", "mjs", "cjs"],
83            tree_sitter_javascript::LANGUAGE.into(),
84            &format!(
85                "{}\n{}",
86                tree_sitter_javascript::HIGHLIGHT_QUERY,
87                tree_sitter_javascript::JSX_HIGHLIGHT_QUERY
88            ),
89            Some(tree_sitter_javascript::TAGS_QUERY),
90        );
91        r.add(
92            "typescript",
93            &["ts", "mts", "cts"],
94            tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
95            &format!(
96                "{}\n{}",
97                tree_sitter_javascript::HIGHLIGHT_QUERY,
98                tree_sitter_typescript::HIGHLIGHTS_QUERY
99            ),
100            Some(tree_sitter_typescript::TAGS_QUERY),
101        );
102        r.add(
103            "tsx",
104            &["tsx"],
105            tree_sitter_typescript::LANGUAGE_TSX.into(),
106            &format!(
107                "{}\n{}\n{}",
108                tree_sitter_javascript::HIGHLIGHT_QUERY,
109                tree_sitter_javascript::JSX_HIGHLIGHT_QUERY,
110                tree_sitter_typescript::HIGHLIGHTS_QUERY
111            ),
112            Some(tree_sitter_typescript::TAGS_QUERY),
113        );
114        r.add(
115            "go",
116            &["go"],
117            tree_sitter_go::LANGUAGE.into(),
118            tree_sitter_go::HIGHLIGHTS_QUERY,
119            Some(tree_sitter_go::TAGS_QUERY),
120        );
121        r.add(
122            "c",
123            &["c", "h"],
124            tree_sitter_c::LANGUAGE.into(),
125            tree_sitter_c::HIGHLIGHT_QUERY,
126            Some(tree_sitter_c::TAGS_QUERY),
127        );
128        r.add(
129            "cpp",
130            &["cpp", "cc", "cxx", "hpp", "hh", "hxx"],
131            tree_sitter_cpp::LANGUAGE.into(),
132            tree_sitter_cpp::HIGHLIGHT_QUERY,
133            Some(tree_sitter_cpp::TAGS_QUERY),
134        );
135        r.add(
136            "java",
137            &["java"],
138            tree_sitter_java::LANGUAGE.into(),
139            tree_sitter_java::HIGHLIGHTS_QUERY,
140            Some(tree_sitter_java::TAGS_QUERY),
141        );
142        r.add(
143            "c-sharp",
144            &["cs"],
145            tree_sitter_c_sharp::LANGUAGE.into(),
146            tree_sitter_c_sharp::HIGHLIGHTS_QUERY,
147            Some(tree_sitter_c_sharp::TAGS_QUERY),
148        );
149        r.add(
150            "ruby",
151            &["rb"],
152            tree_sitter_ruby::LANGUAGE.into(),
153            tree_sitter_ruby::HIGHLIGHTS_QUERY,
154            Some(tree_sitter_ruby::TAGS_QUERY),
155        );
156        r.add(
157            "php",
158            &["php"],
159            tree_sitter_php::LANGUAGE_PHP.into(),
160            tree_sitter_php::HIGHLIGHTS_QUERY,
161            Some(tree_sitter_php::TAGS_QUERY),
162        );
163        r.add(
164            "bash",
165            &["sh", "bash", "zsh"],
166            tree_sitter_bash::LANGUAGE.into(),
167            tree_sitter_bash::HIGHLIGHT_QUERY,
168            None,
169        );
170        r.add(
171            "json",
172            &["json"],
173            tree_sitter_json::LANGUAGE.into(),
174            tree_sitter_json::HIGHLIGHTS_QUERY,
175            None,
176        );
177        r.add(
178            "html",
179            &["html", "htm"],
180            tree_sitter_html::LANGUAGE.into(),
181            tree_sitter_html::HIGHLIGHTS_QUERY,
182            None,
183        );
184        r.add(
185            "css",
186            &["css"],
187            tree_sitter_css::LANGUAGE.into(),
188            tree_sitter_css::HIGHLIGHTS_QUERY,
189            None,
190        );
191
192        r
193    }
194
195    fn add(
196        &mut self,
197        name: &'static str,
198        extensions: &'static [&'static str],
199        language: Language,
200        highlights: &str,
201        tags: Option<&str>,
202    ) {
203        let config = HighlightConfiguration::new(language.clone(), name, highlights, "", "")
204            .ok()
205            .map(|mut c| {
206                c.configure(HIGHLIGHT_NAMES);
207                c
208            });
209        let tags = tags.and_then(|q| Query::new(&language, q).ok());
210        let idx = self.entries.len();
211        self.entries.push(LangEntry {
212            language,
213            config,
214            tags,
215        });
216        for ext in extensions {
217            self.by_ext.insert(ext, idx);
218        }
219    }
220
221    /// The entry whose grammar handles `path`, keyed by file extension.
222    pub fn for_path(&self, path: &str) -> Option<&LangEntry> {
223        let ext = Path::new(path).extension()?.to_str()?;
224        let &idx = self.by_ext.get(ext)?;
225        self.entries.get(idx)
226    }
227}
228
229impl Default for LanguageRegistry {
230    fn default() -> Self {
231        Self::build()
232    }
233}