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-templ")]
44pub use tree_sitter_templ;
45#[cfg(feature = "tree-sitter-typescript")]
46pub use tree_sitter_typescript;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum HighlightCategory {
51 Attribute,
52 Comment,
53 Constant,
54 Function,
55 Keyword,
56 Number,
57 Operator,
58 PunctuationBracket,
59 PunctuationDelimiter,
60 Property,
61 String,
62 Type,
63 Variable,
64 Inserted,
69 Deleted,
72 Changed,
76}
77
78impl HighlightCategory {
79 pub fn bg_extends_to_line_end(&self) -> bool {
87 matches!(self, Self::Inserted | Self::Deleted | Self::Changed)
88 }
89
90 pub fn from_default_index(index: usize) -> Option<Self> {
92 match index {
93 0 => Some(Self::Attribute),
94 1 => Some(Self::Comment),
95 2 => Some(Self::Constant),
96 3 => Some(Self::Function),
97 4 => Some(Self::Keyword),
98 5 => Some(Self::Number),
99 6 => Some(Self::Operator),
100 7 => Some(Self::PunctuationBracket),
101 8 => Some(Self::PunctuationDelimiter),
102 9 => Some(Self::Property),
103 10 => Some(Self::String),
104 11 => Some(Self::Type),
105 12 => Some(Self::Variable),
106 _ => None,
107 }
108 }
109
110 pub fn from_typescript_index(index: usize) -> Option<Self> {
112 match index {
113 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::PunctuationBracket), 14 => Some(Self::PunctuationDelimiter), 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,
137 }
138 }
139
140 pub fn theme_key(&self) -> &'static str {
142 match self {
143 Self::Keyword => "syntax.keyword",
144 Self::String => "syntax.string",
145 Self::Comment => "syntax.comment",
146 Self::Function => "syntax.function",
147 Self::Type => "syntax.type",
148 Self::Variable | Self::Property => "syntax.variable",
149 Self::Constant | Self::Number | Self::Attribute => "syntax.constant",
150 Self::Operator => "syntax.operator",
151 Self::PunctuationBracket => "syntax.punctuation_bracket",
152 Self::PunctuationDelimiter => "syntax.punctuation_delimiter",
153 Self::Inserted => "editor.diff_add_bg",
158 Self::Deleted => "editor.diff_remove_bg",
159 Self::Changed => "editor.diff_modify_bg",
160 }
161 }
162
163 pub fn display_name(&self) -> &'static str {
165 match self {
166 Self::Attribute => "Attribute",
167 Self::Comment => "Comment",
168 Self::Constant => "Constant",
169 Self::Function => "Function",
170 Self::Keyword => "Keyword",
171 Self::Number => "Number",
172 Self::Operator => "Operator",
173 Self::PunctuationBracket => "Punctuation Bracket",
174 Self::PunctuationDelimiter => "Punctuation Delimiter",
175 Self::Property => "Property",
176 Self::String => "String",
177 Self::Type => "Type",
178 Self::Variable => "Variable",
179 Self::Inserted => "Diff Inserted",
180 Self::Deleted => "Diff Deleted",
181 Self::Changed => "Diff Changed",
182 }
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188pub enum Language {
189 Rust,
190 Python,
191 JavaScript,
192 TypeScript,
193 HTML,
194 CSS,
195 C,
196 Cpp,
197 Go,
198 Json,
199 Jsonc,
200 Java,
201 CSharp,
202 Php,
203 Ruby,
204 Bash,
205 Lua,
206 Pascal,
207 Odin,
208 Templ,
209}
210
211impl Language {
212 pub fn from_path(path: &Path) -> Option<Self> {
218 let ext = path.extension()?.to_str()?;
219 Self::all()
220 .iter()
221 .find(|lang| lang.extensions().contains(&ext))
222 .copied()
223 }
224
225 pub fn highlight_config(&self) -> Result<HighlightConfiguration, String> {
227 match self {
228 Self::Rust => {
229 #[cfg(feature = "tree-sitter-rust")]
230 {
231 let mut config = HighlightConfiguration::new(
232 tree_sitter_rust::LANGUAGE.into(),
233 "rust",
234 tree_sitter_rust::HIGHLIGHTS_QUERY,
235 "",
236 "",
237 )
238 .map_err(|e| format!("Failed to create Rust highlight config: {e}"))?;
239 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
240 Ok(config)
241 }
242 #[cfg(not(feature = "tree-sitter-rust"))]
243 Err("Rust language support not enabled".to_string())
244 }
245 Self::Python => {
246 #[cfg(feature = "tree-sitter-python")]
247 {
248 let mut config = HighlightConfiguration::new(
249 tree_sitter_python::LANGUAGE.into(),
250 "python",
251 tree_sitter_python::HIGHLIGHTS_QUERY,
252 "",
253 "",
254 )
255 .map_err(|e| format!("Failed to create Python highlight config: {e}"))?;
256 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
257 Ok(config)
258 }
259 #[cfg(not(feature = "tree-sitter-python"))]
260 Err("Python language support not enabled".to_string())
261 }
262 Self::JavaScript => {
263 #[cfg(feature = "tree-sitter-javascript")]
264 {
265 let mut config = HighlightConfiguration::new(
266 tree_sitter_javascript::LANGUAGE.into(),
267 "javascript",
268 tree_sitter_javascript::HIGHLIGHT_QUERY,
269 "",
270 "",
271 )
272 .map_err(|e| format!("Failed to create JavaScript highlight config: {e}"))?;
273 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
274 Ok(config)
275 }
276 #[cfg(not(feature = "tree-sitter-javascript"))]
277 Err("JavaScript language support not enabled".to_string())
278 }
279 Self::TypeScript => {
280 #[cfg(all(feature = "tree-sitter-typescript", feature = "tree-sitter-javascript"))]
281 {
282 let combined_highlights = format!(
283 "{}\n{}",
284 tree_sitter_typescript::HIGHLIGHTS_QUERY,
285 tree_sitter_javascript::HIGHLIGHT_QUERY
286 );
287 let mut config = HighlightConfiguration::new(
288 tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
289 "typescript",
290 &combined_highlights,
291 "",
292 tree_sitter_typescript::LOCALS_QUERY,
293 )
294 .map_err(|e| format!("Failed to create TypeScript highlight config: {e}"))?;
295 config.configure(TYPESCRIPT_HIGHLIGHT_CAPTURES);
296 Ok(config)
297 }
298 #[cfg(not(all(
299 feature = "tree-sitter-typescript",
300 feature = "tree-sitter-javascript"
301 )))]
302 Err("TypeScript language support not enabled".to_string())
303 }
304 Self::HTML => {
305 #[cfg(feature = "tree-sitter-html")]
306 {
307 let mut config = HighlightConfiguration::new(
308 tree_sitter_html::LANGUAGE.into(),
309 "html",
310 tree_sitter_html::HIGHLIGHTS_QUERY,
311 "",
312 "",
313 )
314 .map_err(|e| format!("Failed to create HTML highlight config: {e}"))?;
315 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
316 Ok(config)
317 }
318 #[cfg(not(feature = "tree-sitter-html"))]
319 Err("HTML language support not enabled".to_string())
320 }
321 Self::CSS => {
322 #[cfg(feature = "tree-sitter-css")]
323 {
324 let mut config = HighlightConfiguration::new(
325 tree_sitter_css::LANGUAGE.into(),
326 "css",
327 tree_sitter_css::HIGHLIGHTS_QUERY,
328 "",
329 "",
330 )
331 .map_err(|e| format!("Failed to create CSS highlight config: {e}"))?;
332 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
333 Ok(config)
334 }
335 #[cfg(not(feature = "tree-sitter-css"))]
336 Err("CSS language support not enabled".to_string())
337 }
338 Self::C => {
339 #[cfg(feature = "tree-sitter-c")]
340 {
341 let mut config = HighlightConfiguration::new(
342 tree_sitter_c::LANGUAGE.into(),
343 "c",
344 tree_sitter_c::HIGHLIGHT_QUERY,
345 "",
346 "",
347 )
348 .map_err(|e| format!("Failed to create C highlight config: {e}"))?;
349 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
350 Ok(config)
351 }
352 #[cfg(not(feature = "tree-sitter-c"))]
353 Err("C language support not enabled".to_string())
354 }
355 Self::Cpp => {
356 #[cfg(feature = "tree-sitter-cpp")]
357 {
358 let mut config = HighlightConfiguration::new(
359 tree_sitter_cpp::LANGUAGE.into(),
360 "cpp",
361 tree_sitter_cpp::HIGHLIGHT_QUERY,
362 "",
363 "",
364 )
365 .map_err(|e| format!("Failed to create C++ highlight config: {e}"))?;
366 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
367 Ok(config)
368 }
369 #[cfg(not(feature = "tree-sitter-cpp"))]
370 Err("C++ language support not enabled".to_string())
371 }
372 Self::Go => {
373 #[cfg(feature = "tree-sitter-go")]
374 {
375 let mut config = HighlightConfiguration::new(
376 tree_sitter_go::LANGUAGE.into(),
377 "go",
378 tree_sitter_go::HIGHLIGHTS_QUERY,
379 "",
380 "",
381 )
382 .map_err(|e| format!("Failed to create Go highlight config: {e}"))?;
383 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
384 Ok(config)
385 }
386 #[cfg(not(feature = "tree-sitter-go"))]
387 Err("Go language support not enabled".to_string())
388 }
389 Self::Json => {
390 #[cfg(feature = "tree-sitter-json")]
391 {
392 let mut config = HighlightConfiguration::new(
393 tree_sitter_json::LANGUAGE.into(),
394 "json",
395 tree_sitter_json::HIGHLIGHTS_QUERY,
396 "",
397 "",
398 )
399 .map_err(|e| format!("Failed to create JSON highlight config: {e}"))?;
400 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
401 Ok(config)
402 }
403 #[cfg(not(feature = "tree-sitter-json"))]
404 Err("JSON language support not enabled".to_string())
405 }
406 Self::Jsonc => {
407 #[cfg(feature = "tree-sitter-json")]
412 {
413 let mut config = HighlightConfiguration::new(
414 tree_sitter_json::LANGUAGE.into(),
415 "jsonc",
416 tree_sitter_json::HIGHLIGHTS_QUERY,
417 "",
418 "",
419 )
420 .map_err(|e| format!("Failed to create JSONC highlight config: {e}"))?;
421 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
422 Ok(config)
423 }
424 #[cfg(not(feature = "tree-sitter-json"))]
425 Err("JSONC language support not enabled".to_string())
426 }
427 Self::Java => {
428 #[cfg(feature = "tree-sitter-java")]
429 {
430 let mut config = HighlightConfiguration::new(
431 tree_sitter_java::LANGUAGE.into(),
432 "java",
433 tree_sitter_java::HIGHLIGHTS_QUERY,
434 "",
435 "",
436 )
437 .map_err(|e| format!("Failed to create Java highlight config: {e}"))?;
438 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
439 Ok(config)
440 }
441 #[cfg(not(feature = "tree-sitter-java"))]
442 Err("Java language support not enabled".to_string())
443 }
444 Self::CSharp => {
445 #[cfg(feature = "tree-sitter-c-sharp")]
446 {
447 let mut config = HighlightConfiguration::new(
448 tree_sitter_c_sharp::LANGUAGE.into(),
449 "c_sharp",
450 "",
451 "",
452 "",
453 )
454 .map_err(|e| format!("Failed to create C# highlight config: {e}"))?;
455 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
456 Ok(config)
457 }
458 #[cfg(not(feature = "tree-sitter-c-sharp"))]
459 Err("C# language support not enabled".to_string())
460 }
461 Self::Php => {
462 #[cfg(feature = "tree-sitter-php")]
463 {
464 let mut config = HighlightConfiguration::new(
465 tree_sitter_php::LANGUAGE_PHP.into(),
466 "php",
467 tree_sitter_php::HIGHLIGHTS_QUERY,
468 "",
469 "",
470 )
471 .map_err(|e| format!("Failed to create PHP highlight config: {e}"))?;
472 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
473 Ok(config)
474 }
475 #[cfg(not(feature = "tree-sitter-php"))]
476 Err("PHP language support not enabled".to_string())
477 }
478 Self::Ruby => {
479 #[cfg(feature = "tree-sitter-ruby")]
480 {
481 let mut config = HighlightConfiguration::new(
482 tree_sitter_ruby::LANGUAGE.into(),
483 "ruby",
484 tree_sitter_ruby::HIGHLIGHTS_QUERY,
485 "",
486 "",
487 )
488 .map_err(|e| format!("Failed to create Ruby highlight config: {e}"))?;
489 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
490 Ok(config)
491 }
492 #[cfg(not(feature = "tree-sitter-ruby"))]
493 Err("Ruby language support not enabled".to_string())
494 }
495 Self::Bash => {
496 #[cfg(feature = "tree-sitter-bash")]
497 {
498 let mut config = HighlightConfiguration::new(
499 tree_sitter_bash::LANGUAGE.into(),
500 "bash",
501 tree_sitter_bash::HIGHLIGHT_QUERY,
502 "",
503 "",
504 )
505 .map_err(|e| format!("Failed to create Bash highlight config: {e}"))?;
506 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
507 Ok(config)
508 }
509 #[cfg(not(feature = "tree-sitter-bash"))]
510 Err("Bash language support not enabled".to_string())
511 }
512 Self::Lua => {
513 #[cfg(feature = "tree-sitter-lua")]
514 {
515 let mut config = HighlightConfiguration::new(
516 tree_sitter_lua::LANGUAGE.into(),
517 "lua",
518 tree_sitter_lua::HIGHLIGHTS_QUERY,
519 "",
520 "",
521 )
522 .map_err(|e| format!("Failed to create Lua highlight config: {e}"))?;
523 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
524 Ok(config)
525 }
526 #[cfg(not(feature = "tree-sitter-lua"))]
527 Err("Lua language support not enabled".to_string())
528 }
529 Self::Pascal => {
530 #[cfg(feature = "tree-sitter-pascal")]
531 {
532 let mut config = HighlightConfiguration::new(
533 tree_sitter_pascal::LANGUAGE.into(),
534 "pascal",
535 "",
536 "",
537 "",
538 )
539 .map_err(|e| format!("Failed to create Pascal highlight config: {e}"))?;
540 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
541 Ok(config)
542 }
543 #[cfg(not(feature = "tree-sitter-pascal"))]
544 Err("Pascal language support not enabled".to_string())
545 }
546 Self::Odin => {
547 #[cfg(feature = "tree-sitter-odin")]
548 {
549 let mut config = HighlightConfiguration::new(
550 tree_sitter_odin::LANGUAGE.into(),
551 "odin",
552 "",
553 "",
554 "",
555 )
556 .map_err(|e| format!("Failed to create Odin highlight config: {e}"))?;
557 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
558 Ok(config)
559 }
560 #[cfg(not(feature = "tree-sitter-odin"))]
561 Err("Odin language support not enabled".to_string())
562 }
563 Self::Templ => {
564 #[cfg(feature = "tree-sitter-templ")]
569 {
570 let combined_highlights = format!(
571 "{}\n{}",
572 tree_sitter_go::HIGHLIGHTS_QUERY,
573 TEMPL_HIGHLIGHTS_QUERY,
574 );
575 let mut config = HighlightConfiguration::new(
576 tree_sitter_templ::LANGUAGE.into(),
577 "templ",
578 &combined_highlights,
579 "",
580 "",
581 )
582 .map_err(|e| format!("Failed to create Templ highlight config: {e}"))?;
583 config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
584 Ok(config)
585 }
586 #[cfg(not(feature = "tree-sitter-templ"))]
587 Err("Templ language support not enabled".to_string())
588 }
589 }
590 }
591
592 pub fn highlight_category(&self, index: usize) -> Option<HighlightCategory> {
594 match self {
595 Self::TypeScript => HighlightCategory::from_typescript_index(index),
596 _ => HighlightCategory::from_default_index(index),
597 }
598 }
599}
600
601impl Language {
602 pub fn all() -> &'static [Language] {
604 &[
605 Language::Rust,
606 Language::Python,
607 Language::JavaScript,
608 Language::TypeScript,
609 Language::HTML,
610 Language::CSS,
611 Language::C,
612 Language::Cpp,
613 Language::Go,
614 Language::Json,
615 Language::Jsonc,
616 Language::Java,
617 Language::CSharp,
618 Language::Php,
619 Language::Ruby,
620 Language::Bash,
621 Language::Lua,
622 Language::Pascal,
623 Language::Odin,
624 Language::Templ,
625 ]
626 }
627
628 pub fn id(&self) -> &'static str {
630 match self {
631 Self::Rust => "rust",
632 Self::Python => "python",
633 Self::JavaScript => "javascript",
634 Self::TypeScript => "typescript",
635 Self::HTML => "html",
636 Self::CSS => "css",
637 Self::C => "c",
638 Self::Cpp => "cpp",
639 Self::Go => "go",
640 Self::Json => "json",
641 Self::Jsonc => "jsonc",
642 Self::Java => "java",
643 Self::CSharp => "csharp",
644 Self::Php => "php",
645 Self::Ruby => "ruby",
646 Self::Bash => "bash",
647 Self::Lua => "lua",
648 Self::Pascal => "pascal",
649 Self::Odin => "odin",
650 Self::Templ => "templ",
651 }
652 }
653
654 pub fn lsp_language_id(&self, path: &Path) -> &'static str {
662 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
663 match (self, ext) {
664 (Self::TypeScript, "tsx") => "typescriptreact",
665 (Self::JavaScript, "jsx") => "javascriptreact",
666 _ => self.id(),
667 }
668 }
669
670 pub fn extensions(&self) -> &'static [&'static str] {
676 match self {
677 Self::Rust => &["rs"],
678 Self::Python => &["py"],
679 Self::JavaScript => &["js", "jsx", "mjs", "cjs"],
680 Self::TypeScript => &["ts", "tsx", "mts", "cts"],
681 Self::HTML => &["html"],
682 Self::CSS => &["css"],
683 Self::C => &["c", "h"],
684 Self::Cpp => &["cpp", "hpp", "cc", "hh", "cxx", "hxx", "cppm", "ixx"],
685 Self::Go => &["go"],
686 Self::Json => &["json"],
687 Self::Jsonc => &["jsonc"],
688 Self::Java => &["java"],
689 Self::CSharp => &["cs"],
690 Self::Php => &["php"],
691 Self::Ruby => &["rb"],
692 Self::Bash => &["sh", "bash"],
693 Self::Lua => &["lua"],
694 Self::Pascal => &["pas", "p"],
695 Self::Odin => &["odin"],
696 Self::Templ => &["templ"],
697 }
698 }
699
700 pub fn display_name(&self) -> &'static str {
702 match self {
703 Self::Rust => "Rust",
704 Self::Python => "Python",
705 Self::JavaScript => "JavaScript",
706 Self::TypeScript => "TypeScript",
707 Self::HTML => "HTML",
708 Self::CSS => "CSS",
709 Self::C => "C",
710 Self::Cpp => "C++",
711 Self::Go => "Go",
712 Self::Json => "JSON",
713 Self::Jsonc => "JSON with Comments",
714 Self::Java => "Java",
715 Self::CSharp => "C#",
716 Self::Php => "PHP",
717 Self::Ruby => "Ruby",
718 Self::Bash => "Bash",
719 Self::Lua => "Lua",
720 Self::Pascal => "Pascal",
721 Self::Odin => "Odin",
722 Self::Templ => "Templ",
723 }
724 }
725
726 pub fn from_id(id: &str) -> Option<Self> {
728 let id_lower = id.to_lowercase();
729 match id_lower.as_str() {
730 "rust" => Some(Self::Rust),
731 "python" => Some(Self::Python),
732 "javascript" => Some(Self::JavaScript),
733 "typescript" => Some(Self::TypeScript),
734 "html" => Some(Self::HTML),
735 "css" => Some(Self::CSS),
736 "c" => Some(Self::C),
737 "cpp" | "c++" => Some(Self::Cpp),
738 "go" => Some(Self::Go),
739 "json" => Some(Self::Json),
740 "jsonc" => Some(Self::Jsonc),
741 "java" => Some(Self::Java),
742 "c_sharp" | "c#" | "csharp" => Some(Self::CSharp),
743 "php" => Some(Self::Php),
744 "ruby" => Some(Self::Ruby),
745 "bash" => Some(Self::Bash),
746 "lua" => Some(Self::Lua),
747 "pascal" => Some(Self::Pascal),
748 "odin" => Some(Self::Odin),
749 "templ" => Some(Self::Templ),
750 _ => None,
751 }
752 }
753
754 pub fn from_name(name: &str) -> Option<Self> {
763 for lang in Self::all() {
765 if lang.display_name() == name {
766 return Some(*lang);
767 }
768 }
769
770 let name_lower = name.to_lowercase();
772 match name_lower.as_str() {
773 "rust" => Some(Self::Rust),
774 "python" => Some(Self::Python),
775 "javascript" | "javascript (babel)" => Some(Self::JavaScript),
776 "typescript" | "typescriptreact" => Some(Self::TypeScript),
777 "html" => Some(Self::HTML),
778 "css" => Some(Self::CSS),
779 "c" => Some(Self::C),
780 "c++" => Some(Self::Cpp),
781 "go" | "golang" => Some(Self::Go),
782 "json" => Some(Self::Json),
783 "jsonc" | "json with comments" => Some(Self::Jsonc),
784 "java" => Some(Self::Java),
785 "c#" => Some(Self::CSharp),
786 "php" => Some(Self::Php),
787 "ruby" => Some(Self::Ruby),
788 "lua" => Some(Self::Lua),
789 "pascal" => Some(Self::Pascal),
790 "odin" => Some(Self::Odin),
791 "templ" => Some(Self::Templ),
792 _ => {
793 if name_lower.contains("bash") || name_lower.contains("shell") {
795 return Some(Self::Bash);
796 }
797 None
798 }
799 }
800 }
801}
802
803impl std::fmt::Display for Language {
804 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
805 write!(f, "{}", self.id())
806 }
807}
808
809const DEFAULT_HIGHLIGHT_CAPTURES: &[&str] = &[
810 "attribute",
811 "comment",
812 "constant",
813 "function",
814 "keyword",
815 "number",
816 "operator",
817 "punctuation.bracket",
818 "punctuation.delimiter",
819 "property",
820 "string",
821 "type",
822 "variable",
823];
824
825#[cfg(feature = "tree-sitter-templ")]
836const TEMPL_HIGHLIGHTS_QUERY: &str = include_str!("../queries/templ/highlights.scm");
837
838const TYPESCRIPT_HIGHLIGHT_CAPTURES: &[&str] = &[
839 "attribute",
840 "comment",
841 "constant",
842 "constant.builtin",
843 "constructor",
844 "embedded",
845 "function",
846 "function.builtin",
847 "function.method",
848 "keyword",
849 "number",
850 "operator",
851 "property",
852 "punctuation.bracket",
853 "punctuation.delimiter",
854 "punctuation.special",
855 "string",
856 "string.special",
857 "type",
858 "type.builtin",
859 "variable",
860 "variable.builtin",
861 "variable.parameter",
862];
863
864#[cfg(test)]
865mod tests {
866 use super::*;
867 use std::path::Path;
868
869 #[test]
870 fn test_lsp_language_id_tsx() {
871 let lang = Language::TypeScript;
872 assert_eq!(
873 lang.lsp_language_id(Path::new("app.tsx")),
874 "typescriptreact"
875 );
876 }
877
878 #[test]
879 fn test_lsp_language_id_ts() {
880 let lang = Language::TypeScript;
881 assert_eq!(lang.lsp_language_id(Path::new("app.ts")), "typescript");
882 }
883
884 #[test]
885 fn test_lsp_language_id_jsx() {
886 let lang = Language::JavaScript;
887 assert_eq!(
888 lang.lsp_language_id(Path::new("component.jsx")),
889 "javascriptreact"
890 );
891 }
892
893 #[test]
894 fn test_lsp_language_id_js() {
895 let lang = Language::JavaScript;
896 assert_eq!(lang.lsp_language_id(Path::new("app.js")), "javascript");
897 }
898
899 #[test]
900 fn test_lsp_language_id_csharp() {
901 let lang = Language::CSharp;
902 assert_eq!(lang.lsp_language_id(Path::new("main.cs")), "csharp");
903 }
904
905 #[test]
906 fn test_lsp_language_id_other_languages() {
907 assert_eq!(Language::Rust.lsp_language_id(Path::new("main.rs")), "rust");
908 assert_eq!(
909 Language::Python.lsp_language_id(Path::new("script.py")),
910 "python"
911 );
912 assert_eq!(Language::Go.lsp_language_id(Path::new("main.go")), "go");
913 }
914
915 #[test]
916 fn test_csharp_id_matches_config_key() {
917 assert_eq!(Language::CSharp.id(), "csharp");
920 }
921
922 #[test]
923 fn test_templ_detected_from_extension() {
924 let path = Path::new("home.templ");
925 assert!(matches!(Language::from_path(path), Some(Language::Templ)));
926 }
927
928 #[test]
929 #[cfg(feature = "tree-sitter-templ")]
930 fn test_templ_highlight_config_builds() {
931 Language::Templ
935 .highlight_config()
936 .expect("Templ highlight config should build");
937 }
938
939 #[test]
943 fn test_from_path_matches_extensions() {
944 for lang in Language::all() {
945 for ext in lang.extensions() {
946 let path = std::path::PathBuf::from(format!("x.{}", ext));
947 let detected = Language::from_path(&path).unwrap_or_else(|| {
948 panic!(
949 "extension .{} listed by {:?} but from_path returned None",
950 ext, lang
951 )
952 });
953 assert_eq!(
954 detected, *lang,
955 "extension .{} listed by {:?} but from_path returned {:?}",
956 ext, lang, detected
957 );
958 }
959 }
960 }
961}