use benchmarks::bench_utils::random_rust_file; use gpui::{ AppContext as _, BenchAppContext, Context, Entity, IntoElement, Render, SharedString, Window, }; use language::LanguageRegistry; use markdown::{ CodeBlockRenderer, CopyButtonVisibility, Markdown, MarkdownElement, MarkdownFont, MarkdownOptions, MarkdownStyle, WrapButtonVisibility, }; use rand::{Rng as _, SeedableRng as _, rngs::StdRng}; use settings::SettingsStore; use std::sync::Arc; use ui::prelude::*; const SEED: u64 = 1; #[gpui::bench( inputs = markdown_sizes(), group = "Markdown render", input_name = "min_bytes", sample_size = 10 )] fn markdown_render(target_size: &usize, cx: &mut BenchAppContext) { init_context(cx); let source = SharedString::from(generate_markdown(SEED, *target_size)); let language_registry = markdown_language_registry(cx); let mut window = cx.add_empty_window(); let view = window.update(|window, cx| { let markdown = cx.new({ let source = source.clone(); let language_registry = language_registry.clone(); move |cx| build_markdown(source, language_registry, cx) }); window.replace_root(cx, |_window, _cx| MarkdownBenchView { markdown }) }); cx.bench_renderer(view, |_, _, cx| cx.notify()); } struct MarkdownBenchView { markdown: Entity, } impl Render for MarkdownBenchView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let style = MarkdownStyle::themed(MarkdownFont::Preview, window, cx); div().w_full().h_full().child( MarkdownElement::new(self.markdown.clone(), style).code_block_renderer( CodeBlockRenderer::Default { copy_button_visibility: CopyButtonVisibility::VisibleOnHover, wrap_button_visibility: WrapButtonVisibility::VisibleOnHover, border: false, }, ), ) } } fn build_markdown( source: SharedString, language_registry: Arc, cx: &mut Context, ) -> Markdown { Markdown::new_with_options( source, Some(language_registry), None, MarkdownOptions { // Mermaid and embedded resources need their own focused benchmarks. render_metadata_blocks: true, ..Default::default() }, cx, ) } fn generate_markdown(seed: u64, target_size: usize) -> String { let mut rng = StdRng::seed_from_u64(seed); let mut markdown = String::with_capacity(target_size); markdown.push_str("---\n"); markdown.push_str("title: Markdown renderer benchmark\n"); markdown.push_str("author: Zed benchmark\n"); markdown.push_str("---\n\n"); while markdown.len() < target_size { push_mixed_block(&mut markdown, &mut rng); } markdown } fn push_mixed_block(markdown: &mut String, rng: &mut StdRng) { match rng.random_range(0..10) { 0 => push_heading(markdown, rng), 1 | 2 => push_paragraph(markdown, rng), 3 => push_list(markdown, rng), 4 => push_task_list(markdown, rng), 5 => push_table(markdown, rng), 6 | 7 => push_code_block(markdown, rng), 8 => push_block_quote(markdown, rng), _ => push_rule(markdown), } } fn push_heading(markdown: &mut String, rng: &mut StdRng) { let level = rng.random_range(1..=4); markdown.push_str(&"#".repeat(level)); markdown.push(' '); let word_count = rng.random_range(3..8); push_words(markdown, rng, word_count); markdown.push_str("\n\n"); } fn push_paragraph(markdown: &mut String, rng: &mut StdRng) { let sentence_count = rng.random_range(2..7); for sentence_index in 0..sentence_count { if sentence_index > 0 { markdown.push(' '); } push_sentence(markdown, rng); } markdown.push_str("\n\n"); } fn push_sentence(markdown: &mut String, rng: &mut StdRng) { let word_count = rng.random_range(8..24); for word_index in 0..word_count { if word_index > 0 { markdown.push(' '); } match rng.random_range(0..18) { 0 => { markdown.push_str("**"); markdown.push_str(random_word(rng)); markdown.push_str("**"); } 1 => { markdown.push('*'); markdown.push_str(random_word(rng)); markdown.push('*'); } 2 => { markdown.push('`'); markdown.push_str(random_identifier(rng)); markdown.push('`'); } 3 => { markdown.push('['); markdown.push_str(random_word(rng)); markdown.push_str("](https://example.com/"); markdown.push_str(random_identifier(rng)); markdown.push(')'); } 4 => { markdown.push_str("https://zed.dev/"); markdown.push_str(random_identifier(rng)); } _ => markdown.push_str(random_word(rng)), } } markdown.push('.'); } fn push_list(markdown: &mut String, rng: &mut StdRng) { let item_count = rng.random_range(3..9); let ordered = rng.random(); for item_index in 0..item_count { if ordered { markdown.push_str(&(item_index + 1).to_string()); markdown.push_str(". "); } else { markdown.push_str("- "); } let word_count = rng.random_range(5..14); push_words(markdown, rng, word_count); markdown.push('\n'); } markdown.push('\n'); } fn push_task_list(markdown: &mut String, rng: &mut StdRng) { let item_count = rng.random_range(3..8); for _ in 0..item_count { if rng.random() { markdown.push_str("- [x] "); } else { markdown.push_str("- [ ] "); } let word_count = rng.random_range(4..12); push_words(markdown, rng, word_count); markdown.push('\n'); } markdown.push('\n'); } fn push_table(markdown: &mut String, rng: &mut StdRng) { let column_count = rng.random_range(3..7); let row_count = rng.random_range(3..9); for column_index in 0..column_count { if column_index == 0 { markdown.push('|'); } markdown.push(' '); markdown.push_str(random_word(rng)); markdown.push(' '); markdown.push('|'); } markdown.push('\n'); for column_index in 0..column_count { if column_index == 0 { markdown.push('|'); } markdown.push_str(" --- |"); } markdown.push('\n'); for _ in 0..row_count { for column_index in 0..column_count { if column_index == 0 { markdown.push('|'); } markdown.push(' '); let word_count = rng.random_range(2..6); push_words(markdown, rng, word_count); markdown.push(' '); markdown.push('|'); } markdown.push('\n'); } markdown.push('\n'); } fn push_code_block(markdown: &mut String, rng: &mut StdRng) { let line_count = rng.random_range(24..80); let rust = random_rust_file(rng, line_count); markdown.push_str("```rust\n"); markdown.push_str(&rust.join("\n")); markdown.push_str("\n```\n\n"); } fn push_block_quote(markdown: &mut String, rng: &mut StdRng) { let line_count = rng.random_range(2..6); for _ in 0..line_count { markdown.push_str("> "); push_sentence(markdown, rng); markdown.push('\n'); } markdown.push('\n'); } fn push_rule(markdown: &mut String) { markdown.push_str("---\n\n"); } fn push_words(markdown: &mut String, rng: &mut StdRng, word_count: usize) { for word_index in 0..word_count { if word_index > 0 { markdown.push(' '); } markdown.push_str(random_word(rng)); } } fn random_word(rng: &mut StdRng) -> &'static str { const WORDS: &[&str] = &[ "renderer", "layout", "markdown", "paragraph", "heading", "table", "selection", "syntax", "highlight", "window", "element", "callback", "benchmark", "profile", "latency", "throughput", "scroll", "wrapping", "theme", "language", "fenced", "blockquote", "inline", "content", ]; choose(rng, WORDS) } fn random_identifier(rng: &mut StdRng) -> &'static str { const IDENTIFIERS: &[&str] = &[ "markdown_renderer", "layout_cache", "rendered_text", "source_range", "bench_input", "window_state", "code_block", "table_row", "link_target", "scroll_handle", "text_style", "root_block", ]; choose(rng, IDENTIFIERS) } fn choose(rng: &mut StdRng, items: &'static [&'static str]) -> &'static str { let index = rng.random_range(0..items.len()); items.get(index).copied().unwrap_or("markdown") } fn markdown_language_registry(cx: &BenchAppContext) -> Arc { let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); registry.add(language::rust_lang()); registry } fn init_context(cx: &mut BenchAppContext) { cx.update(|cx| { let store = SettingsStore::test(cx); cx.set_global(store); assets::Assets.load_test_fonts(cx); theme_settings::init(theme::LoadThemes::JustBase, cx); }); } fn markdown_sizes() -> Vec { let mut sizes = vec![5_000, 10_000, 50_000, 250_000]; if std::env::var("ZED_BENCH_HUGE").is_ok() { sizes.push(1_000_000); } sizes } gpui::bench_group!(benches, markdown_render); gpui::bench_main!(benches);