Skip to main content

syntax/
lang.rs

1//! A language row: the fence aliases it answers to, the tree-sitter grammar,
2//! and its highlights query.
3
4use std::{ops::Range, sync::Arc};
5use theme::HighlightKind;
6use tree_sitter::Language;
7use tree_sitter_highlight::HighlightConfiguration;
8use tree_sitter_language::LanguageFn;
9
10/// Where a grammar's parse tables come from.
11///
12/// Both variants compile in every configuration — `Wasm` carries bytes, which
13/// need no engine. Loading them does, and that is behind the `wasm` feature.
14pub enum Grammar {
15    /// Linked at build time.
16    Native(LanguageFn),
17    /// A compiled module, held as bytes: a wasm `Language` belongs to the
18    /// `WasmStore` that loaded it, so one cannot be made here.
19    Wasm(Arc<[u8]>),
20}
21
22impl Grammar {
23    /// The tree-sitter language, where one can be made without a store.
24    /// [`Grammar::Wasm`] needs a `WasmStore` and answers `None`.
25    pub fn language(&self) -> Option<Language> {
26        match self {
27            Self::Native(grammar) => Some((*grammar).into()),
28            Self::Wasm(_) => None,
29        }
30    }
31}
32
33pub struct Lang {
34    pub name: &'static str,
35    pub aliases: &'static [&'static str],
36    pub grammar: Grammar,
37    pub query: &'static str,
38    /// Which regions are written in another language, and which. Empty is a
39    /// document parsed with one grammar throughout.
40    pub injections: &'static str,
41}
42
43/// Every capture name [`kind_of`] answers to.
44///
45/// One list for every language, because a `Highlight` index means whatever the
46/// layer that produced it was configured with — an injected CSS parse inside
47/// html returns indices its own query decided. Configuring each `Lang` with its
48/// own captures makes those indices mean different things per layer.
49pub const NAMES: &[&str] = &[
50    "comment",
51    "comment.documentation",
52    "keyword",
53    "keyword.function",
54    "keyword.return",
55    "keyword.operator",
56    "keyword.conditional",
57    "keyword.conditional.ternary",
58    "keyword.coroutine",
59    "keyword.directive",
60    "keyword.exception",
61    "keyword.import",
62    "keyword.modifier",
63    "keyword.repeat",
64    "keyword.type",
65    "string",
66    "string.special",
67    "string.special.key",
68    "string.special.url",
69    "string.regexp",
70    "character.special",
71    "escape",
72    "string.escape",
73    "number",
74    "boolean",
75    "type",
76    "type.interface",
77    "type.builtin",
78    "constructor",
79    "function",
80    "function.method",
81    "function.method.call",
82    "function.call",
83    "function.builtin",
84    "macro",
85    "function.macro",
86    "property",
87    "property.definition",
88    "variable.member",
89    "constant",
90    "constant.builtin",
91    "module",
92    "module.builtin",
93    "variable",
94    "variable.builtin",
95    "variable.special",
96    "self",
97    "variable.parameter",
98    "parameter",
99    "operator",
100    "punctuation",
101    "punctuation.bracket",
102    "punctuation.delimiter",
103    "punctuation.special",
104    "tag",
105    "tag.builtin",
106    "tag.delimiter",
107    "attribute",
108    "tag.attribute",
109    "label",
110    "invalid",
111];
112
113/// A grammar's query, compiled and configured. Held by a
114/// [`Session`](crate::session::Session), never shared between two.
115pub struct Compiled {
116    pub config: HighlightConfiguration,
117    /// Languages named by a `#set! injection.language` in the query, so a
118    /// session can compile them before a parse needs them.
119    pub injected: Vec<String>,
120}
121
122impl Lang {
123    /// A language of your own: a grammar, its highlights query, and the fence
124    /// tags it answers to. `const`, so it can be a `static` beside the built-in
125    /// rows.
126    pub const fn new(
127        name: &'static str,
128        aliases: &'static [&'static str],
129        grammar: Grammar,
130        query: &'static str,
131    ) -> Self {
132        Self {
133            name,
134            aliases,
135            grammar,
136            query,
137            injections: "",
138        }
139    }
140
141    /// Give this language an injections query. `@injection.content` marks the
142    /// region and `@injection.language` names what it is written in; the name
143    /// is resolved through [`crate::registry`], so an injected language must be
144    /// one the registry can paint.
145    pub const fn with_injections(mut self, injections: &'static str) -> Self {
146        self.injections = injections;
147        self
148    }
149
150    /// Spans over `source`, through this thread's
151    /// [`Session`](crate::session::Session).
152    pub fn highlight(&'static self, source: &str) -> Option<Vec<(Range<usize>, HighlightKind)>> {
153        crate::session::with(|session| session.highlight(self, source))
154    }
155
156    /// Compile this language's queries against `grammar`, which the session
157    /// supplies because a wasm one comes from its store. `None` where a query
158    /// does not compile.
159    pub(crate) fn compile_with(&self, grammar: Language) -> Option<Compiled> {
160        let mut config =
161            HighlightConfiguration::new(grammar, self.name, self.query, self.injections, "")
162                .ok()?;
163        config.configure(NAMES);
164        let injected = (0..config.query.pattern_count())
165            .flat_map(|pattern| config.query.property_settings(pattern))
166            .filter(|property| property.key.as_ref() == "injection.language")
167            .filter_map(|property| property.value.as_ref().map(|value| value.to_string()))
168            .collect();
169        Some(Compiled { config, injected })
170    }
171}
172
173/// Find the language a fence tag names, where this build carries a grammar for
174/// it. [`crate::registry::of_tag`] answers for a tag it can only name.
175pub fn resolve(tag: &str) -> Option<&'static Lang> {
176    crate::registry::of_tag(tag)?.lang()
177}
178
179/// Map a tree-sitter highlight capture name onto the bezel vocabulary.
180///
181/// [`NAMES`] is this function's domain. A capture outside it is never
182/// configured, so it produces no span and its text is left plain.
183pub fn kind_of(name: &str) -> HighlightKind {
184    match name {
185        "comment" | "comment.documentation" => HighlightKind::Comment,
186        "keyword"
187        | "keyword.function"
188        | "keyword.return"
189        | "keyword.operator"
190        | "keyword.conditional"
191        | "keyword.conditional.ternary"
192        | "keyword.coroutine"
193        | "keyword.directive"
194        | "keyword.exception"
195        | "keyword.import"
196        | "keyword.modifier"
197        | "keyword.repeat"
198        | "keyword.type" => HighlightKind::Keyword,
199        "string" => HighlightKind::String,
200        "string.special" | "string.special.key" | "string.special.url" | "string.regexp"
201        | "character.special" => HighlightKind::StringSpecial,
202        "escape" | "string.escape" => HighlightKind::Escape,
203        "number" => HighlightKind::Number,
204        "boolean" => HighlightKind::Boolean,
205        "type" | "type.interface" => HighlightKind::TypeName,
206        "type.builtin" => HighlightKind::TypeBuiltin,
207        "constructor" => HighlightKind::Constructor,
208        "function" | "function.method" | "function.method.call" | "function.call" => {
209            HighlightKind::Function
210        }
211        "function.builtin" => HighlightKind::FunctionBuiltin,
212        "macro" | "function.macro" => HighlightKind::MacroName,
213        "property" | "property.definition" | "variable.member" => HighlightKind::Property,
214        "constant" | "constant.builtin" | "module" | "module.builtin" => HighlightKind::Constant,
215        "variable" | "variable.builtin" => HighlightKind::Variable,
216        "variable.special" | "self" => HighlightKind::VariableSpecial,
217        "variable.parameter" | "parameter" => HighlightKind::Parameter,
218        "operator" => HighlightKind::Operator,
219        "punctuation" | "punctuation.bracket" | "punctuation.delimiter" | "punctuation.special" => {
220            HighlightKind::Punctuation
221        }
222        "tag" | "tag.builtin" => HighlightKind::Tag,
223        "tag.delimiter" => HighlightKind::Punctuation,
224        "attribute" | "tag.attribute" => HighlightKind::Attribute,
225        "label" => HighlightKind::Label,
226        "invalid" => HighlightKind::Invalid,
227        _ => HighlightKind::Variable,
228    }
229}