Skip to main content

boltz_language/
language.rs

1//! Minimal tree-sitter language registry + highlight query runner.
2//!
3//! Deliberately does NOT port Zed's `language` crate (22.9kLOC) — ~70% of it
4//! is `LanguageServer`/`Capability`/diagnostics glue for LSP, which is out of
5//! scope for every phase of this plan (see
6//! `plans/20260705-1722-zed-ui-component-enrichment/plan.md`). Only the
7//! rope+tree-sitter binding — the ~30% actually needed for syntax
8//! highlighting — is reproduced here, written fresh rather than extracted
9//! from Zed's source (that source is entangled with the LSP types being cut).
10
11mod highlight;
12
13pub use highlight::highlighted_spans;
14
15/// A single language: its tree-sitter grammar plus a compiled highlight
16/// query. Construction fails only if `highlight_query_source` doesn't parse
17/// against `grammar` — a bug in the bundled query, not user input, so a
18/// caller-facing `Result` would just be unwrapped everywhere; panicking here
19/// surfaces the bug immediately in development instead.
20pub struct Language {
21    name: &'static str,
22    pub(crate) grammar: tree_sitter::Language,
23    pub(crate) highlight_query: tree_sitter::Query,
24}
25
26impl Language {
27    pub fn new(
28        name: &'static str,
29        grammar: tree_sitter::Language,
30        highlight_query_source: &str,
31    ) -> Self {
32        let highlight_query = tree_sitter::Query::new(&grammar, highlight_query_source)
33            .unwrap_or_else(|error| {
34                panic!("bundled highlight query for {name} failed to parse: {error}")
35            });
36        Self {
37            name,
38            grammar,
39            highlight_query,
40        }
41    }
42
43    pub fn name(&self) -> &'static str {
44        self.name
45    }
46}
47
48/// Resolves a [`Language`] by file extension (no leading dot, e.g. `"rs"`).
49/// Intentionally minimal: no shebang detection, no content sniffing — see
50/// Zed's `LanguageRegistry` for that scope, out of bounds here.
51pub trait LanguageRegistry {
52    fn language_for_extension(&self, extension: &str) -> Option<&Language>;
53}
54
55/// The default registry: exactly the grammars compiled in via Cargo
56/// features (see this crate's `Cargo.toml` — `lang-rust` is on by default
57/// as the minimal working example; every other grammar is opt-in so
58/// consumers only pay its ~200-300KB binary-size cost if they enable it).
59pub struct DefaultLanguageRegistry {
60    #[cfg(feature = "lang-rust")]
61    rust: Language,
62    #[cfg(feature = "lang-javascript")]
63    javascript: Language,
64    #[cfg(feature = "lang-typescript")]
65    typescript: Language,
66    #[cfg(feature = "lang-typescript")]
67    tsx: Language,
68    #[cfg(feature = "lang-markdown")]
69    markdown: Language,
70    #[cfg(feature = "lang-json")]
71    json: Language,
72}
73
74impl DefaultLanguageRegistry {
75    pub fn new() -> Self {
76        Self {
77            #[cfg(feature = "lang-rust")]
78            rust: Language::new(
79                "Rust",
80                tree_sitter_rust::LANGUAGE.into(),
81                tree_sitter_rust::HIGHLIGHTS_QUERY,
82            ),
83            #[cfg(feature = "lang-javascript")]
84            javascript: Language::new(
85                "JavaScript",
86                tree_sitter_javascript::LANGUAGE.into(),
87                tree_sitter_javascript::HIGHLIGHT_QUERY,
88            ),
89            #[cfg(feature = "lang-typescript")]
90            typescript: Language::new(
91                "TypeScript",
92                tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
93                tree_sitter_typescript::HIGHLIGHTS_QUERY,
94            ),
95            #[cfg(feature = "lang-typescript")]
96            tsx: Language::new(
97                "TSX",
98                tree_sitter_typescript::LANGUAGE_TSX.into(),
99                tree_sitter_typescript::HIGHLIGHTS_QUERY,
100            ),
101            // Markdown's tree-sitter grammar splits block/inline parsing into
102            // two separate grammars; only the block grammar is wired here
103            // (headings, lists, code fences) — inline emphasis/link spans
104            // inside a block need the second `inline_language()` grammar
105            // layered on top, which is out of scope for this minimal pass.
106            #[cfg(feature = "lang-markdown")]
107            markdown: Language::new(
108                "Markdown",
109                tree_sitter_md::LANGUAGE.into(),
110                tree_sitter_md::HIGHLIGHT_QUERY_BLOCK,
111            ),
112            #[cfg(feature = "lang-json")]
113            json: Language::new(
114                "JSON",
115                tree_sitter_json::LANGUAGE.into(),
116                tree_sitter_json::HIGHLIGHTS_QUERY,
117            ),
118        }
119    }
120}
121
122impl Default for DefaultLanguageRegistry {
123    fn default() -> Self {
124        Self::new()
125    }
126}
127
128impl LanguageRegistry for DefaultLanguageRegistry {
129    fn language_for_extension(&self, extension: &str) -> Option<&Language> {
130        match extension {
131            #[cfg(feature = "lang-rust")]
132            "rs" => Some(&self.rust),
133            #[cfg(feature = "lang-javascript")]
134            "js" | "mjs" | "cjs" | "jsx" => Some(&self.javascript),
135            #[cfg(feature = "lang-typescript")]
136            "ts" | "mts" | "cts" => Some(&self.typescript),
137            #[cfg(feature = "lang-typescript")]
138            "tsx" => Some(&self.tsx),
139            #[cfg(feature = "lang-markdown")]
140            "md" | "markdown" => Some(&self.markdown),
141            #[cfg(feature = "lang-json")]
142            "json" => Some(&self.json),
143            _ => None,
144        }
145    }
146}