nargo-document 0.0.0

Nargo documentation tool
Documentation
//! Shiki 代码高亮插件
//!
//! 提供对 Markdown 中代码块的 Shiki 语法高亮支持

use crate::plugin::{DocumentPlugin, PluginContext, PluginMeta};
use lazy_static::lazy_static;
use regex::Regex;

lazy_static! {
    /// 匹配代码块的正则表达式,支持带语言标识
    static ref CODE_BLOCK_RE: Regex = Regex::new(r"```([a-zA-Z0-9_]*)\s*\n([\s\S]*?)\n```").unwrap();
}

/// Shiki 代码高亮插件
pub struct ShikiPlugin {
    /// 插件元数据
    meta: PluginMeta,
}

impl ShikiPlugin {
    /// 创建新的 Shiki 插件实例
    pub fn new() -> Self {
        Self { meta: PluginMeta::new("nargo-document-plugin-shiki".to_string(), "0.1.0".to_string(), "Shiki 代码高亮插件,提供代码块语法高亮功能".to_string()) }
    }

    /// 处理代码块,将 ```lang ... ``` 替换为 <pre class="shiki" data-language="lang"><code>...</code></pre>
    ///
    /// # Arguments
    ///
    /// * `content` - 包含代码块的文本内容
    ///
    /// # Returns
    ///
    /// 替换后的文本内容
    fn process_code_blocks(&self, content: &str) -> String {
        CODE_BLOCK_RE
            .replace_all(content, |caps: &regex::Captures| {
                let lang = caps.get(1).map_or("", |m| m.as_str());
                let code = &caps[2];
                if lang.is_empty() {
                    format!("<pre class=\"shiki\"><code>{}</code></pre>", code.trim())
                }
                else {
                    format!("<pre class=\"shiki\" data-language=\"{}\"><code>{}</code></pre>", lang, code.trim())
                }
            })
            .to_string()
    }
}

impl Default for ShikiPlugin {
    fn default() -> Self {
        Self::new()
    }
}

impl DocumentPlugin for ShikiPlugin {
    /// 获取插件元数据
    fn meta(&self) -> &PluginMeta {
        &self.meta
    }

    /// 渲染前钩子,在 Markdown 解析后、HTML 渲染前处理代码块
    ///
    /// # Arguments
    ///
    /// * `context` - 插件上下文,包含文档内容等信息
    ///
    /// # Returns
    ///
    /// 处理后的插件上下文
    fn before_render(&self, context: PluginContext) -> PluginContext {
        let content = self.process_code_blocks(&context.content);

        PluginContext { content, frontmatter: context.frontmatter, path: context.path }
    }
}