markdown_that/plugins/extra/
syntect.rs

1//! Syntax highlighting for code blocks
2use syntect::highlighting::ThemeSet;
3use syntect::html::highlighted_html_for_string;
4use syntect::parsing::SyntaxSet;
5
6use crate::parser::core::CoreRule;
7use crate::parser::extset::MarkdownThatExt;
8use crate::plugins::cmark::block::code::CodeBlock;
9use crate::plugins::cmark::block::fence::CodeFence;
10use crate::{MarkdownThat, Node, NodeValue, Renderer};
11
12#[derive(Debug)]
13pub struct SyntectSnippet {
14    pub html: String,
15}
16
17impl NodeValue for SyntectSnippet {
18    fn render(&self, _: &Node, fmt: &mut dyn Renderer) {
19        fmt.text_raw(&self.html);
20    }
21}
22
23#[derive(Debug, Clone, Copy)]
24struct SyntectSettings(&'static str);
25impl MarkdownThatExt for SyntectSettings {}
26
27impl Default for SyntectSettings {
28    fn default() -> Self {
29        Self("InspiredGitHub")
30    }
31}
32
33pub fn add(md: &mut MarkdownThat) {
34    md.add_rule::<SyntectRule>();
35}
36
37pub fn set_theme(md: &mut MarkdownThat, theme: &'static str) {
38    md.ext.insert(SyntectSettings(theme));
39}
40
41pub struct SyntectRule;
42impl CoreRule for SyntectRule {
43    fn run(root: &mut Node, md: &MarkdownThat) {
44        let ss = SyntaxSet::load_defaults_newlines();
45        let ts = ThemeSet::load_defaults();
46        let theme = &ts.themes[md
47            .ext
48            .get::<SyntectSettings>()
49            .copied()
50            .unwrap_or_default()
51            .0];
52
53        root.walk_mut(|node, _| {
54            let mut content = None;
55            let mut language = None;
56
57            if let Some(data) = node.cast::<CodeBlock>() {
58                content = Some(&data.content);
59            } else if let Some(data) = node.cast::<CodeFence>() {
60                language = Some(data.info.clone());
61                content = Some(&data.content);
62            }
63
64            if let Some(content) = content {
65                let mut syntax = None;
66                if let Some(language) = language {
67                    syntax = ss.find_syntax_by_token(&language);
68                }
69                let syntax = syntax.unwrap_or_else(|| ss.find_syntax_plain_text());
70
71                let html = highlighted_html_for_string(content, &ss, syntax, theme);
72
73                if let Ok(html) = html {
74                    node.replace(SyntectSnippet { html });
75                }
76            }
77        });
78    }
79}