1use std::{ops::Range, sync::OnceLock};
6use theme::HighlightKind;
7use tree_sitter::Language;
8use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
9use tree_sitter_language::LanguageFn;
10
11pub struct Lang {
12 pub name: &'static str,
13 pub aliases: &'static [&'static str],
14 pub grammar: LanguageFn,
15 pub query: &'static str,
16 compiled: OnceLock<Option<Compiled>>,
19}
20
21pub struct Compiled {
23 pub config: HighlightConfiguration,
24 pub names: Vec<String>,
26}
27
28impl Lang {
29 pub const fn new(
33 name: &'static str,
34 aliases: &'static [&'static str],
35 grammar: LanguageFn,
36 query: &'static str,
37 ) -> Self {
38 Self {
39 name,
40 aliases,
41 grammar,
42 query,
43 compiled: OnceLock::new(),
44 }
45 }
46
47 pub fn highlight(&'static self, source: &str) -> Option<Vec<(Range<usize>, HighlightKind)>> {
50 let compiled = self.compiled()?;
51 let config = &compiled.config;
52 let mut highlighter = Highlighter::new();
53 highlighter.parser().set_language(&config.language).ok()?;
54 let mut spans = Vec::new();
55 let mut kinds: Vec<HighlightKind> = Vec::new();
58 for event in highlighter
59 .highlight(config, source.as_bytes(), None, |_| None)
60 .ok()?
61 .flatten()
62 {
63 match event {
64 HighlightEvent::HighlightStart(hl) => {
65 let name = compiled.names.get(hl.0).map(String::as_str).unwrap_or("");
66 kinds.push(kind_of(name));
67 }
68 HighlightEvent::HighlightEnd => {
69 kinds.pop();
70 }
71 HighlightEvent::Source { start, end } => {
72 if let Some(&kind) = kinds.last() {
73 spans.push((start..end, kind));
74 }
75 }
76 }
77 }
78 Some(spans)
79 }
80
81 pub fn compiled(&'static self) -> Option<&'static Compiled> {
82 self.compiled
83 .get_or_init(|| {
84 let grammar: Language = self.grammar.into();
85 let mut config =
86 HighlightConfiguration::new(grammar, self.name, self.query, "", "").ok()?;
87 let names: Vec<String> = config
92 .query
93 .capture_names()
94 .iter()
95 .map(|s| s.to_string())
96 .filter(|s| !s.starts_with('_'))
97 .collect();
98 config.configure(&names);
99 Some(Compiled { config, names })
100 })
101 .as_ref()
102 }
103}
104
105#[cfg(feature = "rust")]
106static RUST: Lang = Lang::new(
107 "rust",
108 &["rust", "rs"],
109 tree_sitter_rust::LANGUAGE,
110 include_str!("../queries/rust.scm"),
111);
112#[cfg(feature = "python")]
113static PYTHON: Lang = Lang::new(
114 "python",
115 &["python", "py"],
116 tree_sitter_python::LANGUAGE,
117 include_str!("../queries/python.scm"),
118);
119#[cfg(feature = "typescript")]
120static TYPESCRIPT: Lang = Lang::new(
121 "typescript",
122 &["typescript", "ts"],
123 tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
124 include_str!("../queries/typescript.scm"),
125);
126#[cfg(feature = "typescript")]
130static TSX: Lang = Lang::new(
131 "tsx",
132 &["tsx", "jsx", "javascript", "js"],
133 tree_sitter_typescript::LANGUAGE_TSX,
134 include_str!("../queries/tsx.scm"),
135);
136#[cfg(feature = "json")]
137static JSON: Lang = Lang::new(
138 "json",
139 &["json", "jsonc"],
140 tree_sitter_json::LANGUAGE,
141 include_str!("../queries/json.scm"),
142);
143#[cfg(feature = "go")]
144static GO: Lang = Lang::new(
145 "go",
146 &["go", "golang"],
147 tree_sitter_go::LANGUAGE,
148 include_str!("../queries/go.scm"),
149);
150#[cfg(feature = "bash")]
151static BASH: Lang = Lang::new(
152 "bash",
153 &["bash", "sh", "shell", "zsh", "console"],
154 tree_sitter_bash::LANGUAGE,
155 include_str!("../queries/bash.scm"),
156);
157#[cfg(feature = "toml")]
158static TOML: Lang = Lang::new(
159 "toml",
160 &["toml"],
161 tree_sitter_toml_ng::LANGUAGE,
162 include_str!("../queries/toml.scm"),
163);
164
165pub static LANGS: &[&Lang] = &[
168 #[cfg(feature = "rust")]
169 &RUST,
170 #[cfg(feature = "python")]
171 &PYTHON,
172 #[cfg(feature = "typescript")]
173 &TYPESCRIPT,
174 #[cfg(feature = "typescript")]
175 &TSX,
176 #[cfg(feature = "json")]
177 &JSON,
178 #[cfg(feature = "go")]
179 &GO,
180 #[cfg(feature = "bash")]
181 &BASH,
182 #[cfg(feature = "toml")]
183 &TOML,
184];
185
186pub fn resolve(tag: &str) -> Option<&'static Lang> {
190 let tag = tag
191 .split([' ', ','])
192 .next()
193 .unwrap_or("")
194 .to_ascii_lowercase();
195 LANGS
196 .iter()
197 .copied()
198 .find(|l| l.aliases.contains(&tag.as_str()))
199}
200
201pub fn kind_of(name: &str) -> HighlightKind {
205 match name {
206 "comment" | "comment.documentation" => HighlightKind::Comment,
207 "keyword"
208 | "keyword.function"
209 | "keyword.return"
210 | "keyword.operator"
211 | "keyword.conditional"
212 | "keyword.conditional.ternary"
213 | "keyword.coroutine"
214 | "keyword.directive"
215 | "keyword.exception"
216 | "keyword.import"
217 | "keyword.modifier"
218 | "keyword.repeat"
219 | "keyword.type" => HighlightKind::Keyword,
220 "string" => HighlightKind::String,
221 "string.special" | "string.special.key" | "string.special.url" | "string.regexp"
222 | "character.special" => HighlightKind::StringSpecial,
223 "escape" | "string.escape" => HighlightKind::Escape,
224 "number" => HighlightKind::Number,
225 "boolean" => HighlightKind::Boolean,
226 "type" | "type.interface" => HighlightKind::TypeName,
227 "type.builtin" => HighlightKind::TypeBuiltin,
228 "constructor" => HighlightKind::Constructor,
229 "function" | "function.method" | "function.method.call" | "function.call" => {
230 HighlightKind::Function
231 }
232 "function.builtin" => HighlightKind::FunctionBuiltin,
233 "macro" | "function.macro" => HighlightKind::MacroName,
234 "property" | "property.definition" | "variable.member" => HighlightKind::Property,
235 "constant" | "constant.builtin" | "module" | "module.builtin" => HighlightKind::Constant,
236 "variable" | "variable.builtin" => HighlightKind::Variable,
237 "variable.special" | "self" => HighlightKind::VariableSpecial,
238 "variable.parameter" | "parameter" => HighlightKind::Parameter,
239 "operator" => HighlightKind::Operator,
240 "punctuation" | "punctuation.bracket" | "punctuation.delimiter" | "punctuation.special" => {
241 HighlightKind::Punctuation
242 }
243 "tag" | "tag.builtin" => HighlightKind::Tag,
244 "tag.delimiter" => HighlightKind::Punctuation,
245 "attribute" | "tag.attribute" => HighlightKind::Attribute,
246 "label" => HighlightKind::Label,
247 "invalid" => HighlightKind::Invalid,
248 _ => HighlightKind::Variable,
249 }
250}