From 8848291d5d25790b158a304cfd7408f720ebdc5c Mon Sep 17 00:00:00 2001 From: Erlend Walstad <96946613+lampsitter@users.noreply.github.com> Date: Sat, 7 Sep 2024 20:29:14 +0200 Subject: [PATCH] Inline code blocks --- CHANGELOG.md | 1 + egui_commonmark/examples/macros.rs | 16 ++++-- .../examples/markdown/code-blocks.md | 3 ++ egui_commonmark/examples/mixing.rs | 3 ++ egui_commonmark/examples/scroll.rs | 2 +- egui_commonmark/src/parsers/pulldown.rs | 33 +++++++----- egui_commonmark_backend/src/lib.rs | 2 +- egui_commonmark_backend/src/misc.rs | 36 +++++++++---- egui_commonmark_macros/src/generator.rs | 53 ++++++++++--------- 9 files changed, 95 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81a86c9..2050bce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - Definition lists +- Proper inline code block rendering ### Changed diff --git a/egui_commonmark/examples/macros.rs b/egui_commonmark/examples/macros.rs index 61d10be..59865d5 100644 --- a/egui_commonmark/examples/macros.rs +++ b/egui_commonmark/examples/macros.rs @@ -61,11 +61,17 @@ impl eframe::App for App { commonmark!(ui, &mut self.cache, "------------"); - commonmark_str!( - ui, - &mut self.cache, - "egui_commonmark/examples/markdown/tables.md" - ); + // The table will end up with the same id as the table in the hello_world file. + // Providing the id explicitly is annoying for all other widgets that are not tables + // so push_id must be used in this case. + ui.push_id("tables", |ui| { + commonmark_str!( + ui, + &mut self.cache, + "egui_commonmark/examples/markdown/tables.md" + ); + }); + commonmark!(ui, &mut self.cache, "------------"); commonmark_str!( diff --git a/egui_commonmark/examples/markdown/code-blocks.md b/egui_commonmark/examples/markdown/code-blocks.md index 91e0f22..d787865 100644 --- a/egui_commonmark/examples/markdown/code-blocks.md +++ b/egui_commonmark/examples/markdown/code-blocks.md @@ -26,4 +26,7 @@ image = { version = "0.24", default-features = false, features = ["png"] } ``` - Code blocks can be in lists too :) + More content... + + Inline code blocks are supported if you for some reason need them diff --git a/egui_commonmark/examples/mixing.rs b/egui_commonmark/examples/mixing.rs index e81b2b1..3ea1395 100644 --- a/egui_commonmark/examples/mixing.rs +++ b/egui_commonmark/examples/mixing.rs @@ -54,6 +54,9 @@ let x = 3; ``` "#, r#" + let x = 3; + "#, + r#" A footnote [^F1] [^F1]: The footnote"#, diff --git a/egui_commonmark/examples/scroll.rs b/egui_commonmark/examples/scroll.rs index 0dc50be..e75cb68 100644 --- a/egui_commonmark/examples/scroll.rs +++ b/egui_commonmark/examples/scroll.rs @@ -15,7 +15,7 @@ struct App { impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { let mut text = r#"# Commonmark Viewer Example - This is a fairly large markdown file showcasing scroll. +This is a fairly large markdown file showcasing scroll. "# .to_string(); diff --git a/egui_commonmark/src/parsers/pulldown.rs b/egui_commonmark/src/parsers/pulldown.rs index 7afc7a9..d217515 100644 --- a/egui_commonmark/src/parsers/pulldown.rs +++ b/egui_commonmark/src/parsers/pulldown.rs @@ -71,7 +71,7 @@ pub struct CommonMarkViewerInternal { link: Option, image: Option, line: Newline, - fenced_code_block: Option, + code_block: Option, is_list_item: bool, def_list: DefinitionList, is_table: bool, @@ -95,7 +95,7 @@ impl CommonMarkViewerInternal { line: Newline::default(), is_list_item: false, def_list: Default::default(), - fenced_code_block: None, + code_block: None, is_table: false, is_blockquote: false, checkbox_events: Vec::new(), @@ -542,7 +542,7 @@ impl CommonMarkViewerInternal { let rich_text = self.text_style.to_richtext(ui, &text); if let Some(image) = &mut self.image { image.alt_text.push(rich_text); - } else if let Some(block) = &mut self.fenced_code_block { + } else if let Some(block) = &mut self.code_block { block.content.push_str(&text); } else if let Some(link) = &mut self.link { link.text.push(rich_text); @@ -576,17 +576,23 @@ impl CommonMarkViewerInternal { self.is_blockquote = true; } pulldown_cmark::Tag::CodeBlock(c) => { - if let pulldown_cmark::CodeBlockKind::Fenced(lang) = c { - self.fenced_code_block = Some(crate::FencedCodeBlock { - lang: lang.to_string(), - content: "".to_string(), - }); - - self.line.try_insert_start(ui); + match c { + pulldown_cmark::CodeBlockKind::Fenced(lang) => { + self.code_block = Some(crate::CodeBlock { + lang: Some(lang.to_string()), + content: "".to_string(), + }); + } + pulldown_cmark::CodeBlockKind::Indented => { + self.code_block = Some(crate::CodeBlock { + lang: None, + content: "".to_string(), + }); + } } - - self.text_style.code = true; + self.line.try_insert_start(ui); } + pulldown_cmark::Tag::List(point) => { if !self.list.is_inside_a_list() && self.line.can_insert_start() { newline(ui); @@ -739,10 +745,9 @@ impl CommonMarkViewerInternal { options: &CommonMarkOptions, max_width: f32, ) { - if let Some(block) = self.fenced_code_block.take() { + if let Some(block) = self.code_block.take() { block.end(ui, cache, options, max_width); self.line.try_insert_end(ui); } - self.text_style.code = false; } } diff --git a/egui_commonmark_backend/src/lib.rs b/egui_commonmark_backend/src/lib.rs index d516589..4c0e35f 100644 --- a/egui_commonmark_backend/src/lib.rs +++ b/egui_commonmark_backend/src/lib.rs @@ -17,7 +17,7 @@ pub use { alerts::{alert_ui, Alert, AlertBundle}, // Pretty much every single element in this module is used by the proc macros elements::*, - misc::{prepare_show, CommonMarkOptions, FencedCodeBlock, Image, Link}, + misc::{prepare_show, CodeBlock, CommonMarkOptions, Image, Link}, }; // The only struct that is allowed to use directly. (If one does not need egui_commonmark) diff --git a/egui_commonmark_backend/src/misc.rs b/egui_commonmark_backend/src/misc.rs index d5c6da8..91519a5 100644 --- a/egui_commonmark_backend/src/misc.rs +++ b/egui_commonmark_backend/src/misc.rs @@ -229,12 +229,12 @@ impl Image { } } -pub struct FencedCodeBlock { - pub lang: String, +pub struct CodeBlock { + pub lang: Option, pub content: String, } -impl FencedCodeBlock { +impl CodeBlock { pub fn end( &self, ui: &mut Ui, @@ -246,7 +246,12 @@ impl FencedCodeBlock { Self::pre_syntax_highlighting(cache, options, ui); let mut layout = |ui: &Ui, string: &str, wrap_width: f32| { - let mut job = self.syntax_highlighting(cache, options, &self.lang, ui, string); + let mut job = if let Some(lang) = &self.lang { + self.syntax_highlighting(cache, options, lang, ui, string) + } else { + plain_highlighting(ui, string) + }; + job.wrap.max_width = wrap_width; ui.fonts(|f| f.layout_job(job)) }; @@ -257,7 +262,7 @@ impl FencedCodeBlock { } #[cfg(not(feature = "better_syntax_highlighting"))] -impl FencedCodeBlock { +impl CodeBlock { fn pre_syntax_highlighting( _cache: &mut CommonMarkCache, _options: &CommonMarkOptions, @@ -274,12 +279,12 @@ impl FencedCodeBlock { ui: &Ui, text: &str, ) -> egui::text::LayoutJob { - plain_highlighting(ui, text, extension) + simple_highlighting(ui, text, extension) } } #[cfg(feature = "better_syntax_highlighting")] -impl FencedCodeBlock { +impl CodeBlock { fn pre_syntax_highlighting( cache: &mut CommonMarkCache, options: &CommonMarkOptions, @@ -328,12 +333,12 @@ impl FencedCodeBlock { job } else { - plain_highlighting(ui, text, extension) + simple_highlighting(ui, text, extension) } } } -fn plain_highlighting(ui: &Ui, text: &str, extension: &str) -> egui::text::LayoutJob { +fn simple_highlighting(ui: &Ui, text: &str, extension: &str) -> egui::text::LayoutJob { egui_extras::syntax_highlighting::highlight( ui.ctx(), &egui_extras::syntax_highlighting::CodeTheme::from_style(ui.style()), @@ -342,6 +347,19 @@ fn plain_highlighting(ui: &Ui, text: &str, extension: &str) -> egui::text::Layou ) } +fn plain_highlighting(ui: &Ui, text: &str) -> egui::text::LayoutJob { + let mut job = egui::text::LayoutJob::default(); + job.append( + text, + 0.0, + egui::TextFormat::simple( + TextStyle::Monospace.resolve(ui.style()), + ui.style().visuals.text_color(), + ), + ); + job +} + #[cfg(feature = "better_syntax_highlighting")] fn syntect_color_to_egui(color: syntect::highlighting::Color) -> egui::Color32 { egui::Color32::from_rgb(color.r, color.g, color.b) diff --git a/egui_commonmark_macros/src/generator.rs b/egui_commonmark_macros/src/generator.rs index 8d4e8ee..dafe89b 100644 --- a/egui_commonmark_macros/src/generator.rs +++ b/egui_commonmark_macros/src/generator.rs @@ -1,7 +1,7 @@ use std::iter::Peekable; use egui_commonmark_backend::{ - alerts::Alert, misc::Style, pulldown::*, CommonMarkOptions, FencedCodeBlock, Image, + alerts::Alert, misc::Style, pulldown::*, CodeBlock, CommonMarkOptions, Image, }; use proc_macro2::TokenStream; @@ -169,7 +169,7 @@ pub(crate) struct CommonMarkViewerInternal { link: Option, image: Option, line: Newline, - fenced_code_block: Option, + code_block: Option, is_list_item: bool, def_list: DefinitionList, is_table: bool, @@ -192,7 +192,7 @@ impl CommonMarkViewerInternal { line: Newline::default(), is_list_item: false, def_list: Default::default(), - fenced_code_block: None, + code_block: None, is_table: false, is_blockquote: false, dumps_heading: false, @@ -559,7 +559,7 @@ impl CommonMarkViewerInternal { image .alt_text .push(StyledText::new(self.text_style.clone(), text.to_string())); - } else if let Some(block) = &mut self.fenced_code_block { + } else if let Some(block) = &mut self.code_block { block.content.push_str(&text); } else if let Some(link) = &mut self.link { link.text @@ -595,18 +595,22 @@ impl CommonMarkViewerInternal { TokenStream::new() } pulldown_cmark::Tag::CodeBlock(c) => { - self.text_style.code = true; - - if let pulldown_cmark::CodeBlockKind::Fenced(lang) = c { - self.fenced_code_block = Some(FencedCodeBlock { - lang: lang.to_string(), - content: "".to_string(), - }); - - self.line.try_insert_start() - } else { - TokenStream::new() + match c { + pulldown_cmark::CodeBlockKind::Fenced(lang) => { + self.code_block = Some(CodeBlock { + lang: Some(lang.to_string()), + content: "".to_string(), + }); + } + pulldown_cmark::CodeBlockKind::Indented => { + self.code_block = Some(CodeBlock { + lang: None, + content: "".to_string(), + }); + } } + + self.line.try_insert_start() } pulldown_cmark::Tag::List(point) => { @@ -807,21 +811,22 @@ impl CommonMarkViewerInternal { fn end_code_block(&mut self, cache: &Expr) -> TokenStream { let mut stream = TokenStream::new(); - if let Some(block) = self.fenced_code_block.take() { - let lang = block.lang; + if let Some(block) = self.code_block.take() { let content = block.content; - stream.extend( - quote!( - egui_commonmark_backend::FencedCodeBlock {lang: #lang.to_owned(), content: #content.to_owned()} - .end(ui, #cache, &options, max_width); - )); + stream.extend(if let Some(lang) = block.lang { + quote!(egui_commonmark_backend::CodeBlock { + lang: Some(#lang.to_owned()), content: #content.to_owned()} + .end(ui, #cache, &options, max_width);) + } else { + quote!(egui_commonmark_backend::CodeBlock { + lang: None, content: #content.to_owned()} + .end(ui, #cache, &options, max_width);) + }); stream.extend(self.line.try_insert_end()); } - self.text_style.code = false; - stream }