context_notation/
code.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
use crate::safety::html::unsafe_string;
use synoptic::TokOpt;

pub const TAB_WIDTH: usize = 2;

pub fn convert_code_to_html(kind: &str, code: &str) -> Option<String> {
    // Add some aliases to file extensions
    let kind = match kind {
        "rust" => "rs",
        "python" => "py",
        "javascript" => "js",
        "typescript" => "ts",
        _ => kind,
    };

    let mut clip = 0;

    for character in code.chars().rev() {
        match character {
            '\n' | ' ' => clip += 1,
            _ => {
                break;
            }
        }
    }

    let (code, trailing_whitespace) = code.split_at(code.len() - clip);

    let trailing_whitespace = trailing_whitespace.replace('\n', "&#10;");

    let Some(mut highlighter) = synoptic::from_extension(kind, TAB_WIDTH) else {
        return None;
    };

    let lines = code
        .lines()
        .map(|line| line.to_owned())
        .collect::<Vec<String>>();

    highlighter.run(&lines);

    let highlighted = Some(
        lines
            .iter()
            .enumerate()
            .map(|(line_number, line)| {
                highlighter
                    .line(line_number, line)
                    .into_iter()
                    .map(|token| match token {
                        TokOpt::Some(text, kind) => {
                            format!("<span class=\"{kind}\">{}</span>", unsafe_string(&text))
                        }
                        TokOpt::None(text) => unsafe_string(&text).to_string(),
                    })
                    .collect::<Vec<String>>()
                    .concat()
            })
            .collect::<Vec<String>>()
            .join("&#10;")
            + &format!("{trailing_whitespace}"),
    );

    highlighted
}