Skip to main content

strop_syntax/
languages.rs

1//! The curated language registry (0002 §2.2: statically linked, always).
2//! All highlight queries are Helix-vendored (queries/ per language, MPL-2.0) —
3//! one upstream, one review surface, divergence watched weekly (0002 §7).
4//! Extension → (grammar, highlights query). Adding a language is one
5//! row here plus one crate in Cargo.toml — never a download, never a
6//! dlopen. C++ included specifically: its scanner is C++, the musl
7//! static-libstdc++ path the release gate guards (0002 §5).
8
9use tree_sitter::Language;
10
11pub struct LanguageSpec {
12    pub name: &'static str,
13    pub language: Language,
14    pub highlights: &'static str,
15}
16
17// tree-sitter-toml 0.20 targets an old ABI (a second tree-sitter in the
18// tree — not worth it); TOML joins when a 0.24-ABI grammar crate exists.
19macro_rules! lang_fn {
20    ($name:literal, $f:expr, $q:expr) => {
21        LanguageSpec {
22            name: $name,
23            language: $f.into(),
24            highlights: $q,
25        }
26    };
27}
28
29/// Extension (with dot) → spec. First match wins.
30pub fn for_extension(ext: &str) -> Option<LanguageSpec> {
31    Some(match ext {
32        ".rs" => lang_fn!(
33            "rust",
34            tree_sitter_rust::LANGUAGE,
35            include_str!("../queries/rust/highlights.scm")
36        ),
37        ".py" | ".pyi" => {
38            lang_fn!(
39                "python",
40                tree_sitter_python::LANGUAGE,
41                include_str!("../queries/python/highlights.scm")
42            )
43        }
44        ".js" | ".jsx" | ".mjs" | ".cjs" => {
45            lang_fn!(
46                "javascript",
47                tree_sitter_javascript::LANGUAGE,
48                include_str!("../queries/javascript/highlights.scm")
49            )
50        }
51        ".ts" => lang_fn!(
52            "typescript",
53            tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
54            include_str!("../queries/typescript/highlights.scm")
55        ),
56        ".tsx" => lang_fn!(
57            "tsx",
58            tree_sitter_typescript::LANGUAGE_TSX,
59            include_str!("../queries/tsx/highlights.scm")
60        ),
61        ".go" => lang_fn!(
62            "go",
63            tree_sitter_go::LANGUAGE,
64            include_str!("../queries/go/highlights.scm")
65        ),
66        ".c" | ".h" => lang_fn!(
67            "c",
68            tree_sitter_c::LANGUAGE,
69            include_str!("../queries/c/highlights.scm")
70        ),
71        ".cpp" | ".cc" | ".cxx" | ".hpp" | ".hh" => {
72            lang_fn!(
73                "cpp",
74                tree_sitter_cpp::LANGUAGE,
75                tree_sitter_cpp::HIGHLIGHT_QUERY
76            )
77        }
78        ".json" => lang_fn!(
79            "json",
80            tree_sitter_json::LANGUAGE,
81            include_str!("../queries/json/highlights.scm")
82        ),
83        ".sh" | ".bash" => lang_fn!(
84            "bash",
85            tree_sitter_bash::LANGUAGE,
86            include_str!("../queries/bash/highlights.scm")
87        ),
88        _ => return None,
89    })
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn covers_the_curated_set() {
98        for ext in [
99            ".rs", ".py", ".js", ".ts", ".tsx", ".go", ".c", ".cpp", ".json", ".sh",
100        ] {
101            assert!(for_extension(ext).is_some(), "missing {ext}");
102        }
103        assert!(for_extension(".xyz").is_none());
104    }
105}