Cargo.lock 🔗
@@ -13764,7 +13764,7 @@ dependencies = [
"language",
"languages",
"log",
- "markdown_preview",
+ "markdown",
"menu",
"multi_buffer",
"nbformat",
Kyle Kelley created
Brought the Markdown output up to date with how Markdown is used in the
Agent panel. This fixed an issue with outputs that were too large for
the execution view as well as made sure that markdown would wrap.
<img width="3222" height="2334" alt="image"
src="https://github.com/user-attachments/assets/c65efa53-b792-4529-909a-9117053e30be"
/>
Release Notes:
- N/A
Cargo.lock | 2
crates/agent_ui/src/acp/thread_view.rs | 245 +++++++--------------------
crates/markdown/Cargo.toml | 1
crates/markdown/src/markdown.rs | 131 ++++++++++++++
crates/repl/Cargo.toml | 2
crates/repl/src/notebook/cell.rs | 87 +--------
crates/repl/src/outputs.rs | 12 +
crates/repl/src/outputs/markdown.rs | 75 ++------
crates/repl/src/session.rs | 2
9 files changed, 242 insertions(+), 315 deletions(-)
@@ -13764,7 +13764,7 @@ dependencies = [
"language",
"languages",
"log",
- "markdown_preview",
+ "markdown",
"menu",
"multi_buffer",
"nbformat",
@@ -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 {
@@ -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
@@ -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,
@@ -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
@@ -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<RetainAllImageCache>,
source: String,
editor: Entity<Editor>,
- parsed_markdown: Option<markdown_preview::markdown_elements::ParsedMarkdown>,
- markdown_parsing_task: Task<()>,
+ markdown: Entity<Markdown>,
editing: bool,
selected: bool,
cell_position: Option<CellPosition>,
@@ -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))
@@ -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)
@@ -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<RetainAllImageCache>,
- contents: Option<ParsedMarkdown>,
- parsing_markdown_task: Option<Task<Result<()>>>,
+ markdown: Entity<Markdown>,
}
impl MarkdownView {
pub fn from(text: String, cx: &mut Context<Self>) -> 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<ClipboardItem> {
- Some(ClipboardItem::new_string(self.raw_text.clone()))
+ fn clipboard_content(&self, _window: &Window, cx: &App) -> Option<ClipboardItem> {
+ 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<Entity<Buffer>> {
+ 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<Self>) -> 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)
+}
@@ -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()