1use crate::style::{Style, Theme};
10
11#[cfg(any(
16 feature = "syntax-rust",
17 feature = "syntax-python",
18 feature = "syntax-javascript",
19 feature = "syntax-typescript",
20 feature = "syntax-go",
21 feature = "syntax-bash",
22 feature = "syntax-json",
23 feature = "syntax-toml",
24 feature = "syntax-c",
25 feature = "syntax-cpp",
26 feature = "syntax-java",
27 feature = "syntax-ruby",
28 feature = "syntax-css",
29 feature = "syntax-html",
30 feature = "syntax-yaml",
31))]
32const HIGHLIGHT_NAMES: &[&str] = &[
33 "attribute",
34 "comment",
35 "constant",
36 "constant.builtin",
37 "constructor",
38 "embedded",
39 "function",
40 "function.builtin",
41 "function.macro",
42 "keyword",
43 "module",
44 "number",
45 "operator",
46 "property",
47 "property.builtin",
48 "punctuation",
49 "punctuation.bracket",
50 "punctuation.delimiter",
51 "punctuation.special",
52 "string",
53 "string.special",
54 "tag",
55 "type",
56 "type.builtin",
57 "variable",
58 "variable.builtin",
59 "variable.parameter",
60];
61
62#[cfg(any(
63 feature = "syntax-rust",
64 feature = "syntax-python",
65 feature = "syntax-javascript",
66 feature = "syntax-typescript",
67 feature = "syntax-go",
68 feature = "syntax-bash",
69 feature = "syntax-json",
70 feature = "syntax-toml",
71 feature = "syntax-c",
72 feature = "syntax-cpp",
73 feature = "syntax-java",
74 feature = "syntax-ruby",
75 feature = "syntax-css",
76 feature = "syntax-html",
77 feature = "syntax-yaml",
78))]
79use std::sync::OnceLock;
80
81#[cfg(any(
82 feature = "syntax-rust",
83 feature = "syntax-python",
84 feature = "syntax-javascript",
85 feature = "syntax-typescript",
86 feature = "syntax-go",
87 feature = "syntax-bash",
88 feature = "syntax-json",
89 feature = "syntax-toml",
90 feature = "syntax-c",
91 feature = "syntax-cpp",
92 feature = "syntax-java",
93 feature = "syntax-ruby",
94 feature = "syntax-css",
95 feature = "syntax-html",
96 feature = "syntax-yaml",
97))]
98use tree_sitter_highlight::HighlightConfiguration;
99
100#[cfg(any(
103 feature = "syntax-rust",
104 feature = "syntax-python",
105 feature = "syntax-javascript",
106 feature = "syntax-typescript",
107 feature = "syntax-go",
108 feature = "syntax-bash",
109 feature = "syntax-json",
110 feature = "syntax-toml",
111 feature = "syntax-c",
112 feature = "syntax-cpp",
113 feature = "syntax-java",
114 feature = "syntax-ruby",
115 feature = "syntax-css",
116 feature = "syntax-html",
117 feature = "syntax-yaml",
118))]
119fn get_config(lang: &str) -> Option<&'static HighlightConfiguration> {
120 match lang {
121 #[cfg(feature = "syntax-rust")]
122 "rust" | "rs" => {
123 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
124 CFG.get_or_init(|| {
125 HighlightConfiguration::new(
126 tree_sitter_rust::LANGUAGE.into(),
127 "rust",
128 tree_sitter_rust::HIGHLIGHTS_QUERY,
129 tree_sitter_rust::INJECTIONS_QUERY,
130 "",
131 )
132 .ok()
133 .map(|mut c| {
134 c.configure(HIGHLIGHT_NAMES);
135 c
136 })
137 })
138 .as_ref()
139 }
140
141 #[cfg(feature = "syntax-python")]
142 "python" | "py" => {
143 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
144 CFG.get_or_init(|| {
145 HighlightConfiguration::new(
146 tree_sitter_python::LANGUAGE.into(),
147 "python",
148 tree_sitter_python::HIGHLIGHTS_QUERY,
149 "",
150 "",
151 )
152 .ok()
153 .map(|mut c| {
154 c.configure(HIGHLIGHT_NAMES);
155 c
156 })
157 })
158 .as_ref()
159 }
160
161 #[cfg(feature = "syntax-javascript")]
162 "javascript" | "js" | "jsx" => {
163 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
164 CFG.get_or_init(|| {
165 HighlightConfiguration::new(
166 tree_sitter_javascript::LANGUAGE.into(),
167 "javascript",
168 tree_sitter_javascript::HIGHLIGHT_QUERY,
169 tree_sitter_javascript::INJECTIONS_QUERY,
170 tree_sitter_javascript::LOCALS_QUERY,
171 )
172 .ok()
173 .map(|mut c| {
174 c.configure(HIGHLIGHT_NAMES);
175 c
176 })
177 })
178 .as_ref()
179 }
180
181 #[cfg(feature = "syntax-go")]
182 "go" | "golang" => {
183 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
184 CFG.get_or_init(|| {
185 HighlightConfiguration::new(
186 tree_sitter_go::LANGUAGE.into(),
187 "go",
188 tree_sitter_go::HIGHLIGHTS_QUERY,
189 "",
190 "",
191 )
192 .ok()
193 .map(|mut c| {
194 c.configure(HIGHLIGHT_NAMES);
195 c
196 })
197 })
198 .as_ref()
199 }
200
201 #[cfg(feature = "syntax-bash")]
202 "bash" | "sh" | "shell" | "zsh" => {
203 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
204 CFG.get_or_init(|| {
205 HighlightConfiguration::new(
206 tree_sitter_bash::LANGUAGE.into(),
207 "bash",
208 tree_sitter_bash::HIGHLIGHT_QUERY,
209 "",
210 "",
211 )
212 .ok()
213 .map(|mut c| {
214 c.configure(HIGHLIGHT_NAMES);
215 c
216 })
217 })
218 .as_ref()
219 }
220
221 #[cfg(feature = "syntax-json")]
222 "json" | "jsonc" => {
223 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
224 CFG.get_or_init(|| {
225 HighlightConfiguration::new(
226 tree_sitter_json::LANGUAGE.into(),
227 "json",
228 tree_sitter_json::HIGHLIGHTS_QUERY,
229 "",
230 "",
231 )
232 .ok()
233 .map(|mut c| {
234 c.configure(HIGHLIGHT_NAMES);
235 c
236 })
237 })
238 .as_ref()
239 }
240
241 #[cfg(feature = "syntax-toml")]
242 "toml" => {
243 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
244 CFG.get_or_init(|| {
245 HighlightConfiguration::new(
246 tree_sitter_toml_ng::LANGUAGE.into(),
247 "toml",
248 tree_sitter_toml_ng::HIGHLIGHTS_QUERY,
249 "",
250 "",
251 )
252 .ok()
253 .map(|mut c| {
254 c.configure(HIGHLIGHT_NAMES);
255 c
256 })
257 })
258 .as_ref()
259 }
260
261 #[cfg(feature = "syntax-c")]
262 "c" | "h" => {
263 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
264 CFG.get_or_init(|| {
265 HighlightConfiguration::new(
266 tree_sitter_c::LANGUAGE.into(),
267 "c",
268 tree_sitter_c::HIGHLIGHT_QUERY,
269 "",
270 "",
271 )
272 .ok()
273 .map(|mut c| {
274 c.configure(HIGHLIGHT_NAMES);
275 c
276 })
277 })
278 .as_ref()
279 }
280
281 #[cfg(feature = "syntax-cpp")]
282 "cpp" | "c++" | "cxx" | "cc" | "hpp" => {
283 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
284 CFG.get_or_init(|| {
285 #[cfg(feature = "syntax-c")]
286 let highlights = {
287 let mut combined = String::with_capacity(
288 tree_sitter_c::HIGHLIGHT_QUERY.len()
289 + tree_sitter_cpp::HIGHLIGHT_QUERY.len()
290 + 1,
291 );
292 combined.push_str(tree_sitter_c::HIGHLIGHT_QUERY);
293 combined.push('\n');
294 combined.push_str(tree_sitter_cpp::HIGHLIGHT_QUERY);
295 combined
296 };
297 #[cfg(not(feature = "syntax-c"))]
298 let highlights = tree_sitter_cpp::HIGHLIGHT_QUERY.to_string();
299
300 HighlightConfiguration::new(
301 tree_sitter_cpp::LANGUAGE.into(),
302 "cpp",
303 &highlights,
304 "",
305 "",
306 )
307 .ok()
308 .map(|mut c| {
309 c.configure(HIGHLIGHT_NAMES);
310 c
311 })
312 })
313 .as_ref()
314 }
315
316 #[cfg(feature = "syntax-typescript")]
317 "typescript" | "ts" => {
318 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
319 CFG.get_or_init(|| {
320 HighlightConfiguration::new(
321 tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
322 "typescript",
323 tree_sitter_typescript::HIGHLIGHTS_QUERY,
324 tree_sitter_typescript::LOCALS_QUERY,
325 "",
326 )
327 .ok()
328 .map(|mut c| {
329 c.configure(HIGHLIGHT_NAMES);
330 c
331 })
332 })
333 .as_ref()
334 }
335
336 #[cfg(feature = "syntax-typescript")]
337 "tsx" => {
338 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
339 CFG.get_or_init(|| {
340 HighlightConfiguration::new(
341 tree_sitter_typescript::LANGUAGE_TSX.into(),
342 "tsx",
343 tree_sitter_typescript::HIGHLIGHTS_QUERY,
344 tree_sitter_typescript::LOCALS_QUERY,
345 "",
346 )
347 .ok()
348 .map(|mut c| {
349 c.configure(HIGHLIGHT_NAMES);
350 c
351 })
352 })
353 .as_ref()
354 }
355
356 #[cfg(feature = "syntax-java")]
357 "java" => {
358 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
359 CFG.get_or_init(|| {
360 HighlightConfiguration::new(
361 tree_sitter_java::LANGUAGE.into(),
362 "java",
363 tree_sitter_java::HIGHLIGHTS_QUERY,
364 "",
365 "",
366 )
367 .ok()
368 .map(|mut c| {
369 c.configure(HIGHLIGHT_NAMES);
370 c
371 })
372 })
373 .as_ref()
374 }
375
376 #[cfg(feature = "syntax-ruby")]
377 "ruby" | "rb" => {
378 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
379 CFG.get_or_init(|| {
380 HighlightConfiguration::new(
381 tree_sitter_ruby::LANGUAGE.into(),
382 "ruby",
383 tree_sitter_ruby::HIGHLIGHTS_QUERY,
384 tree_sitter_ruby::LOCALS_QUERY,
385 "",
386 )
387 .ok()
388 .map(|mut c| {
389 c.configure(HIGHLIGHT_NAMES);
390 c
391 })
392 })
393 .as_ref()
394 }
395
396 #[cfg(feature = "syntax-css")]
397 "css" => {
398 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
399 CFG.get_or_init(|| {
400 HighlightConfiguration::new(
401 tree_sitter_css::LANGUAGE.into(),
402 "css",
403 tree_sitter_css::HIGHLIGHTS_QUERY,
404 "",
405 "",
406 )
407 .ok()
408 .map(|mut c| {
409 c.configure(HIGHLIGHT_NAMES);
410 c
411 })
412 })
413 .as_ref()
414 }
415
416 #[cfg(feature = "syntax-html")]
417 "html" | "htm" => {
418 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
419 CFG.get_or_init(|| {
420 HighlightConfiguration::new(
421 tree_sitter_html::LANGUAGE.into(),
422 "html",
423 tree_sitter_html::HIGHLIGHTS_QUERY,
424 tree_sitter_html::INJECTIONS_QUERY,
425 "",
426 )
427 .ok()
428 .map(|mut c| {
429 c.configure(HIGHLIGHT_NAMES);
430 c
431 })
432 })
433 .as_ref()
434 }
435
436 #[cfg(feature = "syntax-yaml")]
437 "yaml" | "yml" => {
438 static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
439 CFG.get_or_init(|| {
440 HighlightConfiguration::new(
441 tree_sitter_yaml::LANGUAGE.into(),
442 "yaml",
443 tree_sitter_yaml::HIGHLIGHTS_QUERY,
444 "",
445 "",
446 )
447 .ok()
448 .map(|mut c| {
449 c.configure(HIGHLIGHT_NAMES);
450 c
451 })
452 })
453 .as_ref()
454 }
455
456 _ => None,
457 }
458}
459
460#[cfg(any(
468 feature = "syntax-rust",
469 feature = "syntax-python",
470 feature = "syntax-javascript",
471 feature = "syntax-typescript",
472 feature = "syntax-go",
473 feature = "syntax-bash",
474 feature = "syntax-json",
475 feature = "syntax-toml",
476 feature = "syntax-c",
477 feature = "syntax-cpp",
478 feature = "syntax-java",
479 feature = "syntax-ruby",
480 feature = "syntax-css",
481 feature = "syntax-html",
482 feature = "syntax-yaml",
483))]
484fn highlight_name_to_style(name: &str, theme: &Theme) -> Style {
485 let syntax = &theme.syntax;
486 match name {
487 "keyword" => Style::new().fg(syntax.keyword),
488 "string" | "string.special" => Style::new().fg(syntax.string),
489 "comment" => Style::new().fg(theme.text_dim).italic(),
490 "number" | "constant" | "constant.builtin" => Style::new().fg(syntax.constant),
491 "function" | "function.builtin" => Style::new().fg(syntax.function),
492 "function.macro" => Style::new().fg(syntax.macro_),
493 "type" | "type.builtin" | "constructor" => Style::new().fg(syntax.type_),
494 "variable.builtin" => Style::new().fg(syntax.tag),
495 "property" | "property.builtin" => Style::new().fg(syntax.property),
496 "tag" => Style::new().fg(syntax.tag),
497 "attribute" => Style::new().fg(syntax.constant),
498 "module" | "embedded" | "operator" | "variable" | "variable.parameter" => {
499 Style::new().fg(theme.text)
500 }
501 "punctuation" | "punctuation.bracket" | "punctuation.delimiter" | "punctuation.special" => {
502 Style::new().fg(theme.text_dim)
503 }
504 _ => Style::new().fg(theme.text),
505 }
506}
507
508#[cfg(any(
509 feature = "syntax-rust",
510 feature = "syntax-python",
511 feature = "syntax-javascript",
512 feature = "syntax-typescript",
513 feature = "syntax-go",
514 feature = "syntax-bash",
515 feature = "syntax-json",
516 feature = "syntax-toml",
517 feature = "syntax-c",
518 feature = "syntax-cpp",
519 feature = "syntax-java",
520 feature = "syntax-ruby",
521 feature = "syntax-css",
522 feature = "syntax-html",
523 feature = "syntax-yaml",
524))]
525thread_local! {
526 static HIGHLIGHTER: std::cell::RefCell<tree_sitter_highlight::Highlighter> =
530 std::cell::RefCell::new(tree_sitter_highlight::Highlighter::new());
531}
532
533#[allow(unused_variables)]
550pub fn highlight_code(code: &str, lang: &str, theme: &Theme) -> Option<Vec<Vec<(String, Style)>>> {
551 #[cfg(any(
552 feature = "syntax-rust",
553 feature = "syntax-python",
554 feature = "syntax-javascript",
555 feature = "syntax-typescript",
556 feature = "syntax-go",
557 feature = "syntax-bash",
558 feature = "syntax-json",
559 feature = "syntax-toml",
560 feature = "syntax-c",
561 feature = "syntax-cpp",
562 feature = "syntax-java",
563 feature = "syntax-ruby",
564 feature = "syntax-css",
565 feature = "syntax-html",
566 feature = "syntax-yaml",
567 ))]
568 {
569 use tree_sitter_highlight::HighlightEvent;
570
571 let config = get_config(lang)?;
572 let highlights = HIGHLIGHTER.with(|cell| {
573 let mut highlighter = cell.borrow_mut();
574 highlighter
575 .highlight(config, code.as_bytes(), None, |_| None)
576 .ok()
577 .map(|iter| iter.collect::<Vec<_>>())
578 })?;
579 let highlights = highlights.into_iter();
580
581 let default_style = Style::new().fg(theme.text);
582 let mut result: Vec<Vec<(String, Style)>> = Vec::new();
583 let mut current_line: Vec<(String, Style)> = Vec::new();
584 let mut style_stack: Vec<Style> = vec![default_style];
585
586 for event in highlights {
587 match event.ok()? {
588 HighlightEvent::Source { start, end } => {
589 let text = &code[start..end];
590 let style = *style_stack.last().unwrap_or(&default_style);
591 for (i, part) in text.split('\n').enumerate() {
593 if i > 0 {
594 result.push(std::mem::take(&mut current_line));
595 }
596 if !part.is_empty() {
597 current_line.push((part.to_string(), style));
598 }
599 }
600 }
601 HighlightEvent::HighlightStart(highlight) => {
602 let name = HIGHLIGHT_NAMES.get(highlight.0).copied().unwrap_or("");
603 let style = highlight_name_to_style(name, theme);
604 style_stack.push(style);
605 }
606 HighlightEvent::HighlightEnd => {
607 style_stack.pop();
608 }
609 }
610 }
611
612 if !current_line.is_empty() {
613 result.push(current_line);
614 }
615
616 Some(result)
617 }
618
619 #[cfg(not(any(
620 feature = "syntax-rust",
621 feature = "syntax-python",
622 feature = "syntax-javascript",
623 feature = "syntax-typescript",
624 feature = "syntax-go",
625 feature = "syntax-bash",
626 feature = "syntax-json",
627 feature = "syntax-toml",
628 feature = "syntax-c",
629 feature = "syntax-cpp",
630 feature = "syntax-java",
631 feature = "syntax-ruby",
632 feature = "syntax-css",
633 feature = "syntax-html",
634 feature = "syntax-yaml",
635 )))]
636 {
637 None
638 }
639}
640
641#[allow(unused_variables)]
646pub fn is_language_supported(lang: &str) -> bool {
647 #[cfg(any(
648 feature = "syntax-rust",
649 feature = "syntax-python",
650 feature = "syntax-javascript",
651 feature = "syntax-typescript",
652 feature = "syntax-go",
653 feature = "syntax-bash",
654 feature = "syntax-json",
655 feature = "syntax-toml",
656 feature = "syntax-c",
657 feature = "syntax-cpp",
658 feature = "syntax-java",
659 feature = "syntax-ruby",
660 feature = "syntax-css",
661 feature = "syntax-html",
662 feature = "syntax-yaml",
663 ))]
664 {
665 get_config(lang).is_some()
666 }
667 #[cfg(not(any(
668 feature = "syntax-rust",
669 feature = "syntax-python",
670 feature = "syntax-javascript",
671 feature = "syntax-typescript",
672 feature = "syntax-go",
673 feature = "syntax-bash",
674 feature = "syntax-json",
675 feature = "syntax-toml",
676 feature = "syntax-c",
677 feature = "syntax-cpp",
678 feature = "syntax-java",
679 feature = "syntax-ruby",
680 feature = "syntax-css",
681 feature = "syntax-html",
682 feature = "syntax-yaml",
683 )))]
684 {
685 false
686 }
687}
688
689#[cfg(test)]
690mod tests {
691 #![allow(clippy::unwrap_used)]
692 use super::*;
693 use crate::style::Theme;
694
695 #[test]
696 fn highlight_returns_none_for_unknown_lang() {
697 let theme = Theme::dark();
698 assert!(highlight_code("let x = 1;", "brainfuck", &theme).is_none());
699 }
700
701 #[test]
702 fn is_language_supported_unknown() {
703 assert!(!is_language_supported("haskell"));
704 }
705
706 #[cfg(feature = "syntax-rust")]
707 #[test]
708 fn highlight_rust_basic() {
709 let theme = Theme::dark();
710 let result = highlight_code("let x = 1;", "rust", &theme);
711 assert!(result.is_some());
712 let lines = result.unwrap();
713 assert_eq!(lines.len(), 1);
714 let flat: String = lines[0].iter().map(|(t, _)| t.as_str()).collect();
716 assert!(flat.contains("let"));
717 assert!(flat.contains("1"));
718 }
719
720 #[cfg(feature = "syntax-rust")]
721 #[test]
722 fn highlight_rust_multiline() {
723 let theme = Theme::dark();
724 let code = "fn main() {\n println!(\"hello\");\n}";
725 let result = highlight_code(code, "rust", &theme).unwrap();
726 assert_eq!(result.len(), 3);
727 }
728
729 #[cfg(feature = "syntax-rust")]
730 #[test]
731 fn highlight_rust_rs_alias() {
732 let theme = Theme::dark();
733 assert!(highlight_code("let x = 1;", "rs", &theme).is_some());
734 }
735
736 #[cfg(feature = "syntax-python")]
737 #[test]
738 fn highlight_python_basic() {
739 let theme = Theme::dark();
740 let result = highlight_code("def foo():\n return 42", "python", &theme);
741 assert!(result.is_some());
742 let lines = result.unwrap();
743 assert_eq!(lines.len(), 2);
744 }
745
746 #[cfg(feature = "syntax-javascript")]
747 #[test]
748 fn highlight_javascript_basic() {
749 let theme = Theme::dark();
750 let result = highlight_code("const x = () => 42;", "js", &theme);
751 assert!(result.is_some());
752 }
753
754 #[cfg(feature = "syntax-bash")]
755 #[test]
756 fn highlight_bash_basic() {
757 let theme = Theme::dark();
758 let result = highlight_code("echo \"hello\"", "sh", &theme);
759 assert!(result.is_some());
760 }
761
762 #[cfg(feature = "syntax-json")]
763 #[test]
764 fn highlight_json_basic() {
765 let theme = Theme::dark();
766 let result = highlight_code("{\"key\": 42}", "json", &theme);
767 assert!(result.is_some());
768 }
769
770 #[cfg(feature = "syntax-toml")]
771 #[test]
772 fn highlight_toml_basic() {
773 let theme = Theme::dark();
774 let result = highlight_code("[package]\nname = \"slt\"", "toml", &theme);
775 assert!(result.is_some());
776 }
777
778 #[cfg(feature = "syntax-go")]
779 #[test]
780 fn highlight_go_basic() {
781 let theme = Theme::dark();
782 let result = highlight_code("package main\nfunc main() {}", "go", &theme);
783 assert!(result.is_some());
784 }
785
786 #[cfg(feature = "syntax-rust")]
787 #[test]
788 fn highlight_light_theme_differs() {
789 let dark = Theme::dark();
790 let light = Theme::light();
791 let dark_result = highlight_code("let x = 1;", "rust", &dark).unwrap();
792 let light_result = highlight_code("let x = 1;", "rust", &light).unwrap();
793 let dark_styles: Vec<Style> = dark_result[0].iter().map(|(_, s)| *s).collect();
795 let light_styles: Vec<Style> = light_result[0].iter().map(|(_, s)| *s).collect();
796 assert_ne!(dark_styles, light_styles);
797 }
798
799 #[cfg(feature = "syntax-rust")]
800 #[test]
801 fn highlight_keyword_uses_theme_palette() {
802 let nord = Theme::nord();
805 let catppuccin = Theme::catppuccin();
806
807 let kw_fg = |theme: &Theme| -> crate::style::Color {
808 let line = highlight_code("let x = 1;", "rust", theme).unwrap();
809 line[0]
810 .iter()
811 .find_map(|(text, style)| (text.as_str() == "let").then_some(style.fg.unwrap()))
812 .expect("`let` keyword segment present")
813 };
814
815 assert_eq!(kw_fg(&nord), nord.syntax.keyword);
816 assert_eq!(kw_fg(&catppuccin), catppuccin.syntax.keyword);
817 assert_ne!(nord.syntax.keyword, catppuccin.syntax.keyword);
820 }
821
822 #[cfg(feature = "syntax-rust")]
823 #[test]
824 fn code_block_renders_with_theme_syntax_palette() {
825 use crate::style::Theme;
826 use crate::test_utils::TestBackend;
827
828 let theme = Theme::tokyo_night();
829 let mut tb = TestBackend::new(40, 8);
830 tb.render(|ui| {
831 ui.set_theme(theme);
832 let _ = ui.code_block("fn main() {}").lang("rust").show();
833 });
834
835 tb.assert_contains("fn");
837 tb.assert_contains("main");
838
839 let one_dark_keyword = crate::style::Color::Rgb(198, 120, 221);
842 let buffer = tb.buffer();
843 let mut saw_theme_keyword = false;
844 for y in 0..tb.height() {
845 for x in 0..tb.width() {
846 let fg = buffer.get(x, y).style.fg;
847 assert_ne!(
848 fg,
849 Some(one_dark_keyword),
850 "One Dark keyword color must not appear under Tokyo Night"
851 );
852 if fg == Some(theme.syntax.keyword) {
853 saw_theme_keyword = true;
854 }
855 }
856 }
857 assert!(
858 saw_theme_keyword,
859 "expected a cell colored with Tokyo Night's keyword color"
860 );
861 }
862
863 #[cfg(feature = "syntax-rust")]
864 #[test]
865 fn highlight_incomplete_code_does_not_panic() {
866 let theme = Theme::dark();
867 let result = highlight_code("fn main( {", "rust", &theme);
868 assert!(result.is_some());
869 }
870
871 #[cfg(feature = "syntax-c")]
872 #[test]
873 fn highlight_c_basic() {
874 let theme = Theme::dark();
875 assert!(
876 highlight_code("#include <stdio.h>\nint main() { return 0; }", "c", &theme).is_some()
877 );
878 }
879
880 #[cfg(feature = "syntax-cpp")]
881 #[test]
882 fn highlight_cpp_basic() {
883 let theme = Theme::dark();
884 assert!(highlight_code("class Foo { public: void bar(); };", "cpp", &theme).is_some());
885 }
886
887 #[cfg(feature = "syntax-typescript")]
888 #[test]
889 fn highlight_typescript_basic() {
890 let theme = Theme::dark();
891 assert!(highlight_code("const x: number = 42;", "ts", &theme).is_some());
892 }
893
894 #[cfg(feature = "syntax-typescript")]
895 #[test]
896 fn highlight_tsx_basic() {
897 let theme = Theme::dark();
898 assert!(highlight_code("const App = () => <div>hello</div>;", "tsx", &theme).is_some());
899 }
900
901 #[cfg(feature = "syntax-java")]
902 #[test]
903 fn highlight_java_basic() {
904 let theme = Theme::dark();
905 assert!(highlight_code(
906 "public class Main { public static void main(String[] args) {} }",
907 "java",
908 &theme
909 )
910 .is_some());
911 }
912
913 #[cfg(feature = "syntax-ruby")]
914 #[test]
915 fn highlight_ruby_basic() {
916 let theme = Theme::dark();
917 assert!(highlight_code("def hello\n puts 'world'\nend", "ruby", &theme).is_some());
918 }
919
920 #[cfg(feature = "syntax-css")]
921 #[test]
922 fn highlight_css_basic() {
923 let theme = Theme::dark();
924 assert!(highlight_code("body { color: red; }", "css", &theme).is_some());
925 }
926
927 #[cfg(feature = "syntax-html")]
928 #[test]
929 fn highlight_html_basic() {
930 let theme = Theme::dark();
931 assert!(highlight_code("<div class=\"test\">hello</div>", "html", &theme).is_some());
932 }
933
934 #[cfg(feature = "syntax-yaml")]
935 #[test]
936 fn highlight_yaml_basic() {
937 let theme = Theme::dark();
938 assert!(highlight_code("name: slt\nversion: 0.14", "yaml", &theme).is_some());
939 }
940
941 #[cfg(feature = "syntax-rust")]
944 #[test]
945 fn highlight_reuse_does_not_panic() {
946 let theme = Theme::dark();
947 let first = highlight_code("let x = 1;", "rust", &theme);
949 let second = highlight_code("fn foo() {}", "rust", &theme);
950 assert!(first.is_some(), "first call should succeed");
951 assert!(second.is_some(), "second call should succeed");
952 }
953
954 #[cfg(all(feature = "syntax-rust", feature = "syntax-python"))]
957 #[test]
958 fn highlight_reuse_across_languages() {
959 let theme = Theme::dark();
960 let r1 = highlight_code("let x = 1;", "rust", &theme);
961 let r2 = highlight_code("def foo(): pass", "python", &theme);
962 let r3 = highlight_code("fn bar() {}", "rust", &theme);
963 assert!(r1.is_some());
964 assert!(r2.is_some());
965 assert!(r3.is_some());
966 }
967}