context_notation/
code.rs

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    // Add some aliases to file extensions
8    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', "&#10;");
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("&#10;")
61            + &format!("{trailing_whitespace}"),
62    );
63
64    highlighted
65}