mdxt/container/codefence/
syntect.rs1use crate::escape::escape_htmls;
2use crate::utils::{from_v32, inclusive_split, into_v32};
3use lazy_static::lazy_static;
4use syntect::easy::HighlightLines;
5use syntect::highlighting::{Color, Theme, ThemeSet};
6use syntect::parsing::SyntaxSet;
7
8lazy_static! {
9 pub static ref SYNTAX_SET: SyntaxSet = {
10 let default_set = SyntaxSet::load_defaults_newlines();
11 let mut syntax_set_builder = default_set.into_builder();
12
13 if let Err(e) = syntax_set_builder.add_from_folder("./extra_syntaxes", true) {
14 println!("Error while reading `./extra_syntaxes`: {e:?}");
15 }
16
17 syntax_set_builder.build()
18 };
19 pub static ref THEME: Theme = {
20 let theme_set = ThemeSet::load_defaults();
21
22 theme_set.themes["base16-eighties.dark"].clone()
23 };
24}
25
26pub fn highlight_syntax(content: &[u32], language: &[u32], class_prefix: &str) -> Vec<Vec<u32>> {
27
28 #[cfg(test)]
29 assert!(is_syntax_available(language));
30
31 let syntax_reference = SYNTAX_SET.find_syntax_by_token(&from_v32(language)).unwrap();
33 let mut highlighter = HighlightLines::new(syntax_reference, &THEME);
34 let mut result = vec![];
35
36 for line_u32 in inclusive_split(&content, '\n' as u32).into_iter() {
38 let line_u32 = line_u32.to_vec();
39
40 let curr_line = &from_v32(&line_u32);
41 let mut curr_stack = vec![];
42
43 match highlighter.highlight_line(curr_line, &SYNTAX_SET) {
44 Ok(styled_line) => {
45
46 for (style, content) in styled_line.into_iter() {
47 let curr_stack_len = curr_stack.len();
48
49 if curr_stack.is_empty() {
50 curr_stack.push((style.foreground.clone(), content.to_string()));
51 }
52
53 else if curr_stack.last().unwrap().0 == style.foreground {
54 curr_stack[curr_stack_len - 1].1 = format!("{}{content}", curr_stack.last().unwrap().1);
55 }
56
57 else if content.chars().all(|c| c == ' ') {
58 curr_stack[curr_stack_len - 1].1 = format!("{}{content}", curr_stack.last().unwrap().1);
59 }
60
61 else {
62 curr_stack.push((style.foreground.clone(), content.to_string()));
63 }
64
65 }
66
67 result.push(curr_stack.iter().map(
68 |(color, content)|
69 classify_style_to_css(color, content, class_prefix)
70 ).collect::<Vec<Vec<u32>>>().concat());
71 }
72 Err(_) => {
73 result.push(classify_style_to_css(&Color::WHITE, curr_line, class_prefix));
74 }
75 }
76
77 }
78
79 result
80}
81
82pub fn is_syntax_available(language: &[u32]) -> bool {
83 SYNTAX_SET.find_syntax_by_token(&from_v32(language)).is_some()
84}
85
86fn classify_style_to_css(color: &Color, content: &str, class_prefix: &str) -> Vec<u32> {
87
88 let color = match color {
90 Color { r: 211, g: 208, b: 200, .. } => "white",
91 Color { r: 45, g: 45, b: 45, .. } => "dark",
92 Color { r: 242, g: 119, b: 122, .. } => "red",
93 Color { r: 116, g: 115, b: 105, .. } => "gray",
94 Color { r: 204, g: 153, b: 204, .. } => "violet",
95 Color { r: 102, g: 153, b: 204, .. } => "aqua",
96 Color { r: 249, g: 145, b: 87, .. } => "gold",
97 Color { r: 153, g: 204, b: 153, .. } => "green",
98 Color { r: 102, g: 204, b: 204, .. } => "emerald",
99 Color { r: 210, g: 123, b: 83, .. } => "pink",
100 Color { r: 255, g: 204, b: 102, .. } => "grassgreen",
101 Color { r, g, b, .. } => if cfg!(test) {
102 panic!("Uninitialized Color: (r: {r} g: {g} b: {b})")
103 } else {
104 "white"
105 }
106 };
107
108 let content_v32 = into_v32(content);
109
110 if content_v32.is_empty() {
116 vec![]
117 }
118
119 else if content_v32.iter().all(|c| *c == ' ' as u32 || *c == '\n' as u32) {
121 content_v32.into_iter().filter(|c| *c != '\n' as u32).collect()
122 }
123
124 else {
125 vec![
126 into_v32(&format!("<span class=\"{class_prefix}color-{color}\">")),
127 escape_htmls(&content_v32).into_iter().filter(|c| *c != '\n' as u32).collect(),
128 vec![60, 47, 115, 112, 97, 110, 62], ].concat()
130 }
131
132}