raw
1use std::{
2 borrow::Cow,
3 collections::HashMap,
4 fmt::{self, Write},
5};
6
7use comrak::{
8 Options, adapters::SyntaxHighlighterAdapter, html::write_opening_tag,
9 markdown_to_html_with_plugins, options::Plugins,
10};
11use syntect::{
12 Error,
13 html::{ClassStyle, ClassedHTMLGenerator},
14 parsing::{SyntaxReference, SyntaxSet},
15 util::LinesWithEndings,
16};
17
18use crate::SYNTAXES;
19
20#[tracing::instrument(skip_all)]
21pub fn render(input: &str) -> String {
22 let adaptor = SyntectAdapter {
23 syntax_set: &SYNTAXES,
24 };
25
26 let options = Options::default();
27
28 let mut plugins = Plugins::default();
29
30 plugins.render.codefence_syntax_highlighter = Some(&adaptor);
31
32 markdown_to_html_with_plugins(input, &options, &plugins)
33}
34
35struct SyntectAdapter {
36 syntax_set: &'static SyntaxSet,
37}
38
39impl SyntectAdapter {
40 fn highlight_html(&self, code: &str, syntax: &SyntaxReference) -> Result<String, Error> {
41 let mut html_generator =
42 ClassedHTMLGenerator::new_with_class_style(syntax, self.syntax_set, ClassStyle::Spaced);
43 for line in LinesWithEndings::from(code) {
44 html_generator.parse_html_for_line_which_includes_newline(line)?;
45 }
46 Ok(html_generator.finalize())
47 }
48}
49
50impl SyntaxHighlighterAdapter for SyntectAdapter {
51 fn write_highlighted(
52 &self,
53 output: &mut dyn Write,
54 lang: Option<&str>,
55 code: &str,
56 ) -> fmt::Result {
57 let fallback_syntax = "Plain Text";
58
59 let lang: &str = match lang {
60 Some(l) if !l.is_empty() => l,
61 _ => fallback_syntax,
62 };
63
64 let syntax = self
65 .syntax_set
66 .find_syntax_by_token(lang)
67 .unwrap_or_else(|| {
68 self.syntax_set
69 .find_syntax_by_first_line(code)
70 .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text())
71 });
72
73 match self.highlight_html(code, syntax) {
74 Ok(highlighted_code) => output.write_str(&highlighted_code),
75 Err(_) => output.write_str(code),
76 }
77 }
78
79 fn write_pre_tag(
80 &self,
81 output: &mut dyn Write,
82 _attributes: HashMap<&'static str, Cow<'_, str>>,
83 ) -> fmt::Result {
84 let mut attributes: HashMap<&str, &str> = HashMap::new();
85 attributes.insert("class", "syntax-highlighting");
86 write_opening_tag(output, "pre", attributes)
87 }
88
89 fn write_code_tag(
90 &self,
91 output: &mut dyn Write,
92 attributes: HashMap<&'static str, Cow<'_, str>>,
93 ) -> fmt::Result {
94 write_opening_tag(output, "code", attributes)
95 }
96}
97