From 0414908c4a6386b45e5f44a5c8ace34a29e6edb9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 7 Apr 2025 16:43:00 -0400 Subject: [PATCH] markdown: Move `open_url` to the `MarkdownElement` as `on_url_click` (#28269) Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra --- crates/agent/src/active_thread.rs | 322 ++++++++++++------------ crates/agent/src/agent_diff.rs | 8 +- crates/editor/src/code_context_menus.rs | 9 +- crates/editor/src/hover_popover.rs | 20 +- crates/markdown/src/markdown.rs | 34 +-- crates/zed/src/zed.rs | 2 +- 6 files changed, 201 insertions(+), 194 deletions(-) diff --git a/crates/agent/src/active_thread.rs b/crates/agent/src/active_thread.rs index 0e0a9761c5..81b8b03cc0 100644 --- a/crates/agent/src/active_thread.rs +++ b/crates/agent/src/active_thread.rs @@ -76,7 +76,6 @@ impl RenderedMessage { fn from_segments( segments: &[MessageSegment], language_registry: Arc, - workspace: WeakEntity, cx: &mut App, ) -> Self { let mut this = Self { @@ -84,12 +83,12 @@ impl RenderedMessage { segments: Vec::with_capacity(segments.len()), }; for segment in segments { - this.push_segment(segment, workspace.clone(), cx); + this.push_segment(segment, cx); } this } - fn append_thinking(&mut self, text: &String, workspace: WeakEntity, cx: &mut App) { + fn append_thinking(&mut self, text: &String, cx: &mut App) { if let Some(RenderedMessageSegment::Thinking { content, scroll_handle, @@ -101,18 +100,13 @@ impl RenderedMessage { scroll_handle.scroll_to_bottom(); } else { self.segments.push(RenderedMessageSegment::Thinking { - content: render_markdown( - text.into(), - self.language_registry.clone(), - workspace, - cx, - ), + content: render_markdown(text.into(), self.language_registry.clone(), cx), scroll_handle: ScrollHandle::default(), }); } } - fn append_text(&mut self, text: &String, workspace: WeakEntity, cx: &mut App) { + fn append_text(&mut self, text: &String, cx: &mut App) { if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() { markdown.update(cx, |markdown, cx| markdown.append(text, cx)); } else { @@ -120,32 +114,20 @@ impl RenderedMessage { .push(RenderedMessageSegment::Text(render_markdown( SharedString::from(text), self.language_registry.clone(), - workspace, cx, ))); } } - fn push_segment( - &mut self, - segment: &MessageSegment, - workspace: WeakEntity, - cx: &mut App, - ) { + fn push_segment(&mut self, segment: &MessageSegment, cx: &mut App) { let rendered_segment = match segment { MessageSegment::Thinking(text) => RenderedMessageSegment::Thinking { - content: render_markdown( - text.into(), - self.language_registry.clone(), - workspace, - cx, - ), + content: render_markdown(text.into(), self.language_registry.clone(), cx), scroll_handle: ScrollHandle::default(), }, MessageSegment::Text(text) => RenderedMessageSegment::Text(render_markdown( text.into(), self.language_registry.clone(), - workspace, cx, )), }; @@ -164,14 +146,9 @@ enum RenderedMessageSegment { fn render_markdown( text: SharedString, language_registry: Arc, - workspace: WeakEntity, cx: &mut App, ) -> Entity { - cx.new(|cx| { - Markdown::new(text, Some(language_registry), None, cx).open_url(move |text, window, cx| { - open_markdown_link(text, workspace.clone(), window, cx); - }) - }) + cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx)) } fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { @@ -261,14 +238,9 @@ fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { fn render_tool_use_markdown( text: SharedString, language_registry: Arc, - workspace: WeakEntity, cx: &mut App, ) -> Entity { - cx.new(|cx| { - Markdown::new(text, Some(language_registry), None, cx).open_url(move |text, window, cx| { - open_markdown_link(text, workspace.clone(), window, cx); - }) - }) + cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx)) } fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle { @@ -502,12 +474,8 @@ impl ActiveThread { self.messages.push(*id); self.list_state.splice(old_len..old_len, 1); - let rendered_message = RenderedMessage::from_segments( - segments, - self.language_registry.clone(), - self.workspace.clone(), - cx, - ); + let rendered_message = + RenderedMessage::from_segments(segments, self.language_registry.clone(), cx); self.rendered_messages_by_id.insert(*id, rendered_message); } @@ -522,12 +490,8 @@ impl ActiveThread { return; }; self.list_state.splice(index..index + 1, 1); - let rendered_message = RenderedMessage::from_segments( - segments, - self.language_registry.clone(), - self.workspace.clone(), - cx, - ); + let rendered_message = + RenderedMessage::from_segments(segments, self.language_registry.clone(), cx); self.rendered_messages_by_id.insert(*id, rendered_message); } @@ -549,12 +513,7 @@ impl ActiveThread { cx: &mut Context, ) { let rendered = RenderedToolUse { - label: render_tool_use_markdown( - tool_label.into(), - self.language_registry.clone(), - self.workspace.clone(), - cx, - ), + label: render_tool_use_markdown(tool_label.into(), self.language_registry.clone(), cx), input: render_tool_use_markdown( format!( "```json\n{}\n```", @@ -562,15 +521,9 @@ impl ActiveThread { ) .into(), self.language_registry.clone(), - self.workspace.clone(), - cx, - ), - output: render_tool_use_markdown( - tool_output, - self.language_registry.clone(), - self.workspace.clone(), cx, ), + output: render_tool_use_markdown(tool_output, self.language_registry.clone(), cx), }; self.rendered_tool_uses .insert(tool_use_id.clone(), rendered); @@ -613,12 +566,12 @@ impl ActiveThread { } ThreadEvent::StreamedAssistantText(message_id, text) => { if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) { - rendered_message.append_text(text, self.workspace.clone(), cx); + rendered_message.append_text(text, cx); } } ThreadEvent::StreamedAssistantThinking(message_id, text) => { if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) { - rendered_message.append_thinking(text, self.workspace.clone(), cx); + rendered_message.append_thinking(text, cx); } } ThreadEvent::MessageAdded(message_id) => { @@ -1550,10 +1503,18 @@ impl ActiveThread { ) .into_any_element(), RenderedMessageSegment::Text(markdown) => div() - .child(MarkdownElement::new( - markdown.clone(), - default_markdown_style(window, cx), - )) + .child( + MarkdownElement::new( + markdown.clone(), + default_markdown_style(window, cx), + ) + .on_url_click({ + let workspace = self.workspace.clone(); + move |text, window, cx| { + open_markdown_link(text, workspace.clone(), window, cx); + } + }), + ) .into_any_element(), }, ), @@ -1712,10 +1673,23 @@ impl ActiveThread { .h_20() .track_scroll(scroll_handle) .text_ui_sm(cx) - .child(MarkdownElement::new( - markdown.clone(), - default_markdown_style(window, cx), - )) + .child( + MarkdownElement::new( + markdown.clone(), + default_markdown_style(window, cx), + ) + .on_url_click({ + let workspace = self.workspace.clone(); + move |text, window, cx| { + open_markdown_link( + text, + workspace.clone(), + window, + cx, + ); + } + }), + ) .overflow_hidden(), ) .child(gradient_overlay), @@ -1730,10 +1704,18 @@ impl ActiveThread { .rounded_b_lg() .bg(editor_bg) .text_ui_sm(cx) - .child(MarkdownElement::new( - markdown.clone(), - default_markdown_style(window, cx), - )), + .child( + MarkdownElement::new( + markdown.clone(), + default_markdown_style(window, cx), + ) + .on_url_click({ + let workspace = self.workspace.clone(); + move |text, window, cx| { + open_markdown_link(text, workspace.clone(), window, cx); + } + }), + ), ) }), ) @@ -1789,13 +1771,41 @@ impl ActiveThread { let rendered_tool_use = self.rendered_tool_uses.get(&tool_use.id).cloned(); let results_content_container = || v_flex().p_2().gap_0p5(); - let results_content = - v_flex() - .gap_1() - .child( + let results_content = v_flex() + .gap_1() + .child( + results_content_container() + .child( + Label::new("Input") + .size(LabelSize::XSmall) + .color(Color::Muted) + .buffer_font(cx), + ) + .child( + div() + .w_full() + .text_ui_sm(cx) + .children(rendered_tool_use.as_ref().map(|rendered| { + MarkdownElement::new( + rendered.input.clone(), + tool_use_markdown_style(window, cx), + ) + .on_url_click({ + let workspace = self.workspace.clone(); + move |text, window, cx| { + open_markdown_link(text, workspace.clone(), window, cx); + } + }) + })), + ), + ) + .map(|container| match tool_use.status { + ToolUseStatus::Finished(_) => container.child( results_content_container() + .border_t_1() + .border_color(self.tool_card_border_color(cx)) .child( - Label::new("Input") + Label::new("Result") .size(LabelSize::XSmall) .color(Color::Muted) .buffer_font(cx), @@ -1803,95 +1813,87 @@ impl ActiveThread { .child(div().w_full().text_ui_sm(cx).children( rendered_tool_use.as_ref().map(|rendered| { MarkdownElement::new( - rendered.input.clone(), + rendered.output.clone(), tool_use_markdown_style(window, cx), ) + .on_url_click({ + let workspace = self.workspace.clone(); + move |text, window, cx| { + open_markdown_link(text, workspace.clone(), window, cx); + } + }) }), )), - ) - .map(|container| match tool_use.status { - ToolUseStatus::Finished(_) => container.child( - results_content_container() + ), + ToolUseStatus::Running => container.child( + results_content_container().child( + h_flex() + .gap_1() + .pb_1() .border_t_1() .border_color(self.tool_card_border_color(cx)) .child( - Label::new("Result") - .size(LabelSize::XSmall) - .color(Color::Muted) - .buffer_font(cx), + Icon::new(IconName::ArrowCircle) + .size(IconSize::Small) + .color(Color::Accent) + .with_animation( + "arrow-circle", + Animation::new(Duration::from_secs(2)).repeat(), + |icon, delta| { + icon.transform(Transformation::rotate(percentage( + delta, + ))) + }, + ), ) - .child(div().w_full().text_ui_sm(cx).children( - rendered_tool_use.as_ref().map(|rendered| { - MarkdownElement::new( - rendered.output.clone(), - tool_use_markdown_style(window, cx), - ) - }), - )), - ), - ToolUseStatus::Running => container.child( - results_content_container().child( - h_flex() - .gap_1() - .pb_1() - .border_t_1() - .border_color(self.tool_card_border_color(cx)) - .child( - Icon::new(IconName::ArrowCircle) - .size(IconSize::Small) - .color(Color::Accent) - .with_animation( - "arrow-circle", - Animation::new(Duration::from_secs(2)).repeat(), - |icon, delta| { - icon.transform(Transformation::rotate(percentage( - delta, - ))) - }, - ), - ) - .child( - Label::new("Running…") - .size(LabelSize::XSmall) - .color(Color::Muted) - .buffer_font(cx), - ), - ), - ), - ToolUseStatus::Error(_) => { - container.child( - results_content_container() - .border_t_1() - .border_color(self.tool_card_border_color(cx)) - .child( - Label::new("Error") - .size(LabelSize::XSmall) - .color(Color::Muted) - .buffer_font(cx), - ) - .child(div().text_ui_sm(cx).children( - rendered_tool_use.as_ref().map(|rendered| { - MarkdownElement::new( - rendered.output.clone(), - tool_use_markdown_style(window, cx), - ) - }), - )), - ) - } - ToolUseStatus::Pending => container, - ToolUseStatus::NeedsConfirmation => container.child( - results_content_container() - .border_t_1() - .border_color(self.tool_card_border_color(cx)) .child( - Label::new("Asking Permission") - .size(LabelSize::Small) + Label::new("Running…") + .size(LabelSize::XSmall) .color(Color::Muted) .buffer_font(cx), ), ), - }); + ), + ToolUseStatus::Error(_) => container.child( + results_content_container() + .border_t_1() + .border_color(self.tool_card_border_color(cx)) + .child( + Label::new("Error") + .size(LabelSize::XSmall) + .color(Color::Muted) + .buffer_font(cx), + ) + .child( + div() + .text_ui_sm(cx) + .children(rendered_tool_use.as_ref().map(|rendered| { + MarkdownElement::new( + rendered.output.clone(), + tool_use_markdown_style(window, cx), + ) + .on_url_click({ + let workspace = self.workspace.clone(); + move |text, window, cx| { + open_markdown_link(text, workspace.clone(), window, cx); + } + }) + })), + ), + ), + ToolUseStatus::Pending => container, + ToolUseStatus::NeedsConfirmation => container.child( + results_content_container() + .border_t_1() + .border_color(self.tool_card_border_color(cx)) + .child( + Label::new("Asking Permission") + .size(LabelSize::Small) + .color(Color::Muted) + .buffer_font(cx), + ), + ), + }); let gradient_overlay = |color: Hsla| { div() @@ -1939,7 +1941,9 @@ impl ActiveThread { ) .child( h_flex().pr_8().text_ui_sm(cx).children( - rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx))) + rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| { + open_markdown_link(text, workspace.clone(), window, cx); + }})) ), ), ) @@ -2027,7 +2031,9 @@ impl ActiveThread { ) .child( h_flex().pr_8().text_ui_sm(cx).children( - rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx))) + rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| { + open_markdown_link(text, workspace.clone(), window, cx); + }})) ), ), ) diff --git a/crates/agent/src/agent_diff.rs b/crates/agent/src/agent_diff.rs index 8a7ac4751c..3b696a3e19 100644 --- a/crates/agent/src/agent_diff.rs +++ b/crates/agent/src/agent_diff.rs @@ -792,15 +792,11 @@ impl editor::Addon for AgentDiffAddon { pub struct AgentDiffToolbar { agent_diff: Option>, - _workspace: WeakEntity, } impl AgentDiffToolbar { - pub fn new(workspace: &Workspace, _: &mut Context) -> Self { - Self { - agent_diff: None, - _workspace: workspace.weak_handle(), - } + pub fn new() -> Self { + Self { agent_diff: None } } fn agent_diff(&self, _: &App) -> Option> { diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 7eb52eb806..6e836abcc6 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -624,16 +624,15 @@ impl CompletionsMenu { .map(|l| l.name().to_proto()); Markdown::new(SharedString::default(), languages, language, cx) .copy_code_block_buttons(false) - .open_url(open_markdown_url) }) }); markdown.update(cx, |markdown, cx| { markdown.reset(parsed.clone(), cx); }); - div().child(MarkdownElement::new( - markdown.clone(), - hover_markdown_style(window, cx), - )) + div().child( + MarkdownElement::new(markdown.clone(), hover_markdown_style(window, cx)) + .on_url_click(open_markdown_url), + ) } CompletionDocumentation::MultiLineMarkdown(_) => return None, CompletionDocumentation::SingleLine(_) => return None, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index c9279f05f3..00391d865f 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -336,7 +336,7 @@ fn show_hover( } }; - Markdown::new_text(SharedString::new(text), cx).open_url(open_markdown_url) + Markdown::new_text(SharedString::new(text), cx) }) .ok(); @@ -547,7 +547,6 @@ async fn parse_blocks( cx, ) .copy_code_block_buttons(false) - .open_url(open_markdown_url) }) .ok(); @@ -783,10 +782,13 @@ impl InfoPopover { .max_h(max_size.height) .p_2() .track_scroll(&self.scroll_handle) - .child(MarkdownElement::new( - markdown.clone(), - hover_markdown_style(window, cx), - )), + .child( + MarkdownElement::new( + markdown.clone(), + hover_markdown_style(window, cx), + ) + .on_url_click(open_markdown_url), + ), ) .child(self.render_vertical_scrollbar(cx)); } @@ -881,8 +883,10 @@ impl DiagnosticPopover { ..Default::default() }; - markdown_div = - markdown_div.child(MarkdownElement::new(markdown.clone(), markdown_style)); + markdown_div = markdown_div.child( + MarkdownElement::new(markdown.clone(), markdown_style) + .on_url_click(open_markdown_url), + ); } if let Some(background_color) = &self.background_color { diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 7247f3edc0..081ccea9fb 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -80,7 +80,6 @@ pub struct Markdown { focus_handle: FocusHandle, language_registry: Option>, fallback_code_block_language: Option, - open_url: Option>, options: Options, copied_code_blocks: HashSet, } @@ -116,23 +115,12 @@ impl Markdown { parse_links_only: false, copy_code_block_buttons: true, }, - open_url: None, copied_code_blocks: HashSet::new(), }; this.parse(cx); this } - pub fn open_url( - self, - open_url: impl Fn(SharedString, &mut Window, &mut App) + 'static, - ) -> Self { - Self { - open_url: Some(Box::new(open_url)), - ..self - } - } - pub fn new_text(source: SharedString, cx: &mut Context) -> Self { let focus_handle = cx.focus_handle(); let mut this = Self { @@ -150,7 +138,6 @@ impl Markdown { parse_links_only: true, copy_code_block_buttons: true, }, - open_url: None, copied_code_blocks: HashSet::new(), }; this.parse(cx); @@ -328,11 +315,24 @@ impl ParsedMarkdown { pub struct MarkdownElement { markdown: Entity, style: MarkdownStyle, + on_url_click: Option>, } impl MarkdownElement { pub fn new(markdown: Entity, style: MarkdownStyle) -> Self { - Self { markdown, style } + Self { + markdown, + style, + on_url_click: None, + } + } + + pub fn on_url_click( + mut self, + handler: impl Fn(SharedString, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_url_click = Some(Box::new(handler)); + self } fn paint_selection( @@ -404,7 +404,7 @@ impl MarkdownElement { } fn paint_mouse_listeners( - &self, + &mut self, hitbox: &Hitbox, rendered_text: &RenderedText, window: &mut Window, @@ -422,6 +422,8 @@ impl MarkdownElement { window.set_cursor_style(CursorStyle::IBeam, Some(hitbox)); } + let on_open_url = self.on_url_click.take(); + self.on_mouse_event(window, cx, { let rendered_text = rendered_text.clone(); let hitbox = hitbox.clone(); @@ -493,7 +495,7 @@ impl MarkdownElement { if phase.bubble() { if let Some(pressed_link) = markdown.pressed_link.take() { if Some(&pressed_link) == rendered_text.link_for_position(event.position) { - if let Some(open_url) = markdown.open_url.as_mut() { + if let Some(open_url) = on_open_url.as_ref() { open_url(pressed_link.destination_url, window, cx); } else { cx.open_url(&pressed_link.destination_url); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 4c3a24100d..a6c230d829 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -938,7 +938,7 @@ fn initialize_pane( toolbar.add_item(migration_banner, window, cx); let project_diff_toolbar = cx.new(|cx| ProjectDiffToolbar::new(workspace, cx)); toolbar.add_item(project_diff_toolbar, window, cx); - let agent_diff_toolbar = cx.new(|cx| AgentDiffToolbar::new(workspace, cx)); + let agent_diff_toolbar = cx.new(|_cx| AgentDiffToolbar::new()); toolbar.add_item(agent_diff_toolbar, window, cx); }) });