use pulldown_cmark::{CodeBlockKind, Event, Options, Tag, html}; use syntect::easy::HighlightLines; use syntect::highlighting::ThemeSet; use syntect::html::{IncludeBackground, styled_line_to_highlighted_html}; use syntect::parsing::SyntaxSet; pub fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &str) -> String { let theme = &ts.themes["base16-ocean.dark"]; let mut options = Options::empty(); options.insert(Options::ENABLE_TABLES); options.insert(Options::ENABLE_FOOTNOTES); options.insert(Options::ENABLE_STRIKETHROUGH); options.insert(Options::ENABLE_TASKLISTS); options.insert(Options::ENABLE_SMART_PUNCTUATION); let parser = pulldown_cmark::Parser::new_ext(markdown, options); let mut processed_events: Vec = Vec::new(); let mut in_code_block = false; let mut current_lang: Option = None; let mut current_code = String::new(); for event in parser { match event { Event::Start(Tag::CodeBlock(kind)) => { in_code_block = true; current_code.clear(); current_lang = if let CodeBlockKind::Fenced(l) = kind { Some(l.to_string()) } else { None }; } Event::End(Tag::CodeBlock(_)) => { in_code_block = false; let is_mermaid = current_lang.as_deref() == Some("mermaid"); if is_mermaid { let escaped_code = escape_html(¤t_code); let mermaid_html = format!( r#"
Mermaid Diagram
{escaped_code}
"# ); processed_events.push(Event::Html(mermaid_html.into())); } else { let lang_display = current_lang.as_deref().unwrap_or("text"); let lang_escaped = escape_html(lang_display); let highlighted_html = if let Some(lang) = ¤t_lang { if let Some(syntax) = ss.find_syntax_by_token(lang) { let mut h = HighlightLines::new(syntax, theme); let mut result_html = String::new(); for line in current_code.lines() { let line_with_newline = format!("{line}\n"); match h.highlight_line(&line_with_newline, ss) { Ok(regions) => { let html_line = styled_line_to_highlighted_html( ®ions[..], IncludeBackground::No, ) .unwrap_or_else(|_| escape_html(&line_with_newline)); result_html.push_str(&html_line); } Err(_) => { result_html.push_str(&escape_html(&line_with_newline)); } } } result_html } else { escape_html(¤t_code) } } else { escape_html(¤t_code) }; let code_container = format!( r#"
{lang_escaped}
{highlighted_html}
"# ); processed_events.push(Event::Html(code_container.into())); } } Event::Text(text) if in_code_block => { current_code.push_str(&text); } _ => { if !in_code_block { processed_events.push(event); } } } } let mut body_html = String::new(); html::push_html(&mut body_html, processed_events.into_iter()); body_html } fn escape_html(text: &str) -> String { text.replace('&', "&") .replace('<', "<") .replace('>', ">") .replace('"', """) .replace('\'', "'") }