diff --git a/Cargo.lock b/Cargo.lock index 431865335480c136f9e8aedff40b827a9d5db891..662703a1beddbd9536b68fe893501578f0d4459d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13764,7 +13764,7 @@ dependencies = [ "language", "languages", "log", - "markdown_preview", + "markdown", "menu", "multi_buffer", "nbformat", diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 8ef92266ea28a60426df2a200c5542b415aadf0c..0ec6fd6b36f28fa9621017b261fc0d169fa08974 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -28,16 +28,15 @@ use file_icons::FileIcons; use fs::Fs; use futures::FutureExt as _; use gpui::{ - Action, Animation, AnimationExt, AnyView, App, BorderStyle, ClickEvent, ClipboardItem, - CursorStyle, EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length, - ListOffset, ListState, ObjectFit, PlatformDisplay, ScrollHandle, SharedString, StyleRefinement, - Subscription, Task, TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, Window, + Action, Animation, AnimationExt, AnyView, App, ClickEvent, ClipboardItem, CursorStyle, + ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, ListOffset, ListState, ObjectFit, + PlatformDisplay, ScrollHandle, SharedString, Subscription, Task, TextStyle, WeakEntity, Window, WindowHandle, div, ease_in_out, img, linear_color_stop, linear_gradient, list, point, pulsating_between, }; use language::Buffer; use language_model::LanguageModelRegistry; -use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle}; +use markdown::{Markdown, MarkdownElement, MarkdownFont, MarkdownStyle}; use project::{AgentServerStore, ExternalAgentServerName, Project, ProjectEntryId}; use prompt_store::{PromptId, PromptStore}; use rope::Point; @@ -49,7 +48,7 @@ use std::time::Instant; use std::{collections::BTreeMap, rc::Rc, time::Duration}; use terminal_view::terminal_panel::TerminalPanel; use text::{Anchor, ToPoint as _}; -use theme::{AgentFontSize, ThemeSettings}; +use theme::AgentFontSize; use ui::{ Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, CopyButton, DecoratedIcon, DiffStat, Disclosure, Divider, DividerColor, IconButtonShape, IconDecoration, @@ -2787,7 +2786,7 @@ impl AcpThreadView { let mut is_blank = true; let is_last = entry_ix + 1 == total_entries; - let style = default_markdown_style(false, false, window, cx); + let style = MarkdownStyle::themed(MarkdownFont::Agent, window, cx); let message_body = v_flex() .w_full() .gap_3() @@ -3072,9 +3071,10 @@ impl AcpThreadView { }) .text_ui_sm(cx) .overflow_hidden() - .child( - self.render_markdown(chunk, default_markdown_style(false, false, window, cx)), - ) + .child(self.render_markdown( + chunk, + MarkdownStyle::themed(MarkdownFont::Agent, window, cx), + )) }; v_flex() @@ -3277,7 +3277,11 @@ impl AcpThreadView { |input| { self.render_markdown( input, - default_markdown_style(false, false, window, cx), + MarkdownStyle::themed( + MarkdownFont::Agent, + window, + cx, + ), ) }, )) @@ -3302,31 +3306,34 @@ impl AcpThreadView { | ToolCallStatus::InProgress | ToolCallStatus::Completed | ToolCallStatus::Failed - | ToolCallStatus::Canceled => { - v_flex() - .when(should_show_raw_input, |this| { - this.mt_1p5().w_full().child( - v_flex() - .ml(rems(0.4)) - .px_3p5() - .pb_1() - .gap_1() - .border_l_1() - .border_color(self.tool_card_border_color(cx)) - .child(input_output_header("Raw Input:".into())) - .children(tool_call.raw_input_markdown.clone().map(|input| { - div().id(("tool-call-raw-input-markdown", entry_ix)).child( - self.render_markdown( - input, - default_markdown_style(false, false, window, cx), - ), - ) - })) - .child(input_output_header("Output:".into())), - ) - }) - .children(tool_call.content.iter().enumerate().map( - |(content_ix, content)| { + | ToolCallStatus::Canceled => v_flex() + .when(should_show_raw_input, |this| { + this.mt_1p5().w_full().child( + v_flex() + .ml(rems(0.4)) + .px_3p5() + .pb_1() + .gap_1() + .border_l_1() + .border_color(self.tool_card_border_color(cx)) + .child(input_output_header("Raw Input:".into())) + .children(tool_call.raw_input_markdown.clone().map(|input| { + div().id(("tool-call-raw-input-markdown", entry_ix)).child( + self.render_markdown( + input, + MarkdownStyle::themed(MarkdownFont::Agent, window, cx), + ), + ) + })) + .child(input_output_header("Output:".into())), + ) + }) + .children( + tool_call + .content + .iter() + .enumerate() + .map(|(content_ix, content)| { div().id(("tool-call-output", entry_ix)).child( self.render_tool_call_content( entry_ix, @@ -3340,10 +3347,9 @@ impl AcpThreadView { cx, ), ) - }, - )) - .into_any() - } + }), + ) + .into_any(), ToolCallStatus::Rejected => Empty.into_any(), } .into() @@ -3613,13 +3619,16 @@ impl AcpThreadView { this.text_color(cx.theme().colors().text_muted) } }) - .child(self.render_markdown( - tool_call.label.clone(), - MarkdownStyle { - prevent_mouse_interaction: true, - ..default_markdown_style(false, true, window, cx) - }, - )) + .child( + self.render_markdown( + tool_call.label.clone(), + MarkdownStyle { + prevent_mouse_interaction: true, + ..MarkdownStyle::themed(MarkdownFont::Agent, window, cx) + .with_muted_text(cx) + }, + ), + ) .tooltip(Tooltip::text("Go to File")) .on_click(cx.listener(move |this, _, window, cx| { this.open_tool_call_location(entry_ix, 0, window, cx); @@ -3630,7 +3639,7 @@ impl AcpThreadView { .w_full() .child(self.render_markdown( tool_call.label.clone(), - default_markdown_style(false, true, window, cx), + MarkdownStyle::themed(MarkdownFont::Agent, window, cx).with_muted_text(cx), )) .into_any() }) @@ -3962,7 +3971,7 @@ impl AcpThreadView { .when_some(last_assistant_markdown, |this, markdown| { this.child(self.render_markdown( markdown, - default_markdown_style(false, false, window, cx), + MarkdownStyle::themed(MarkdownFont::Agent, window, cx), )) }), ) @@ -3998,7 +4007,10 @@ impl AcpThreadView { }) .text_xs() .text_color(cx.theme().colors().text_muted) - .child(self.render_markdown(markdown, default_markdown_style(false, false, window, cx))) + .child(self.render_markdown( + markdown, + MarkdownStyle::themed(MarkdownFont::Agent, window, cx), + )) .when(!card_layout, |this| { this.child( IconButton::new(button_id, IconName::ChevronUp) @@ -5173,7 +5185,7 @@ impl AcpThreadView { .children(description.map(|desc| { self.render_markdown( desc.clone(), - default_markdown_style(false, false, window, cx), + MarkdownStyle::themed(MarkdownFont::Agent, window, cx), ) })) } @@ -8240,7 +8252,8 @@ impl AcpThreadView { markdown }; - let markdown_style = default_markdown_style(false, true, window, cx); + let markdown_style = + MarkdownStyle::themed(MarkdownFont::Agent, window, cx).with_muted_text(cx); let description = self .render_markdown(markdown, markdown_style) .into_any_element(); @@ -8721,138 +8734,12 @@ impl Render for AcpThreadView { } } -fn default_markdown_style( - buffer_font: bool, - muted_text: bool, - window: &Window, - cx: &App, -) -> MarkdownStyle { - let theme_settings = ThemeSettings::get_global(cx); - let colors = cx.theme().colors(); - - let buffer_font_size = theme_settings.agent_buffer_font_size(cx); - - let mut text_style = window.text_style(); - let line_height = buffer_font_size * 1.75; - - let font_family = if buffer_font { - theme_settings.buffer_font.family.clone() - } else { - theme_settings.ui_font.family.clone() - }; - - let font_size = if buffer_font { - theme_settings.agent_buffer_font_size(cx) - } else { - theme_settings.agent_ui_font_size(cx) - }; - - let text_color = if muted_text { - colors.text_muted - } else { - colors.text - }; - - text_style.refine(&TextStyleRefinement { - font_family: Some(font_family), - font_fallbacks: theme_settings.ui_font.fallbacks.clone(), - font_features: Some(theme_settings.ui_font.features.clone()), - font_size: Some(font_size.into()), - line_height: Some(line_height.into()), - color: Some(text_color), - ..Default::default() - }); - - MarkdownStyle { - base_text_style: text_style.clone(), - syntax: cx.theme().syntax().clone(), - selection_background_color: colors.element_selection_background, - code_block_overflow_x_scroll: true, - heading_level_styles: Some(HeadingLevelStyles { - h1: Some(TextStyleRefinement { - font_size: Some(rems(1.15).into()), - ..Default::default() - }), - h2: Some(TextStyleRefinement { - font_size: Some(rems(1.1).into()), - ..Default::default() - }), - h3: Some(TextStyleRefinement { - font_size: Some(rems(1.05).into()), - ..Default::default() - }), - h4: Some(TextStyleRefinement { - font_size: Some(rems(1.).into()), - ..Default::default() - }), - h5: Some(TextStyleRefinement { - font_size: Some(rems(0.95).into()), - ..Default::default() - }), - h6: Some(TextStyleRefinement { - font_size: Some(rems(0.875).into()), - ..Default::default() - }), - }), - code_block: StyleRefinement { - padding: EdgesRefinement { - top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))), - left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))), - right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))), - bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))), - }, - margin: EdgesRefinement { - top: Some(Length::Definite(px(8.).into())), - left: Some(Length::Definite(px(0.).into())), - right: Some(Length::Definite(px(0.).into())), - bottom: Some(Length::Definite(px(12.).into())), - }, - border_style: Some(BorderStyle::Solid), - border_widths: EdgesRefinement { - top: Some(AbsoluteLength::Pixels(px(1.))), - left: Some(AbsoluteLength::Pixels(px(1.))), - right: Some(AbsoluteLength::Pixels(px(1.))), - bottom: Some(AbsoluteLength::Pixels(px(1.))), - }, - border_color: Some(colors.border_variant), - background: Some(colors.editor_background.into()), - text: TextStyleRefinement { - font_family: Some(theme_settings.buffer_font.family.clone()), - font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), - font_features: Some(theme_settings.buffer_font.features.clone()), - font_size: Some(buffer_font_size.into()), - ..Default::default() - }, - ..Default::default() - }, - inline_code: TextStyleRefinement { - font_family: Some(theme_settings.buffer_font.family.clone()), - font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), - font_features: Some(theme_settings.buffer_font.features.clone()), - font_size: Some(buffer_font_size.into()), - background_color: Some(colors.editor_foreground.opacity(0.08)), - ..Default::default() - }, - link: TextStyleRefinement { - background_color: Some(colors.editor_foreground.opacity(0.025)), - color: Some(colors.text_accent), - underline: Some(UnderlineStyle { - color: Some(colors.text_accent.opacity(0.5)), - thickness: px(1.), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - } -} - fn plan_label_markdown_style( status: &acp::PlanEntryStatus, window: &Window, cx: &App, ) -> MarkdownStyle { - let default_md_style = default_markdown_style(false, false, window, cx); + let default_md_style = MarkdownStyle::themed(MarkdownFont::Agent, window, cx); MarkdownStyle { base_text_style: TextStyle { diff --git a/crates/markdown/Cargo.toml b/crates/markdown/Cargo.toml index 3f9b4ea366eca21a25ca422f4b1c5681d4ae9765..f58df4da86784d626b7428b60133475dc952aa8e 100644 --- a/crates/markdown/Cargo.toml +++ b/crates/markdown/Cargo.toml @@ -27,6 +27,7 @@ language.workspace = true linkify.workspace = true log.workspace = true pulldown-cmark.workspace = true +settings.workspace = true sum_tree.workspace = true theme.workspace = true ui.workspace = true diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 950fc4a2eaf86739752c4ec3139fe07520f9985b..f5e3ee1366cbae0a91c24efde1f034d2a2d5ffb1 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -3,10 +3,14 @@ mod path_range; use base64::Engine as _; use futures::FutureExt as _; +use gpui::EdgesRefinement; use gpui::HitboxBehavior; +use gpui::UnderlineStyle; use language::LanguageName; use log::Level; pub use path_range::{LineCol, PathWithRange}; +use settings::Settings as _; +use theme::ThemeSettings; use ui::Checkbox; use ui::CopyButton; @@ -98,6 +102,133 @@ impl Default for MarkdownStyle { } } +pub enum MarkdownFont { + Agent, + Editor, +} + +impl MarkdownStyle { + pub fn themed(font: MarkdownFont, window: &Window, cx: &App) -> Self { + let theme_settings = ThemeSettings::get_global(cx); + let colors = cx.theme().colors(); + + let (buffer_font_size, ui_font_size) = match font { + MarkdownFont::Agent => ( + theme_settings.agent_buffer_font_size(cx), + theme_settings.agent_ui_font_size(cx), + ), + MarkdownFont::Editor => ( + theme_settings.buffer_font_size(cx), + theme_settings.ui_font_size(cx), + ), + }; + + let text_color = colors.text; + + let mut text_style = window.text_style(); + let line_height = buffer_font_size * 1.75; + + text_style.refine(&TextStyleRefinement { + font_family: Some(theme_settings.ui_font.family.clone()), + font_fallbacks: theme_settings.ui_font.fallbacks.clone(), + font_features: Some(theme_settings.ui_font.features.clone()), + font_size: Some(ui_font_size.into()), + line_height: Some(line_height.into()), + color: Some(text_color), + ..Default::default() + }); + + MarkdownStyle { + base_text_style: text_style.clone(), + syntax: cx.theme().syntax().clone(), + selection_background_color: colors.element_selection_background, + code_block_overflow_x_scroll: true, + heading_level_styles: Some(HeadingLevelStyles { + h1: Some(TextStyleRefinement { + font_size: Some(rems(1.15).into()), + ..Default::default() + }), + h2: Some(TextStyleRefinement { + font_size: Some(rems(1.1).into()), + ..Default::default() + }), + h3: Some(TextStyleRefinement { + font_size: Some(rems(1.05).into()), + ..Default::default() + }), + h4: Some(TextStyleRefinement { + font_size: Some(rems(1.).into()), + ..Default::default() + }), + h5: Some(TextStyleRefinement { + font_size: Some(rems(0.95).into()), + ..Default::default() + }), + h6: Some(TextStyleRefinement { + font_size: Some(rems(0.875).into()), + ..Default::default() + }), + }), + code_block: StyleRefinement { + padding: EdgesRefinement { + top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))), + left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))), + right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))), + bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))), + }, + margin: EdgesRefinement { + top: Some(Length::Definite(px(8.).into())), + left: Some(Length::Definite(px(0.).into())), + right: Some(Length::Definite(px(0.).into())), + bottom: Some(Length::Definite(px(12.).into())), + }, + border_style: Some(BorderStyle::Solid), + border_widths: EdgesRefinement { + top: Some(AbsoluteLength::Pixels(px(1.))), + left: Some(AbsoluteLength::Pixels(px(1.))), + right: Some(AbsoluteLength::Pixels(px(1.))), + bottom: Some(AbsoluteLength::Pixels(px(1.))), + }, + border_color: Some(colors.border_variant), + background: Some(colors.editor_background.into()), + text: TextStyleRefinement { + font_family: Some(theme_settings.buffer_font.family.clone()), + font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), + font_features: Some(theme_settings.buffer_font.features.clone()), + font_size: Some(buffer_font_size.into()), + ..Default::default() + }, + ..Default::default() + }, + inline_code: TextStyleRefinement { + font_family: Some(theme_settings.buffer_font.family.clone()), + font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), + font_features: Some(theme_settings.buffer_font.features.clone()), + font_size: Some(buffer_font_size.into()), + background_color: Some(colors.editor_foreground.opacity(0.08)), + ..Default::default() + }, + link: TextStyleRefinement { + background_color: Some(colors.editor_foreground.opacity(0.025)), + color: Some(colors.text_accent), + underline: Some(UnderlineStyle { + color: Some(colors.text_accent.opacity(0.5)), + thickness: px(1.), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + } + } + + pub fn with_muted_text(mut self, cx: &App) -> Self { + let colors = cx.theme().colors(); + self.base_text_style.color = colors.text_muted; + self + } +} + pub struct Markdown { source: SharedString, selection: Selection, diff --git a/crates/repl/Cargo.toml b/crates/repl/Cargo.toml index ca263d4edf14bb84407cf47dd3c0b17c60255a6f..1047dd68f2b10181765916612fd330b27fed66ad 100644 --- a/crates/repl/Cargo.toml +++ b/crates/repl/Cargo.toml @@ -36,7 +36,7 @@ jupyter-websocket-client.workspace = true jupyter-protocol.workspace = true language.workspace = true log.workspace = true -markdown_preview.workspace = true +markdown.workspace = true menu.workspace = true multi_buffer.workspace = true nbformat.workspace = true diff --git a/crates/repl/src/notebook/cell.rs b/crates/repl/src/notebook/cell.rs index 495285f86e44266d22455d56ff202e69f5f4c2fe..a62c6cd4b7baedff929442e93f90a9c8e3f736a8 100644 --- a/crates/repl/src/notebook/cell.rs +++ b/crates/repl/src/notebook/cell.rs @@ -9,7 +9,7 @@ use gpui::{ StatefulInteractiveElement, Task, TextStyleRefinement, image_cache, prelude::*, }; use language::{Buffer, Language, LanguageRegistry}; -use markdown_preview::{markdown_parser::parse_markdown, markdown_renderer::render_markdown_block}; +use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use nbformat::v4::{CellId, CellMetadata, CellType}; use runtimelib::{JupyterMessage, JupyterMessageContent}; use settings::Settings as _; @@ -322,8 +322,7 @@ pub struct MarkdownCell { image_cache: Entity, source: String, editor: Entity, - parsed_markdown: Option, - markdown_parsing_task: Task<()>, + markdown: Entity, editing: bool, selected: bool, cell_position: Option, @@ -381,23 +380,7 @@ impl MarkdownCell { editor }); - let markdown_parsing_task = { - let languages = languages.clone(); - let source = source.clone(); - - cx.spawn_in(window, async move |this, cx| { - let parsed_markdown = cx - .background_spawn(async move { - parse_markdown(&source, None, Some(languages)).await - }) - .await; - - this.update(cx, |cell: &mut MarkdownCell, _| { - cell.parsed_markdown = Some(parsed_markdown); - }) - .log_err(); - }) - }; + let markdown = cx.new(|cx| Markdown::new(source.clone().into(), None, None, cx)); let cell_id = id.clone(); let editor_subscription = @@ -419,9 +402,8 @@ impl MarkdownCell { image_cache: RetainAllImageCache::new(cx), source, editor, - parsed_markdown: None, - markdown_parsing_task, - editing: start_editing, // Start in edit mode if empty + markdown, + editing: start_editing, selected: false, cell_position: None, languages, @@ -477,18 +459,8 @@ impl MarkdownCell { self.source = source.clone(); let languages = self.languages.clone(); - self.markdown_parsing_task = cx.spawn(async move |this, cx| { - let parsed_markdown = cx - .background_spawn( - async move { parse_markdown(&source, None, Some(languages)).await }, - ) - .await; - - this.update(cx, |cell: &mut MarkdownCell, cx| { - cell.parsed_markdown = Some(parsed_markdown); - cx.notify(); - }) - .log_err(); + self.markdown.update(cx, |markdown, cx| { + markdown.reset(source.into(), cx); }); } @@ -581,42 +553,11 @@ impl Render for MarkdownCell { } // Preview mode - show rendered markdown - let Some(parsed) = self.parsed_markdown.as_ref() else { - // No parsed content yet, show placeholder that can be clicked to edit - let focus_handle = self.editor.focus_handle(cx); - return v_flex() - .size_full() - .children(self.cell_position_spacer(true, window, cx)) - .child( - h_flex() - .w_full() - .pr_6() - .rounded_xs() - .items_start() - .gap(DynamicSpacing::Base08.rems(cx)) - .bg(self.selected_bg_color(window, cx)) - .child(self.gutter(window, cx)) - .child( - div() - .id("markdown-placeholder") - .flex_1() - .p_3() - .italic() - .text_color(cx.theme().colors().text_muted) - .child("Click to edit markdown...") - .cursor_pointer() - .on_click(cx.listener(move |this, _event, window, cx| { - this.editing = true; - window.focus(&this.editor.focus_handle(cx), cx); - cx.notify(); - })), - ), - ) - .children(self.cell_position_spacer(false, window, cx)); - }; - let mut markdown_render_context = - markdown_preview::markdown_renderer::RenderContext::new(None, window, cx); + let style = MarkdownStyle { + base_text_style: window.text_style(), + ..Default::default() + }; v_flex() .size_full() @@ -645,11 +586,7 @@ impl Render for MarkdownCell { window.focus(&this.editor.focus_handle(cx), cx); cx.notify(); })) - .children(parsed.children.iter().map(|child| { - div().relative().child(div().relative().child( - render_markdown_block(child, &mut markdown_render_context), - )) - })), + .child(MarkdownElement::new(self.markdown.clone(), style)), ), ) .children(self.cell_position_spacer(false, window, cx)) diff --git a/crates/repl/src/outputs.rs b/crates/repl/src/outputs.rs index 8a48868ab31a20795b166ab63aae014fa8e14ac0..d1942a67d8ceaafd2ad207397a6677682e2e1e05 100644 --- a/crates/repl/src/outputs.rs +++ b/crates/repl/src/outputs.rs @@ -254,12 +254,20 @@ impl Output { Self::ClearOutputWaitMarker => None, }; + let needs_horizontal_scroll = matches!(self, Self::Table { .. } | Self::Image { .. }); + h_flex() .id("output-content") .w_full() - .overflow_x_scroll() + .when(needs_horizontal_scroll, |el| el.overflow_x_scroll()) .items_start() - .child(div().flex_1().children(content)) + .child( + div() + .when(!needs_horizontal_scroll, |el| { + el.flex_1().w_full().overflow_x_hidden() + }) + .children(content), + ) .children(match self { Self::Plain { content, .. } => { Self::render_output_controls(content.clone(), workspace, window, cx) diff --git a/crates/repl/src/outputs/markdown.rs b/crates/repl/src/outputs/markdown.rs index bd88f4e159f7dbd472ad52a9fa424741400394d7..445932987c7dc27c6ae992739aa9c4fbeb8b87fb 100644 --- a/crates/repl/src/outputs/markdown.rs +++ b/crates/repl/src/outputs/markdown.rs @@ -1,51 +1,25 @@ -use anyhow::Result; -use gpui::{ - App, ClipboardItem, Context, Entity, RetainAllImageCache, Task, Window, div, prelude::*, -}; +use gpui::{App, AppContext, ClipboardItem, Context, Entity, Window, div, prelude::*}; use language::Buffer; -use markdown_preview::{ - markdown_elements::ParsedMarkdown, markdown_parser::parse_markdown, - markdown_renderer::render_markdown_block, -}; -use ui::v_flex; +use markdown::{Markdown, MarkdownElement, MarkdownFont, MarkdownStyle}; use crate::outputs::OutputContent; pub struct MarkdownView { - raw_text: String, - image_cache: Entity, - contents: Option, - parsing_markdown_task: Option>>, + markdown: Entity, } impl MarkdownView { pub fn from(text: String, cx: &mut Context) -> Self { - let parsed = { - let text = text.clone(); - cx.background_spawn(async move { parse_markdown(&text.clone(), None, None).await }) - }; - let task = cx.spawn(async move |markdown_view, cx| { - let content = parsed.await; + let markdown = cx.new(|cx| Markdown::new(text.clone().into(), None, None, cx)); - markdown_view.update(cx, |markdown, cx| { - markdown.parsing_markdown_task.take(); - markdown.contents = Some(content); - cx.notify(); - }) - }); - - Self { - raw_text: text, - image_cache: RetainAllImageCache::new(cx), - contents: None, - parsing_markdown_task: Some(task), - } + Self { markdown } } } impl OutputContent for MarkdownView { - fn clipboard_content(&self, _window: &Window, _cx: &App) -> Option { - Some(ClipboardItem::new_string(self.raw_text.clone())) + fn clipboard_content(&self, _window: &Window, cx: &App) -> Option { + let source = self.markdown.read(cx).source().to_string(); + Some(ClipboardItem::new_string(source)) } fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool { @@ -57,10 +31,10 @@ impl OutputContent for MarkdownView { } fn buffer_content(&mut self, _: &mut Window, cx: &mut App) -> Option> { + let source = self.markdown.read(cx).source().to_string(); let buffer = cx.new(|cx| { - // TODO: Bring in the language registry so we can set the language to markdown - let mut buffer = Buffer::local(self.raw_text.clone(), cx) - .with_language(language::PLAIN_TEXT.clone(), cx); + let mut buffer = + Buffer::local(source.clone(), cx).with_language(language::PLAIN_TEXT.clone(), cx); buffer.set_capability(language::Capability::ReadOnly, cx); buffer }); @@ -70,24 +44,13 @@ impl OutputContent for MarkdownView { impl Render for MarkdownView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - let Some(parsed) = self.contents.as_ref() else { - return div().into_any_element(); - }; - - let mut markdown_render_context = - markdown_preview::markdown_renderer::RenderContext::new(None, window, cx); - - v_flex() - .image_cache(self.image_cache.clone()) - .gap_3() - .py_4() - .children(parsed.children.iter().map(|child| { - div().relative().child( - div() - .relative() - .child(render_markdown_block(child, &mut markdown_render_context)), - ) - })) - .into_any_element() + let style = markdown_style(window, cx); + div() + .w_full() + .child(MarkdownElement::new(self.markdown.clone(), style)) } } + +fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle { + MarkdownStyle::themed(MarkdownFont::Editor, window, cx) +} diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index ef6757df07be310913b83eda2cc9c4a3c8d0ff09..57703c305badcf7f09d09e65e8d712c0ef3bef7a 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -191,7 +191,7 @@ impl EditorBlock { .child( div() .flex_1() - .size_full() + .overflow_x_hidden() .py(text_line_height / 2.) .mr(editor_margins.right) .pr_2()