use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::ops::Range;
use std::panic::{AssertUnwindSafe, catch_unwind};
use mermaid_rs_renderer::{RenderOptions, Theme, render_with_options};
use crate::image_cache::{ImageCache, LoadedImage, decode};
pub struct MermaidBlock {
pub block: Range<usize>,
pub content: Range<usize>,
pub anchor_line: usize,
}
pub fn key_for(source: &str) -> String {
let mut h = DefaultHasher::new();
source.hash(&mut h);
format!("mermaid:{:016x}", h.finish())
}
fn render_to_image(source: &str) -> Option<LoadedImage> {
let opts = RenderOptions {
theme: Theme::dark(),
..Default::default()
};
let svg = catch_unwind(AssertUnwindSafe(|| render_with_options(source, opts)))
.ok()?
.ok()?;
decode(svg.as_bytes())
}
pub fn spawn_mermaid_renders(
sources: &[(String, String)],
cache: &ImageCache,
notify: impl Fn() + Send + Clone + 'static,
) {
for (key, source) in sources {
if cache.contains(key) {
continue; }
cache.mark_loading(key);
let cache = cache.clone();
let notify = notify.clone();
let key = key.clone();
let source = source.clone();
std::thread::spawn(move || {
match render_to_image(&source) {
Some(img) => cache.set_loaded(&key, img),
None => cache.set_failed(&key),
}
notify();
});
}
}
pub fn render_mermaid_blocking(sources: &[(String, String)], cache: &ImageCache) {
for (key, source) in sources {
if cache.contains(key) {
continue;
}
match render_to_image(source) {
Some(img) => cache.set_loaded(key, img),
None => cache.set_failed(key),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::image_cache::ImageState;
#[test]
fn renders_diagram_into_cache() {
let src = "flowchart TD\n A[Start] --> B[End]";
let key = key_for(src);
let cache = ImageCache::new();
render_mermaid_blocking(&[(key.clone(), src.to_string())], &cache);
assert!(
matches!(cache.get(&key), Some(ImageState::Loaded(_))),
"a valid flowchart should render to SVG and decode"
);
}
#[test]
fn bad_source_fails_without_panicking() {
let src = "%%%% definitely not mermaid %%%%";
let key = key_for(src);
let cache = ImageCache::new();
render_mermaid_blocking(&[(key.clone(), src.to_string())], &cache);
assert!(cache.get(&key).is_some());
}
#[test]
fn key_is_stable_per_source() {
assert_eq!(key_for("graph LR; A-->B"), key_for("graph LR; A-->B"));
assert_ne!(key_for("graph LR; A-->B"), key_for("graph LR; A-->C"));
}
}