Skip to main content

syntax/
lang.rs

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