markdown_it/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::MarkdownItExt;
8use crate::plugins::cmark::block::code::CodeBlock;
9use crate::plugins::cmark::block::fence::CodeFence;
10use crate::{MarkdownIt, 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 MarkdownItExt for SyntectSettings {}
26
27impl Default for SyntectSettings {
28    fn default() -> Self {
29        Self("InspiredGitHub")
30    }
31}
32
33pub fn add(md: &mut MarkdownIt) {
34    md.add_rule::<SyntectRule>();
35}
36
37pub fn set_theme(md: &mut MarkdownIt, 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: &MarkdownIt) {
44        let ss = SyntaxSet::load_defaults_newlines();
45        let ts = ThemeSet::load_defaults();
46        let theme = &ts.themes[md.ext.get::<SyntectSettings>().copied().unwrap_or_default().0];
47
48        root.walk_mut(|node, _| {
49            let mut content = None;
50            let mut language = None;
51
52            if let Some(data) = node.cast::<CodeBlock>() {
53                content = Some(&data.content);
54            } else if let Some(data) = node.cast::<CodeFence>() {
55                language = Some(data.info.clone());
56                content = Some(&data.content);
57            }
58
59            if let Some(content) = content {
60                let mut syntax = None;
61                if let Some(language) = language {
62                    syntax = ss.find_syntax_by_token(&language);
63                }
64                let syntax = syntax.unwrap_or_else(|| ss.find_syntax_plain_text());
65
66                let html = highlighted_html_for_string(content, &ss, syntax, theme);
67
68                if let Ok(html) = html {
69                    node.replace(SyntectSnippet { html });
70                }
71            }
72        });
73    }
74}