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