diff --git a/Cargo.lock b/Cargo.lock index 3eebf5e3d5..4443526775 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1558,12 +1558,12 @@ dependencies = [ "gpui", "language", "log", - "markdown_element", "menu", "picker", "postage", "project", "recent_projects", + "rich_text", "schemars", "serde", "serde_derive", @@ -2406,6 +2406,7 @@ dependencies = [ "project", "pulldown-cmark", "rand 0.8.5", + "rich_text", "rpc", "schemars", "serde", @@ -4324,24 +4325,6 @@ dependencies = [ "libc", ] -[[package]] -name = "markdown_element" -version = "0.1.0" -dependencies = [ - "anyhow", - "collections", - "futures 0.3.28", - "gpui", - "language", - "lazy_static", - "pulldown-cmark", - "smallvec", - "smol", - "sum_tree", - "theme", - "util", -] - [[package]] name = "matchers" version = "0.1.0" @@ -6261,6 +6244,24 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rich_text" +version = "0.1.0" +dependencies = [ + "anyhow", + "collections", + "futures 0.3.28", + "gpui", + "language", + "lazy_static", + "pulldown-cmark", + "smallvec", + "smol", + "sum_tree", + "theme", + "util", +] + [[package]] name = "ring" version = "0.16.20" diff --git a/Cargo.toml b/Cargo.toml index bc04baa17c..7dae3bd81f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,6 @@ members = [ "crates/lsp", "crates/media", "crates/menu", - "crates/markdown_element", "crates/node_runtime", "crates/outline", "crates/picker", @@ -65,6 +64,7 @@ members = [ "crates/sqlez", "crates/sqlez_macros", "crates/feature_flags", + "crates/rich_text", "crates/storybook", "crates/sum_tree", "crates/terminal", diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index 613ad4c7f5..98790778c9 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -37,7 +37,7 @@ fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } language = { path = "../language" } menu = { path = "../menu" } -markdown_element = { path = "../markdown_element" } +rich_text = { path = "../rich_text" } picker = { path = "../picker" } project = { path = "../project" } recent_projects = {path = "../recent_projects"} diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 0d60e7e155..b446521c5a 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -17,9 +17,9 @@ use gpui::{ View, ViewContext, ViewHandle, WeakViewHandle, }; use language::{language_settings::SoftWrap, LanguageRegistry}; -use markdown_element::{MarkdownData, MarkdownElement}; use menu::Confirm; use project::Fs; +use rich_text::RichText; use serde::{Deserialize, Serialize}; use settings::SettingsStore; use std::sync::Arc; @@ -50,7 +50,7 @@ pub struct ChatPanel { subscriptions: Vec, workspace: WeakViewHandle, has_focus: bool, - markdown_data: HashMap>, + markdown_data: HashMap, } #[derive(Serialize, Deserialize)] @@ -370,12 +370,10 @@ impl ChatPanel { }; let is_pending = message.is_pending(); - let markdown = self.markdown_data.entry(message.id).or_insert_with(|| { - Arc::new(markdown_element::render_markdown( - message.body, - &self.languages, - )) - }); + let text = self + .markdown_data + .entry(message.id) + .or_insert_with(|| rich_text::render_markdown(message.body, &self.languages, None)); let now = OffsetDateTime::now_utc(); let theme = theme::current(cx); @@ -401,11 +399,11 @@ impl ChatPanel { if is_continuation { Flex::row() .with_child( - MarkdownElement::new( - markdown.clone(), - style.body.clone(), + text.element( theme.editor.syntax.clone(), + style.body.clone(), theme.editor.document_highlight_read_background, + cx, ) .flex(1., true), ) @@ -457,11 +455,11 @@ impl ChatPanel { .with_child( Flex::row() .with_child( - MarkdownElement::new( - markdown.clone(), - style.body.clone(), + text.element( theme.editor.syntax.clone(), + style.body.clone(), theme.editor.document_highlight_read_background, + cx, ) .flex(1., true), ) diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index b0f8323a76..2c3d6227a9 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -36,6 +36,7 @@ language = { path = "../language" } lsp = { path = "../lsp" } project = { path = "../project" } rpc = { path = "../rpc" } +rich_text = { path = "../rich_text" } settings = { path = "../settings" } snippet = { path = "../snippet" } sum_tree = { path = "../sum_tree" } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index f460b18bce..553cb321c3 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -8,12 +8,12 @@ use futures::FutureExt; use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, - fonts::{HighlightStyle, Underline, Weight}, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, + AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, }; use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; +use rich_text::{new_paragraph, render_code, render_markdown_mut, RichText}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; @@ -346,158 +346,25 @@ fn show_hover( } fn render_blocks( - theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, language: Option<&Arc>, - style: &EditorStyle, -) -> RenderedInfo { - let mut text = String::new(); - let mut highlights = Vec::new(); - let mut region_ranges = Vec::new(); - let mut regions = Vec::new(); +) -> RichText { + let mut data = RichText { + text: Default::default(), + highlights: Default::default(), + region_ranges: Default::default(), + regions: Default::default(), + }; for block in blocks { match &block.kind { HoverBlockKind::PlainText => { - new_paragraph(&mut text, &mut Vec::new()); - text.push_str(&block.text); + new_paragraph(&mut data.text, &mut Vec::new()); + data.text.push_str(&block.text); } HoverBlockKind::Markdown => { - use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; - - let mut bold_depth = 0; - let mut italic_depth = 0; - let mut link_url = None; - let mut current_language = None; - let mut list_stack = Vec::new(); - - for event in Parser::new_ext(&block.text, Options::all()) { - let prev_len = text.len(); - match event { - Event::Text(t) => { - if let Some(language) = ¤t_language { - render_code( - &mut text, - &mut highlights, - t.as_ref(), - language, - style, - ); - } else { - text.push_str(t.as_ref()); - - let mut style = HighlightStyle::default(); - if bold_depth > 0 { - style.weight = Some(Weight::BOLD); - } - if italic_depth > 0 { - style.italic = Some(true); - } - if let Some(link_url) = link_url.clone() { - region_ranges.push(prev_len..text.len()); - regions.push(RenderedRegion { - link_url: Some(link_url), - code: false, - }); - style.underline = Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }); - } - - if style != HighlightStyle::default() { - let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { - if last_range.end == prev_len && last_style == &style { - last_range.end = text.len(); - new_highlight = false; - } - } - if new_highlight { - highlights.push((prev_len..text.len(), style)); - } - } - } - } - Event::Code(t) => { - text.push_str(t.as_ref()); - region_ranges.push(prev_len..text.len()); - if link_url.is_some() { - highlights.push(( - prev_len..text.len(), - HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), - ..Default::default() - }, - )); - } - regions.push(RenderedRegion { - code: true, - link_url: link_url.clone(), - }); - } - Event::Start(tag) => match tag { - Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), - Tag::Heading(_, _, _) => { - new_paragraph(&mut text, &mut list_stack); - bold_depth += 1; - } - Tag::CodeBlock(kind) => { - new_paragraph(&mut text, &mut list_stack); - current_language = if let CodeBlockKind::Fenced(language) = kind { - language_registry - .language_for_name(language.as_ref()) - .now_or_never() - .and_then(Result::ok) - } else { - language.cloned() - } - } - Tag::Emphasis => italic_depth += 1, - Tag::Strong => bold_depth += 1, - Tag::Link(_, url, _) => link_url = Some(url.to_string()), - Tag::List(number) => { - list_stack.push((number, false)); - } - Tag::Item => { - let len = list_stack.len(); - if let Some((list_number, has_content)) = list_stack.last_mut() { - *has_content = false; - if !text.is_empty() && !text.ends_with('\n') { - text.push('\n'); - } - for _ in 0..len - 1 { - text.push_str(" "); - } - if let Some(number) = list_number { - text.push_str(&format!("{}. ", number)); - *number += 1; - *has_content = false; - } else { - text.push_str("- "); - } - } - } - _ => {} - }, - Event::End(tag) => match tag { - Tag::Heading(_, _, _) => bold_depth -= 1, - Tag::CodeBlock(_) => current_language = None, - Tag::Emphasis => italic_depth -= 1, - Tag::Strong => bold_depth -= 1, - Tag::Link(_, _, _) => link_url = None, - Tag::List(_) => drop(list_stack.pop()), - _ => {} - }, - Event::HardBreak => text.push('\n'), - Event::SoftBreak => text.push(' '), - _ => {} - } - } + render_markdown_mut(&block.text, language_registry, language, &mut data) } HoverBlockKind::Code { language } => { if let Some(language) = language_registry @@ -505,62 +372,17 @@ fn render_blocks( .now_or_never() .and_then(Result::ok) { - render_code(&mut text, &mut highlights, &block.text, &language, style); + render_code(&mut data.text, &mut data.highlights, &block.text, &language); } else { - text.push_str(&block.text); + data.text.push_str(&block.text); } } } } - RenderedInfo { - theme_id, - text: text.trim().to_string(), - highlights, - region_ranges, - regions, - } -} + data.text = data.text.trim().to_string(); -fn render_code( - text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, - content: &str, - language: &Arc, - style: &EditorStyle, -) { - let prev_len = text.len(); - text.push_str(content); - for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { - if let Some(style) = highlight_id.style(&style.syntax) { - highlights.push((prev_len + range.start..prev_len + range.end, style)); - } - } -} - -fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { - let mut is_subsequent_paragraph_of_list = false; - if let Some((_, has_content)) = list_stack.last_mut() { - if *has_content { - is_subsequent_paragraph_of_list = true; - } else { - *has_content = true; - return; - } - } - - if !text.is_empty() { - if !text.ends_with('\n') { - text.push('\n'); - } - text.push('\n'); - } - for _ in 0..list_stack.len().saturating_sub(1) { - text.push_str(" "); - } - if is_subsequent_paragraph_of_list { - text.push_str(" "); - } + data } #[derive(Default)] @@ -623,22 +445,7 @@ pub struct InfoPopover { symbol_range: RangeInEditor, pub blocks: Vec, language: Option>, - rendered_content: Option, -} - -#[derive(Debug, Clone)] -struct RenderedInfo { - theme_id: usize, - text: String, - highlights: Vec<(Range, HighlightStyle)>, - region_ranges: Vec>, - regions: Vec, -} - -#[derive(Debug, Clone)] -struct RenderedRegion { - code: bool, - link_url: Option, + rendered_content: Option, } impl InfoPopover { @@ -647,63 +454,24 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - if let Some(rendered) = &self.rendered_content { - if rendered.theme_id != style.theme_id { - self.rendered_content = None; - } - } - let rendered_content = self.rendered_content.get_or_insert_with(|| { render_blocks( - style.theme_id, &self.blocks, self.project.read(cx).languages(), self.language.as_ref(), - style, ) }); - MouseEventHandler::new::(0, cx, |_, cx| { - let mut region_id = 0; - let view_id = cx.view_id(); - + MouseEventHandler::new::(0, cx, move |_, cx| { let code_span_background_color = style.document_highlight_read_background; - let regions = rendered_content.regions.clone(); Flex::column() .scrollable::(1, None, cx) - .with_child( - Text::new(rendered_content.text.clone(), style.text.clone()) - .with_highlights(rendered_content.highlights.clone()) - .with_custom_runs( - rendered_content.region_ranges.clone(), - move |ix, bounds, cx| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - cx.scene().push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - cx.scene().push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::( - MouseButton::Left, - move |_, _, cx| cx.platform().open_url(&url), - ), - ); - } - if region.code { - cx.scene().push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }, - ) - .with_soft_wrap(true), - ) + .with_child(rendered_content.element( + style.syntax.clone(), + style.text.clone(), + code_span_background_color, + cx, + )) .contained() .with_style(style.hover_popover.container) }) @@ -799,11 +567,12 @@ mod tests { InlayId, }; use collections::BTreeSet; - use gpui::fonts::Weight; + use gpui::fonts::{HighlightStyle, Underline, Weight}; use indoc::indoc; use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; + use rich_text::Highlight; use smol::stream::StreamExt; use unindent::Unindent; use util::test::marked_text_ranges; @@ -1014,7 +783,7 @@ mod tests { .await; cx.condition(|editor, _| editor.hover_state.visible()).await; - cx.editor(|editor, cx| { + cx.editor(|editor, _| { let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; assert_eq!( blocks, @@ -1024,8 +793,7 @@ mod tests { }], ); - let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = render_blocks(&blocks, &Default::default(), None); assert_eq!( rendered.text, code_str.trim(), @@ -1217,7 +985,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = render_blocks(&blocks, &Default::default(), None); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges @@ -1228,8 +996,21 @@ mod tests { rendered.text, expected_text, "wrong text for input {blocks:?}" ); + + let rendered_highlights: Vec<_> = rendered + .highlights + .iter() + .filter_map(|(range, highlight)| { + let style = match highlight { + Highlight::Id(id) => id.style(&style.syntax)?, + Highlight::Highlight(style) => style.clone(), + }; + Some((range.clone(), style)) + }) + .collect(); + assert_eq!( - rendered.highlights, expected_highlights, + rendered_highlights, expected_highlights, "wrong highlights for input {blocks:?}" ); } diff --git a/crates/markdown_element/Cargo.toml b/crates/rich_text/Cargo.toml similarity index 90% rename from crates/markdown_element/Cargo.toml rename to crates/rich_text/Cargo.toml index 920a3c3005..3d2c25406d 100644 --- a/crates/markdown_element/Cargo.toml +++ b/crates/rich_text/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "markdown_element" +name = "rich_text" version = "0.1.0" edition = "2021" publish = false [lib] -path = "src/markdown_element.rs" +path = "src/rich_text.rs" doctest = false [features] diff --git a/crates/markdown_element/src/markdown_element.rs b/crates/rich_text/src/rich_text.rs similarity index 62% rename from crates/markdown_element/src/markdown_element.rs rename to crates/rich_text/src/rich_text.rs index 66011d2a25..72c7bdf6c1 100644 --- a/crates/markdown_element/src/markdown_element.rs +++ b/crates/rich_text/src/rich_text.rs @@ -6,94 +6,68 @@ use gpui::{ elements::Text, fonts::{HighlightStyle, TextStyle, Underline, Weight}, platform::{CursorStyle, MouseButton}, - AnyElement, CursorRegion, Element, MouseRegion, + AnyElement, CursorRegion, Element, MouseRegion, ViewContext, }; use language::{HighlightId, Language, LanguageRegistry}; use theme::SyntaxTheme; #[derive(Debug, Clone, PartialEq, Eq)] -enum Highlight { +pub enum Highlight { Id(HighlightId), Highlight(HighlightStyle), } #[derive(Debug, Clone)] -pub struct MarkdownData { - text: String, - highlights: Vec<(Range, Highlight)>, - region_ranges: Vec>, - regions: Vec, +pub struct RichText { + pub text: String, + pub highlights: Vec<(Range, Highlight)>, + pub region_ranges: Vec>, + pub regions: Vec, } #[derive(Debug, Clone)] -struct RenderedRegion { +pub struct RenderedRegion { code: bool, link_url: Option, } -pub struct MarkdownElement { - data: Arc, - syntax: Arc, - style: TextStyle, - code_span_background_color: Color, -} - -impl MarkdownElement { - pub fn new( - data: Arc, - style: TextStyle, +impl RichText { + pub fn element( + &self, syntax: Arc, + style: TextStyle, code_span_background_color: Color, - ) -> Self { - Self { - data, - style, - syntax, - code_span_background_color, - } - } -} - -impl Element for MarkdownElement { - type LayoutState = AnyElement; - - type PaintState = (); - - fn layout( - &mut self, - constraint: gpui::SizeConstraint, - view: &mut V, - cx: &mut gpui::ViewContext, - ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { + cx: &mut ViewContext, + ) -> AnyElement { let mut region_id = 0; let view_id = cx.view_id(); - let code_span_background_color = self.code_span_background_color; - let data = self.data.clone(); - let mut element = Text::new(self.data.text.clone(), self.style.clone()) + let regions = self.regions.clone(); + + enum Markdown {} + Text::new(self.text.clone(), style.clone()) .with_highlights( - self.data - .highlights + self.highlights .iter() .filter_map(|(range, highlight)| { let style = match highlight { - Highlight::Id(id) => id.style(&self.syntax)?, + Highlight::Id(id) => id.style(&syntax)?, Highlight::Highlight(style) => style.clone(), }; Some((range.clone(), style)) }) .collect::>(), ) - .with_custom_runs(self.data.region_ranges.clone(), move |ix, bounds, cx| { + .with_custom_runs(self.region_ranges.clone(), move |ix, bounds, cx| { region_id += 1; - let region = data.regions[ix].clone(); + let region = regions[ix].clone(); if let Some(url) = region.link_url { cx.scene().push_cursor_region(CursorRegion { bounds, style: CursorStyle::PointingHand, }); cx.scene().push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) + MouseRegion::new::(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), @@ -109,55 +83,16 @@ impl Element for MarkdownElement { } }) .with_soft_wrap(true) - .into_any(); - - let constraint = element.layout(constraint, view, cx); - - (constraint, element) - } - - fn paint( - &mut self, - bounds: gpui::geometry::rect::RectF, - visible_bounds: gpui::geometry::rect::RectF, - layout: &mut Self::LayoutState, - view: &mut V, - cx: &mut gpui::ViewContext, - ) -> Self::PaintState { - layout.paint(bounds.origin(), visible_bounds, view, cx); - } - - fn rect_for_text_range( - &self, - range_utf16: std::ops::Range, - _: gpui::geometry::rect::RectF, - _: gpui::geometry::rect::RectF, - layout: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> Option { - layout.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _: gpui::geometry::rect::RectF, - layout: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> gpui::serde_json::Value { - layout.debug(view, cx) + .into_any() } } -pub fn render_markdown(block: String, language_registry: &Arc) -> MarkdownData { - let mut text = String::new(); - let mut highlights = Vec::new(); - let mut region_ranges = Vec::new(); - let mut regions = Vec::new(); - +pub fn render_markdown_mut( + block: &str, + language_registry: &Arc, + language: Option<&Arc>, + data: &mut RichText, +) { use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; let mut bold_depth = 0; @@ -167,13 +102,13 @@ pub fn render_markdown(block: String, language_registry: &Arc) let mut list_stack = Vec::new(); for event in Parser::new_ext(&block, Options::all()) { - let prev_len = text.len(); + let prev_len = data.text.len(); match event { Event::Text(t) => { if let Some(language) = ¤t_language { - render_code(&mut text, &mut highlights, t.as_ref(), language); + render_code(&mut data.text, &mut data.highlights, t.as_ref(), language); } else { - text.push_str(t.as_ref()); + data.text.push_str(t.as_ref()); let mut style = HighlightStyle::default(); if bold_depth > 0 { @@ -183,8 +118,8 @@ pub fn render_markdown(block: String, language_registry: &Arc) style.italic = Some(true); } if let Some(link_url) = link_url.clone() { - region_ranges.push(prev_len..text.len()); - regions.push(RenderedRegion { + data.region_ranges.push(prev_len..data.text.len()); + data.regions.push(RenderedRegion { link_url: Some(link_url), code: false, }); @@ -196,26 +131,27 @@ pub fn render_markdown(block: String, language_registry: &Arc) if style != HighlightStyle::default() { let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { + if let Some((last_range, last_style)) = data.highlights.last_mut() { if last_range.end == prev_len && last_style == &Highlight::Highlight(style) { - last_range.end = text.len(); + last_range.end = data.text.len(); new_highlight = false; } } if new_highlight { - highlights.push((prev_len..text.len(), Highlight::Highlight(style))); + data.highlights + .push((prev_len..data.text.len(), Highlight::Highlight(style))); } } } } Event::Code(t) => { - text.push_str(t.as_ref()); - region_ranges.push(prev_len..text.len()); + data.text.push_str(t.as_ref()); + data.region_ranges.push(prev_len..data.text.len()); if link_url.is_some() { - highlights.push(( - prev_len..text.len(), + data.highlights.push(( + prev_len..data.text.len(), Highlight::Highlight(HighlightStyle { underline: Some(Underline { thickness: 1.0.into(), @@ -225,26 +161,26 @@ pub fn render_markdown(block: String, language_registry: &Arc) }), )); } - regions.push(RenderedRegion { + data.regions.push(RenderedRegion { code: true, link_url: link_url.clone(), }); } Event::Start(tag) => match tag { - Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + Tag::Paragraph => new_paragraph(&mut data.text, &mut list_stack), Tag::Heading(_, _, _) => { - new_paragraph(&mut text, &mut list_stack); + new_paragraph(&mut data.text, &mut list_stack); bold_depth += 1; } Tag::CodeBlock(kind) => { - new_paragraph(&mut text, &mut list_stack); + new_paragraph(&mut data.text, &mut list_stack); current_language = if let CodeBlockKind::Fenced(language) = kind { language_registry .language_for_name(language.as_ref()) .now_or_never() .and_then(Result::ok) } else { - None + language.cloned() } } Tag::Emphasis => italic_depth += 1, @@ -257,18 +193,18 @@ pub fn render_markdown(block: String, language_registry: &Arc) let len = list_stack.len(); if let Some((list_number, has_content)) = list_stack.last_mut() { *has_content = false; - if !text.is_empty() && !text.ends_with('\n') { - text.push('\n'); + if !data.text.is_empty() && !data.text.ends_with('\n') { + data.text.push('\n'); } for _ in 0..len - 1 { - text.push_str(" "); + data.text.push_str(" "); } if let Some(number) = list_number { - text.push_str(&format!("{}. ", number)); + data.text.push_str(&format!("{}. ", number)); *number += 1; *has_content = false; } else { - text.push_str("- "); + data.text.push_str("- "); } } } @@ -283,21 +219,33 @@ pub fn render_markdown(block: String, language_registry: &Arc) Tag::List(_) => drop(list_stack.pop()), _ => {} }, - Event::HardBreak => text.push('\n'), - Event::SoftBreak => text.push(' '), + Event::HardBreak => data.text.push('\n'), + Event::SoftBreak => data.text.push(' '), _ => {} } } - - MarkdownData { - text: text.trim().to_string(), - highlights, - region_ranges, - regions, - } } -fn render_code( +pub fn render_markdown( + block: String, + language_registry: &Arc, + language: Option<&Arc>, +) -> RichText { + let mut data = RichText { + text: Default::default(), + highlights: Default::default(), + region_ranges: Default::default(), + regions: Default::default(), + }; + + render_markdown_mut(&block, language_registry, language, &mut data); + + data.text = data.text.trim().to_string(); + + data +} + +pub fn render_code( text: &mut String, highlights: &mut Vec<(Range, Highlight)>, content: &str, @@ -313,7 +261,7 @@ fn render_code( } } -fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { +pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { let mut is_subsequent_paragraph_of_list = false; if let Some((_, has_content)) = list_stack.last_mut() { if *has_content {