1use std::path::Path;
2
3pub use tree_sitter;
5pub use tree_sitter_highlight;
6pub use tree_sitter_highlight::HighlightConfiguration;
7
8#[cfg(feature = "tree-sitter-bash")]
10pub use tree_sitter_bash;
11#[cfg(feature = "tree-sitter-c")]
12pub use tree_sitter_c;
13#[cfg(feature = "tree-sitter-c-sharp")]
14pub use tree_sitter_c_sharp;
15#[cfg(feature = "tree-sitter-cpp")]
16pub use tree_sitter_cpp;
17#[cfg(feature = "tree-sitter-css")]
18pub use tree_sitter_css;
19#[cfg(feature = "tree-sitter-go")]
20pub use tree_sitter_go;
21#[cfg(feature = "tree-sitter-html")]
22pub use tree_sitter_html;
23#[cfg(feature = "tree-sitter-java")]
24pub use tree_sitter_java;
25#[cfg(feature = "tree-sitter-javascript")]
26pub use tree_sitter_javascript;
27#[cfg(feature = "tree-sitter-json")]
28pub use tree_sitter_json;
29#[cfg(feature = "tree-sitter-lua")]
30pub use tree_sitter_lua;
31#[cfg(feature = "tree-sitter-odin")]
32pub use tree_sitter_odin;
33#[cfg(feature = "tree-sitter-pascal")]
34pub use tree_sitter_pascal;
35#[cfg(feature = "tree-sitter-php")]
36pub use tree_sitter_php;
37#[cfg(feature = "tree-sitter-python")]
38pub use tree_sitter_python;
39#[cfg(feature = "tree-sitter-ruby")]
40pub use tree_sitter_ruby;
41#[cfg(feature = "tree-sitter-rust")]
42pub use tree_sitter_rust;
43#[cfg(feature = "tree-sitter-typescript")]
44pub use tree_sitter_typescript;
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum HighlightCategory {
49 Attribute,
50 Comment,
51 Constant,
52 Function,
53 Keyword,
54 Number,
55 Operator,
56 Property,
57 String,
58 Type,
59 Variable,
60}
61
62impl HighlightCategory {
63 pub fn from_default_index(index: usize) -> Option<Self> {
65 match index {
66 0 => Some(Self::Attribute),
67 1 => Some(Self::Comment),
68 2 => Some(Self::Constant),
69 3 => Some(Self::Function),
70 4 => Some(Self::Keyword),
71 5 => Some(Self::Number),
72 6 => Some(Self::Operator),
73 7 => Some(Self::Property),
74 8 => Some(Self::String),
75 9 => Some(Self::Type),
76 10 => Some(Self::Variable),
77 _ => None,
78 }
79 }
80
81 pub fn from_typescript_index(index: usize) -> Option<Self> {
83 match index {
84 0 => Some(Self::Attribute), 1 => Some(Self::Comment), 2 => Some(Self::Constant), 3 => Some(Self::Constant), 4 => Some(Self::Type), 5 => Some(Self::String), 6 => Some(Self::Function), 7 => Some(Self::Function), 8 => Some(Self::Function), 9 => Some(Self::Keyword), 10 => Some(Self::Number), 11 => Some(Self::Operator), 12 => Some(Self::Property), 13 => Some(Self::Operator), 14 => Some(Self::Operator), 15 => Some(Self::Constant), 16 => Some(Self::String), 17 => Some(Self::String), 18 => Some(Self::Type), 19 => Some(Self::Type), 20 => Some(Self::Variable), 21 => Some(Self::Constant), 22 => Some(Self::Variable), _ => None,
108 }
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum Language {
115 Rust,
116 Python,
117 JavaScript,
118 TypeScript,
119 HTML,
120 CSS,
121 C,
122 Cpp,
123 Go,
124 Json,
125 Java,
126 CSharp,
127 Php,
128 Ruby,
129 Bash,
130 Lua,
131 Pascal,
132 Odin,
133}
134
135impl Language {
136 pub fn from_path(path: &Path) -> Option<Self> {
138 match path.extension()?.to_str()? {
139 "rs" => Some(Language::Rust),
140 "py" => Some(Language::Python),
141 "js" | "jsx" => Some(Language::JavaScript),
142 "ts" | "tsx" => Some(Language::TypeScript),
143 "html" => Some(Language::HTML),
144 "css" => Some(Language::CSS),
145 "c" | "h" => Some(Language::C),
146 "cpp" | "hpp" | "cc" | "hh" | "cxx" | "hxx" | "cppm" | "ixx" => Some(Language::Cpp),
147 "go" => Some(Language::Go),
148 "json" => Some(Language::Json),
149 "java" => Some(Language::Java),
150 "cs" => Some(Language::CSharp),
151 "php" => Some(Language::Php),
152 "rb" => Some(Language::Ruby),
153 "sh" | "bash" => Some(Language::Bash),
154 "lua" => Some(Language::Lua),
155 "pas" | "p" => Some(Language::Pascal),
156 "odin" => Some(Language::Odin),
157 _ => None,
158 }
159 }
160
161 pub fn highlight_config(&self) -> Result<HighlightConfiguration, String> {
163 match self {
164 Self::Rust => {
165 #[cfg(feature = "tree-sitter-rust")]
166 {
167 let mut config = HighlightConfiguration::new(
168 tree_sitter_rust::LANGUAGE.into(),
169 "rust",
170 tree_sitter_rust::HIGHLIGHTS_QUERY,
171 "",
172 "",
173 )
174 .map_err(|e| format!("Failed to create Rust highlight config: {e}"))?;
175 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
176 Ok(config)
177 }
178 #[cfg(not(feature = "tree-sitter-rust"))]
179 Err("Rust language support not enabled".to_string())
180 }
181 Self::Python => {
182 #[cfg(feature = "tree-sitter-python")]
183 {
184 let mut config = HighlightConfiguration::new(
185 tree_sitter_python::LANGUAGE.into(),
186 "python",
187 tree_sitter_python::HIGHLIGHTS_QUERY,
188 "",
189 "",
190 )
191 .map_err(|e| format!("Failed to create Python highlight config: {e}"))?;
192 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
193 Ok(config)
194 }
195 #[cfg(not(feature = "tree-sitter-python"))]
196 Err("Python language support not enabled".to_string())
197 }
198 Self::JavaScript => {
199 #[cfg(feature = "tree-sitter-javascript")]
200 {
201 let mut config = HighlightConfiguration::new(
202 tree_sitter_javascript::LANGUAGE.into(),
203 "javascript",
204 tree_sitter_javascript::HIGHLIGHT_QUERY,
205 "",
206 "",
207 )
208 .map_err(|e| format!("Failed to create JavaScript highlight config: {e}"))?;
209 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
210 Ok(config)
211 }
212 #[cfg(not(feature = "tree-sitter-javascript"))]
213 Err("JavaScript language support not enabled".to_string())
214 }
215 Self::TypeScript => {
216 #[cfg(all(feature = "tree-sitter-typescript", feature = "tree-sitter-javascript"))]
217 {
218 let combined_highlights = format!(
219 "{}\n{}",
220 tree_sitter_typescript::HIGHLIGHTS_QUERY,
221 tree_sitter_javascript::HIGHLIGHT_QUERY
222 );
223 let mut config = HighlightConfiguration::new(
224 tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
225 "typescript",
226 &combined_highlights,
227 "",
228 tree_sitter_typescript::LOCALS_QUERY,
229 )
230 .map_err(|e| format!("Failed to create TypeScript highlight config: {e}"))?;
231 config.configure(TYPESCRIPT_HIGHLIGHT_CAPTURES);
232 Ok(config)
233 }
234 #[cfg(not(all(
235 feature = "tree-sitter-typescript",
236 feature = "tree-sitter-javascript"
237 )))]
238 Err("TypeScript language support not enabled".to_string())
239 }
240 Self::HTML => {
241 #[cfg(feature = "tree-sitter-html")]
242 {
243 let mut config = HighlightConfiguration::new(
244 tree_sitter_html::LANGUAGE.into(),
245 "html",
246 tree_sitter_html::HIGHLIGHTS_QUERY,
247 "",
248 "",
249 )
250 .map_err(|e| format!("Failed to create HTML highlight config: {e}"))?;
251 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
252 Ok(config)
253 }
254 #[cfg(not(feature = "tree-sitter-html"))]
255 Err("HTML language support not enabled".to_string())
256 }
257 Self::CSS => {
258 #[cfg(feature = "tree-sitter-css")]
259 {
260 let mut config = HighlightConfiguration::new(
261 tree_sitter_css::LANGUAGE.into(),
262 "css",
263 tree_sitter_css::HIGHLIGHTS_QUERY,
264 "",
265 "",
266 )
267 .map_err(|e| format!("Failed to create CSS highlight config: {e}"))?;
268 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
269 Ok(config)
270 }
271 #[cfg(not(feature = "tree-sitter-css"))]
272 Err("CSS language support not enabled".to_string())
273 }
274 Self::C => {
275 #[cfg(feature = "tree-sitter-c")]
276 {
277 let mut config = HighlightConfiguration::new(
278 tree_sitter_c::LANGUAGE.into(),
279 "c",
280 tree_sitter_c::HIGHLIGHT_QUERY,
281 "",
282 "",
283 )
284 .map_err(|e| format!("Failed to create C highlight config: {e}"))?;
285 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
286 Ok(config)
287 }
288 #[cfg(not(feature = "tree-sitter-c"))]
289 Err("C language support not enabled".to_string())
290 }
291 Self::Cpp => {
292 #[cfg(feature = "tree-sitter-cpp")]
293 {
294 let mut config = HighlightConfiguration::new(
295 tree_sitter_cpp::LANGUAGE.into(),
296 "cpp",
297 tree_sitter_cpp::HIGHLIGHT_QUERY,
298 "",
299 "",
300 )
301 .map_err(|e| format!("Failed to create C++ highlight config: {e}"))?;
302 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
303 Ok(config)
304 }
305 #[cfg(not(feature = "tree-sitter-cpp"))]
306 Err("C++ language support not enabled".to_string())
307 }
308 Self::Go => {
309 #[cfg(feature = "tree-sitter-go")]
310 {
311 let mut config = HighlightConfiguration::new(
312 tree_sitter_go::LANGUAGE.into(),
313 "go",
314 tree_sitter_go::HIGHLIGHTS_QUERY,
315 "",
316 "",
317 )
318 .map_err(|e| format!("Failed to create Go highlight config: {e}"))?;
319 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
320 Ok(config)
321 }
322 #[cfg(not(feature = "tree-sitter-go"))]
323 Err("Go language support not enabled".to_string())
324 }
325 Self::Json => {
326 #[cfg(feature = "tree-sitter-json")]
327 {
328 let mut config = HighlightConfiguration::new(
329 tree_sitter_json::LANGUAGE.into(),
330 "json",
331 tree_sitter_json::HIGHLIGHTS_QUERY,
332 "",
333 "",
334 )
335 .map_err(|e| format!("Failed to create JSON highlight config: {e}"))?;
336 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
337 Ok(config)
338 }
339 #[cfg(not(feature = "tree-sitter-json"))]
340 Err("JSON language support not enabled".to_string())
341 }
342 Self::Java => {
343 #[cfg(feature = "tree-sitter-java")]
344 {
345 let mut config = HighlightConfiguration::new(
346 tree_sitter_java::LANGUAGE.into(),
347 "java",
348 tree_sitter_java::HIGHLIGHTS_QUERY,
349 "",
350 "",
351 )
352 .map_err(|e| format!("Failed to create Java highlight config: {e}"))?;
353 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
354 Ok(config)
355 }
356 #[cfg(not(feature = "tree-sitter-java"))]
357 Err("Java language support not enabled".to_string())
358 }
359 Self::CSharp => {
360 #[cfg(feature = "tree-sitter-c-sharp")]
361 {
362 let mut config = HighlightConfiguration::new(
363 tree_sitter_c_sharp::LANGUAGE.into(),
364 "c_sharp",
365 "",
366 "",
367 "",
368 )
369 .map_err(|e| format!("Failed to create C# highlight config: {e}"))?;
370 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
371 Ok(config)
372 }
373 #[cfg(not(feature = "tree-sitter-c-sharp"))]
374 Err("C# language support not enabled".to_string())
375 }
376 Self::Php => {
377 #[cfg(feature = "tree-sitter-php")]
378 {
379 let mut config = HighlightConfiguration::new(
380 tree_sitter_php::LANGUAGE_PHP.into(),
381 "php",
382 tree_sitter_php::HIGHLIGHTS_QUERY,
383 "",
384 "",
385 )
386 .map_err(|e| format!("Failed to create PHP highlight config: {e}"))?;
387 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
388 Ok(config)
389 }
390 #[cfg(not(feature = "tree-sitter-php"))]
391 Err("PHP language support not enabled".to_string())
392 }
393 Self::Ruby => {
394 #[cfg(feature = "tree-sitter-ruby")]
395 {
396 let mut config = HighlightConfiguration::new(
397 tree_sitter_ruby::LANGUAGE.into(),
398 "ruby",
399 tree_sitter_ruby::HIGHLIGHTS_QUERY,
400 "",
401 "",
402 )
403 .map_err(|e| format!("Failed to create Ruby highlight config: {e}"))?;
404 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
405 Ok(config)
406 }
407 #[cfg(not(feature = "tree-sitter-ruby"))]
408 Err("Ruby language support not enabled".to_string())
409 }
410 Self::Bash => {
411 #[cfg(feature = "tree-sitter-bash")]
412 {
413 let mut config = HighlightConfiguration::new(
414 tree_sitter_bash::LANGUAGE.into(),
415 "bash",
416 tree_sitter_bash::HIGHLIGHT_QUERY,
417 "",
418 "",
419 )
420 .map_err(|e| format!("Failed to create Bash highlight config: {e}"))?;
421 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
422 Ok(config)
423 }
424 #[cfg(not(feature = "tree-sitter-bash"))]
425 Err("Bash language support not enabled".to_string())
426 }
427 Self::Lua => {
428 #[cfg(feature = "tree-sitter-lua")]
429 {
430 let mut config = HighlightConfiguration::new(
431 tree_sitter_lua::LANGUAGE.into(),
432 "lua",
433 tree_sitter_lua::HIGHLIGHTS_QUERY,
434 "",
435 "",
436 )
437 .map_err(|e| format!("Failed to create Lua highlight config: {e}"))?;
438 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
439 Ok(config)
440 }
441 #[cfg(not(feature = "tree-sitter-lua"))]
442 Err("Lua language support not enabled".to_string())
443 }
444 Self::Pascal => {
445 #[cfg(feature = "tree-sitter-pascal")]
446 {
447 let mut config = HighlightConfiguration::new(
448 tree_sitter_pascal::LANGUAGE.into(),
449 "pascal",
450 "",
451 "",
452 "",
453 )
454 .map_err(|e| format!("Failed to create Pascal highlight config: {e}"))?;
455 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
456 Ok(config)
457 }
458 #[cfg(not(feature = "tree-sitter-pascal"))]
459 Err("Pascal language support not enabled".to_string())
460 }
461 Self::Odin => {
462 #[cfg(feature = "tree-sitter-odin")]
463 {
464 let mut config = HighlightConfiguration::new(
465 tree_sitter_odin::LANGUAGE.into(),
466 "odin",
467 "",
468 "",
469 "",
470 )
471 .map_err(|e| format!("Failed to create Odin highlight config: {e}"))?;
472 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
473 Ok(config)
474 }
475 #[cfg(not(feature = "tree-sitter-odin"))]
476 Err("Odin language support not enabled".to_string())
477 }
478 }
479 }
480
481 pub fn highlight_category(&self, index: usize) -> Option<HighlightCategory> {
483 match self {
484 Self::TypeScript => HighlightCategory::from_typescript_index(index),
485 _ => HighlightCategory::from_default_index(index),
486 }
487 }
488}
489
490impl Language {
491 pub fn all() -> &'static [Language] {
493 &[
494 Language::Rust,
495 Language::Python,
496 Language::JavaScript,
497 Language::TypeScript,
498 Language::HTML,
499 Language::CSS,
500 Language::C,
501 Language::Cpp,
502 Language::Go,
503 Language::Json,
504 Language::Java,
505 Language::CSharp,
506 Language::Php,
507 Language::Ruby,
508 Language::Bash,
509 Language::Lua,
510 Language::Pascal,
511 Language::Odin,
512 ]
513 }
514
515 pub fn id(&self) -> &'static str {
517 match self {
518 Self::Rust => "rust",
519 Self::Python => "python",
520 Self::JavaScript => "javascript",
521 Self::TypeScript => "typescript",
522 Self::HTML => "html",
523 Self::CSS => "css",
524 Self::C => "c",
525 Self::Cpp => "cpp",
526 Self::Go => "go",
527 Self::Json => "json",
528 Self::Java => "java",
529 Self::CSharp => "c_sharp",
530 Self::Php => "php",
531 Self::Ruby => "ruby",
532 Self::Bash => "bash",
533 Self::Lua => "lua",
534 Self::Pascal => "pascal",
535 Self::Odin => "odin",
536 }
537 }
538
539 pub fn display_name(&self) -> &'static str {
541 match self {
542 Self::Rust => "Rust",
543 Self::Python => "Python",
544 Self::JavaScript => "JavaScript",
545 Self::TypeScript => "TypeScript",
546 Self::HTML => "HTML",
547 Self::CSS => "CSS",
548 Self::C => "C",
549 Self::Cpp => "C++",
550 Self::Go => "Go",
551 Self::Json => "JSON",
552 Self::Java => "Java",
553 Self::CSharp => "C#",
554 Self::Php => "PHP",
555 Self::Ruby => "Ruby",
556 Self::Bash => "Bash",
557 Self::Lua => "Lua",
558 Self::Pascal => "Pascal",
559 Self::Odin => "Odin",
560 }
561 }
562
563 pub fn from_id(id: &str) -> Option<Self> {
565 let id_lower = id.to_lowercase();
566 match id_lower.as_str() {
567 "rust" => Some(Self::Rust),
568 "python" => Some(Self::Python),
569 "javascript" => Some(Self::JavaScript),
570 "typescript" => Some(Self::TypeScript),
571 "html" => Some(Self::HTML),
572 "css" => Some(Self::CSS),
573 "c" => Some(Self::C),
574 "cpp" | "c++" => Some(Self::Cpp),
575 "go" => Some(Self::Go),
576 "json" => Some(Self::Json),
577 "java" => Some(Self::Java),
578 "c_sharp" | "c#" | "csharp" => Some(Self::CSharp),
579 "php" => Some(Self::Php),
580 "ruby" => Some(Self::Ruby),
581 "bash" => Some(Self::Bash),
582 "lua" => Some(Self::Lua),
583 "pascal" => Some(Self::Pascal),
584 "odin" => Some(Self::Odin),
585 _ => None,
586 }
587 }
588
589 pub fn from_name(name: &str) -> Option<Self> {
598 for lang in Self::all() {
600 if lang.display_name() == name {
601 return Some(*lang);
602 }
603 }
604
605 let name_lower = name.to_lowercase();
607 match name_lower.as_str() {
608 "rust" => Some(Self::Rust),
609 "python" => Some(Self::Python),
610 "javascript" | "javascript (babel)" => Some(Self::JavaScript),
611 "typescript" | "typescriptreact" => Some(Self::TypeScript),
612 "html" => Some(Self::HTML),
613 "css" => Some(Self::CSS),
614 "c" => Some(Self::C),
615 "c++" => Some(Self::Cpp),
616 "go" | "golang" => Some(Self::Go),
617 "json" => Some(Self::Json),
618 "java" => Some(Self::Java),
619 "c#" => Some(Self::CSharp),
620 "php" => Some(Self::Php),
621 "ruby" => Some(Self::Ruby),
622 "lua" => Some(Self::Lua),
623 "pascal" => Some(Self::Pascal),
624 "odin" => Some(Self::Odin),
625 _ => {
626 if name_lower.contains("bash") || name_lower.contains("shell") {
628 return Some(Self::Bash);
629 }
630 None
631 }
632 }
633 }
634}
635
636impl std::fmt::Display for Language {
637 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638 write!(f, "{}", self.id())
639 }
640}
641
642const DEFAULT_HIGHLIGHT_CAPTURES: &[&str] = &[
643 "attribute",
644 "comment",
645 "constant",
646 "function",
647 "keyword",
648 "number",
649 "operator",
650 "property",
651 "string",
652 "type",
653 "variable",
654];
655
656const TYPESCRIPT_HIGHLIGHT_CAPTURES: &[&str] = &[
657 "attribute",
658 "comment",
659 "constant",
660 "constant.builtin",
661 "constructor",
662 "embedded",
663 "function",
664 "function.builtin",
665 "function.method",
666 "keyword",
667 "number",
668 "operator",
669 "property",
670 "punctuation.bracket",
671 "punctuation.delimiter",
672 "punctuation.special",
673 "string",
674 "string.special",
675 "type",
676 "type.builtin",
677 "variable",
678 "variable.builtin",
679 "variable.parameter",
680];