use pulldown_cmark::{ html::push_html, CodeBlockKind, Event, Options, Parser, Tag, }; use syntect::parsing::SyntaxSet; use crate::syntax_highlighting::highlight_code; /// Renders some Markdown to HTML using [`pulldown_cmark`]. pub fn render_markdown(source: &str) -> String { // Create the parser with all options enabled. let parser = Parser::new_ext(source, Options::all()); // Load the syntaxes from Syntect. let syntax_set = SyntaxSet::load_defaults_newlines(); // Define some state we'll use in the rendering. let mut code_language = String::new(); let mut code_to_highlight = String::new(); let mut events = vec![]; let mut in_code_block = false; let mut syntax = syntax_set.find_syntax_plain_text(); for event in parser { match event { Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(language))) => { // When a fenced codeblock is started, assign it to the state. code_language = language.to_string(); syntax = syntax_set .find_syntax_by_token(&language) .unwrap_or_else(|| syntax_set.find_syntax_plain_text()); in_code_block = true; } Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(_))) => { // When a fenced code is closed, highlight the code that was inside it. if in_code_block { // Add the highlighted code to the final result. events.push(Event::Html( format!( r#"
{}
"#, code_language, highlight_code(&code_to_highlight, syntax, &syntax_set) ) .into(), )); // Reset the state. code_to_highlight = String::new(); in_code_block = false; syntax = syntax_set.find_syntax_plain_text(); } } Event::Text(text) => { if in_code_block { // When we're parsing some text and we're in a code block, add it to // the code we need to highlight. code_to_highlight.push_str(&text); } else { // Otherwise just return it as it came in. events.push(Event::Text(text)); } } _ => events.push(event), }; } // Finally, render the HTML into a string. let mut html = String::new(); push_html(&mut html, events.into_iter()); html }