1use crate::safety::html::unsafe_string;
2use synoptic::TokOpt;
3
4pub const TAB_WIDTH: usize = 2;
5
6pub fn convert_code_to_html(kind: &str, code: &str) -> Option<String> {
7 let kind = match kind {
9 "rust" => "rs",
10 "python" => "py",
11 "javascript" => "js",
12 "typescript" => "ts",
13 _ => kind,
14 };
15
16 let mut clip = 0;
17
18 for character in code.chars().rev() {
19 match character {
20 '\n' | ' ' => clip += 1,
21 _ => {
22 break;
23 }
24 }
25 }
26
27 let (code, trailing_whitespace) = code.split_at(code.len() - clip);
28
29 let trailing_whitespace = trailing_whitespace.replace('\n', " ");
30
31 let Some(mut highlighter) = synoptic::from_extension(kind, TAB_WIDTH) else {
32 return None;
33 };
34
35 let lines = code
36 .lines()
37 .map(|line| line.to_owned())
38 .collect::<Vec<String>>();
39
40 highlighter.run(&lines);
41
42 let highlighted = Some(
43 lines
44 .iter()
45 .enumerate()
46 .map(|(line_number, line)| {
47 highlighter
48 .line(line_number, line)
49 .into_iter()
50 .map(|token| match token {
51 TokOpt::Some(text, kind) => {
52 format!("<span class=\"{kind}\">{}</span>", unsafe_string(&text))
53 }
54 TokOpt::None(text) => unsafe_string(&text).to_string(),
55 })
56 .collect::<Vec<String>>()
57 .concat()
58 })
59 .collect::<Vec<String>>()
60 .join(" ")
61 + &format!("{trailing_whitespace}"),
62 );
63
64 highlighted
65}