From d417e1c5358e2a77c53fd0146f56109f6af24c0b Mon Sep 17 00:00:00 2001 From: Timothy DeHerrera Date: Wed, 28 Jan 2026 20:36:45 -0700 Subject: [PATCH] feat(mermaid): integrate diagram rendering into markdown pipeline Intercept 'mermaid' code blocks in render.rs and call mermaid::render_diagram() to convert to inline SVG. - Use catch_unwind to handle upstream dagre_rust panics gracefully - Graceful fallback: show raw code with mermaid-error class on failure - Flowcharts render correctly; some diagram types hit upstream bugs --- src/mermaid.rs | 24 ++++++++++++++++++++++-- src/render.rs | 46 +++++++++++++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/mermaid.rs b/src/mermaid.rs index caa437b..498400d 100644 --- a/src/mermaid.rs +++ b/src/mermaid.rs @@ -3,6 +3,7 @@ //! Converts Mermaid diagram definitions to SVG at build-time. use mermaid_rs_renderer::RenderOptions; +use std::panic; /// Render a Mermaid diagram to SVG. /// @@ -11,9 +12,21 @@ use mermaid_rs_renderer::RenderOptions; /// /// # Returns /// The rendered SVG string, or an error message on failure. +/// +/// # Note +/// Uses catch_unwind to handle panics in upstream dependencies gracefully. pub fn render_diagram(code: &str) -> Result { - let opts = RenderOptions::modern(); - mermaid_rs_renderer::render_with_options(code, opts).map_err(|e| e.to_string()) + let code = code.to_owned(); + let result = panic::catch_unwind(move || { + let opts = RenderOptions::modern(); + mermaid_rs_renderer::render_with_options(&code, opts) + }); + + match result { + Ok(Ok(svg)) => Ok(svg), + Ok(Err(e)) => Err(e.to_string()), + Err(_) => Err("mermaid rendering panicked (upstream bug)".to_string()), + } } #[cfg(test)] @@ -40,4 +53,11 @@ mod tests { // May succeed with error node or fail gracefully assert!(result.is_ok() || result.is_err()); } + + #[test] + fn test_state_diagram() { + let result = + render_diagram("stateDiagram-v2\n [*] --> Idle\n Idle --> Processing: Start"); + assert!(result.is_ok() || result.is_err()); + } } diff --git a/src/render.rs b/src/render.rs index f83f61d..66a70f0 100644 --- a/src/render.rs +++ b/src/render.rs @@ -46,23 +46,43 @@ pub fn markdown_to_html(markdown: &str) -> String { Event::End(TagEnd::CodeBlock) => { // Render the code block with highlighting let lang_str = code_block_lang.as_deref().unwrap_or(""); - html_output.push_str("
", lang_str));
-                    html_output.push_str(&highlight_code(lang, &code_block_content));
-                } else {
-                    // Unsupported language: render as plain escaped text
-                    if !lang_str.is_empty() {
-                        html_output.push_str(&format!(" class=\"language-{}\">", lang_str));
-                    } else {
-                        html_output.push('>');
+                // Mermaid diagrams: render to SVG
+                if lang_str == "mermaid" {
+                    match crate::mermaid::render_diagram(&code_block_content) {
+                        Ok(svg) => {
+                            html_output.push_str("
\n"); + html_output.push_str(&svg); + html_output.push_str("\n
\n"); + } + Err(e) => { + eprintln!("mermaid render error: {e}"); + html_output.push_str("
");
+                            html_output.push_str(&html_escape(&code_block_content));
+                            html_output.push_str("
\n"); + } } - html_output.push_str(&html_escape(&code_block_content)); + } else { + // Code blocks: syntax highlighting + html_output.push_str("
", lang_str));
+                        html_output.push_str(&highlight_code(lang, &code_block_content));
+                    } else {
+                        // Unsupported language: render as plain escaped text
+                        if !lang_str.is_empty() {
+                            html_output.push_str(&format!(" class=\"language-{}\">", lang_str));
+                        } else {
+                            html_output.push('>');
+                        }
+                        html_output.push_str(&html_escape(&code_block_content));
+                    }
+
+                    html_output.push_str("
\n"); } - html_output.push_str("
\n"); code_block_lang = None; code_block_content.clear(); }