From b6ee367ee0e62a111164dd6e09c534e7e48bad5a Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 7 Apr 2025 15:03:24 -0400 Subject: [PATCH] markdown: Don't retain `MarkdownStyle` in favor of using `MarkdownElement` directly (#28255) This PR removes the retained `MarkdownStyle` on the `Markdown` entity in favor of using the `MarkdownElement` directly and passing the `MarkdownStyle` to it. This makes it so switching themes will be reflected live in the code block styles. Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra Co-authored-by: Agus Zubiaga --- crates/agent/src/active_thread.rs | 287 +++++++++--------- crates/editor/src/code_context_menus.rs | 19 +- crates/editor/src/element.rs | 10 +- crates/editor/src/hover_popover.rs | 78 ++--- crates/git_ui/src/blame_ui.rs | 6 +- crates/git_ui/src/commit_tooltip.rs | 19 +- crates/git_ui/src/git_panel.rs | 4 +- crates/markdown/examples/markdown.rs | 103 +++---- crates/markdown/examples/markdown_as_child.rs | 113 +++---- crates/markdown/src/markdown.rs | 20 +- crates/recent_projects/src/ssh_connections.rs | 35 ++- crates/ui_prompt/src/ui_prompt.rs | 50 +-- 12 files changed, 370 insertions(+), 374 deletions(-) diff --git a/crates/agent/src/active_thread.rs b/crates/agent/src/active_thread.rs index f463a23b41..0e0a9761c5 100644 --- a/crates/agent/src/active_thread.rs +++ b/crates/agent/src/active_thread.rs @@ -22,7 +22,7 @@ use gpui::{ }; use language::{Buffer, LanguageRegistry}; use language_model::{ConfiguredModel, LanguageModelRegistry, LanguageModelToolUseId, Role}; -use markdown::{Markdown, MarkdownStyle}; +use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use project::ProjectItem as _; use settings::{Settings as _, update_settings_file}; use std::rc::Rc; @@ -77,7 +77,6 @@ impl RenderedMessage { segments: &[MessageSegment], language_registry: Arc, workspace: WeakEntity, - window: &Window, cx: &mut App, ) -> Self { let mut this = Self { @@ -85,18 +84,12 @@ impl RenderedMessage { segments: Vec::with_capacity(segments.len()), }; for segment in segments { - this.push_segment(segment, workspace.clone(), window, cx); + this.push_segment(segment, workspace.clone(), cx); } this } - fn append_thinking( - &mut self, - text: &String, - workspace: WeakEntity, - window: &Window, - cx: &mut App, - ) { + fn append_thinking(&mut self, text: &String, workspace: WeakEntity, cx: &mut App) { if let Some(RenderedMessageSegment::Thinking { content, scroll_handle, @@ -112,7 +105,6 @@ impl RenderedMessage { text.into(), self.language_registry.clone(), workspace, - window, cx, ), scroll_handle: ScrollHandle::default(), @@ -120,13 +112,7 @@ impl RenderedMessage { } } - fn append_text( - &mut self, - text: &String, - workspace: WeakEntity, - window: &Window, - cx: &mut App, - ) { + fn append_text(&mut self, text: &String, workspace: WeakEntity, cx: &mut App) { if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() { markdown.update(cx, |markdown, cx| markdown.append(text, cx)); } else { @@ -135,7 +121,6 @@ impl RenderedMessage { SharedString::from(text), self.language_registry.clone(), workspace, - window, cx, ))); } @@ -145,7 +130,6 @@ impl RenderedMessage { &mut self, segment: &MessageSegment, workspace: WeakEntity, - window: &Window, cx: &mut App, ) { let rendered_segment = match segment { @@ -154,7 +138,6 @@ impl RenderedMessage { text.into(), self.language_registry.clone(), workspace, - window, cx, ), scroll_handle: ScrollHandle::default(), @@ -163,7 +146,6 @@ impl RenderedMessage { text.into(), self.language_registry.clone(), workspace, - window, cx, )), }; @@ -183,9 +165,16 @@ fn render_markdown( text: SharedString, language_registry: Arc, workspace: WeakEntity, - window: &Window, 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); + }) + }) +} + +fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); let ui_font_size = TextSize::Default.rems(cx); @@ -201,7 +190,7 @@ fn render_markdown( ..Default::default() }); - let markdown_style = MarkdownStyle { + MarkdownStyle { base_text_style: text_style, syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().players().local().selection, @@ -266,24 +255,23 @@ fn render_markdown( } })), ..Default::default() - }; - - cx.new(|cx| { - Markdown::new(text, markdown_style, Some(language_registry), None, cx).open_url( - move |text, window, cx| { - open_markdown_link(text, workspace.clone(), window, cx); - }, - ) - }) + } } fn render_tool_use_markdown( text: SharedString, language_registry: Arc, workspace: WeakEntity, - window: &Window, 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); + }) + }) +} + +fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle { let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); let ui_font_size = TextSize::Default.rems(cx); @@ -299,7 +287,7 @@ fn render_tool_use_markdown( ..Default::default() }); - let markdown_style = MarkdownStyle { + MarkdownStyle { base_text_style: text_style, syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().players().local().selection, @@ -334,15 +322,7 @@ fn render_tool_use_markdown( ..Default::default() }, ..Default::default() - }; - - cx.new(|cx| { - Markdown::new(text, markdown_style, Some(language_registry), None, cx).open_url( - move |text, window, cx| { - open_markdown_link(text, workspace.clone(), window, cx); - }, - ) - }) + } } fn open_markdown_link( @@ -473,7 +453,6 @@ impl ActiveThread { tool_use.ui_text.clone(), &tool_use.input, tool_use.status.text(), - window, cx, ); } @@ -516,7 +495,7 @@ impl ActiveThread { &mut self, id: &MessageId, segments: &[MessageSegment], - window: &mut Window, + _window: &mut Window, cx: &mut Context, ) { let old_len = self.messages.len(); @@ -527,7 +506,6 @@ impl ActiveThread { segments, self.language_registry.clone(), self.workspace.clone(), - window, cx, ); self.rendered_messages_by_id.insert(*id, rendered_message); @@ -537,7 +515,7 @@ impl ActiveThread { &mut self, id: &MessageId, segments: &[MessageSegment], - window: &mut Window, + _window: &mut Window, cx: &mut Context, ) { let Some(index) = self.messages.iter().position(|message_id| message_id == id) else { @@ -548,7 +526,6 @@ impl ActiveThread { segments, self.language_registry.clone(), self.workspace.clone(), - window, cx, ); self.rendered_messages_by_id.insert(*id, rendered_message); @@ -569,7 +546,6 @@ impl ActiveThread { tool_label: impl Into, tool_input: &serde_json::Value, tool_output: SharedString, - window: &mut Window, cx: &mut Context, ) { let rendered = RenderedToolUse { @@ -577,7 +553,6 @@ impl ActiveThread { tool_label.into(), self.language_registry.clone(), self.workspace.clone(), - window, cx, ), input: render_tool_use_markdown( @@ -588,14 +563,12 @@ impl ActiveThread { .into(), self.language_registry.clone(), self.workspace.clone(), - window, cx, ), output: render_tool_use_markdown( tool_output, self.language_registry.clone(), self.workspace.clone(), - window, cx, ), }; @@ -640,12 +613,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(), window, cx); + rendered_message.append_text(text, self.workspace.clone(), 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(), window, cx); + rendered_message.append_thinking(text, self.workspace.clone(), cx); } } ThreadEvent::MessageAdded(message_id) => { @@ -690,7 +663,6 @@ impl ActiveThread { tool_use.ui_text.clone(), &tool_use.input, "".into(), - window, cx, ); } @@ -711,7 +683,6 @@ impl ActiveThread { .tool_result(&tool_use.id) .map(|result| result.content.clone().into()) .unwrap_or("".into()), - window, cx, ); } @@ -1204,6 +1175,7 @@ impl ActiveThread { message_id, rendered_message, has_tool_uses, + window, cx, )) .into_any() @@ -1370,7 +1342,7 @@ impl ActiveThread { div().children( tool_uses .into_iter() - .map(|tool_use| self.render_tool_use(tool_use, cx)), + .map(|tool_use| self.render_tool_use(tool_use, window, cx)), ), ) }), @@ -1540,6 +1512,7 @@ impl ActiveThread { message_id: MessageId, rendered_message: &RenderedMessage, has_tool_uses: bool, + window: &Window, cx: &Context, ) -> impl IntoElement { let is_last_message = self.messages.last() == Some(&message_id); @@ -1572,12 +1545,16 @@ impl ActiveThread { content.clone(), &scroll_handle, Some(index) == pending_thinking_segment_index, + window, cx, ) .into_any_element(), - RenderedMessageSegment::Text(markdown) => { - div().child(markdown.clone()).into_any_element() - } + RenderedMessageSegment::Text(markdown) => div() + .child(MarkdownElement::new( + markdown.clone(), + default_markdown_style(window, cx), + )) + .into_any_element(), }, ), ) @@ -1601,6 +1578,7 @@ impl ActiveThread { markdown: Entity, scroll_handle: &ScrollHandle, pending: bool, + window: &Window, cx: &Context, ) -> impl IntoElement { let is_open = self @@ -1734,7 +1712,10 @@ impl ActiveThread { .h_20() .track_scroll(scroll_handle) .text_ui_sm(cx) - .child(markdown.clone()) + .child(MarkdownElement::new( + markdown.clone(), + default_markdown_style(window, cx), + )) .overflow_hidden(), ) .child(gradient_overlay), @@ -1749,7 +1730,10 @@ impl ActiveThread { .rounded_b_lg() .bg(editor_bg) .text_ui_sm(cx) - .child(markdown.clone()), + .child(MarkdownElement::new( + markdown.clone(), + default_markdown_style(window, cx), + )), ) }), ) @@ -1758,6 +1742,7 @@ impl ActiveThread { fn render_tool_use( &self, tool_use: ToolUse, + window: &mut Window, cx: &mut Context, ) -> impl IntoElement + use<> { let is_open = self @@ -1804,103 +1789,109 @@ 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( - 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| rendered.input.clone()), - ), - ), - ) - .map(|container| match tool_use.status { - ToolUseStatus::Finished(_) => container.child( + let results_content = + v_flex() + .gap_1() + .child( results_content_container() - .border_t_1() - .border_color(self.tool_card_border_color(cx)) .child( - Label::new("Result") + 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| rendered.output.clone()), - ), - ), - ), - ToolUseStatus::Running => container.child( - results_content_container().child( - h_flex() - .gap_1() - .pb_1() + .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), + ) + }), + )), + ) + .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( - 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…") + Label::new("Result") .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.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) + .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| rendered.output.clone()), - ), - ), - ), - 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() @@ -1948,7 +1939,7 @@ impl ActiveThread { ) .child( h_flex().pr_8().text_ui_sm(cx).children( - rendered_tool_use.map(|rendered| rendered.label) + rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx))) ), ), ) @@ -2036,7 +2027,7 @@ impl ActiveThread { ) .child( h_flex().pr_8().text_ui_sm(cx).children( - rendered_tool_use.map(|rendered| rendered.label) + rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx))) ), ), ) diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index fbbfc75af7..7eb52eb806 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -7,7 +7,7 @@ use gpui::{ }; use language::Buffer; use language::CodeLabel; -use markdown::Markdown; +use markdown::{Markdown, MarkdownElement}; use multi_buffer::{Anchor, ExcerptId}; use ordered_float::OrderedFloat; use project::CompletionSource; @@ -622,21 +622,18 @@ impl CompletionsMenu { let language = editor .language_at(self.initial_position, cx) .map(|l| l.name().to_proto()); - Markdown::new( - SharedString::default(), - hover_markdown_style(window, cx), - languages, - language, - cx, - ) - .copy_code_block_buttons(false) - .open_url(open_markdown_url) + 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(markdown.clone()) + div().child(MarkdownElement::new( + markdown.clone(), + hover_markdown_style(window, cx), + )) } CompletionDocumentation::MultiLineMarkdown(_) => return None, CompletionDocumentation::SingleLine(_) => return None, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 30dc317c64..98b40540f9 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3912,9 +3912,13 @@ impl EditorElement { ); let hover_popovers = self.editor.update(cx, |editor, cx| { - editor - .hover_state - .render(snapshot, visible_display_row_range.clone(), max_size, cx) + editor.hover_state.render( + snapshot, + visible_display_row_range.clone(), + max_size, + window, + cx, + ) }); let Some((position, hover_popovers)) = hover_popovers else { return; diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index f5126a6c3e..c9279f05f3 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -14,7 +14,7 @@ use gpui::{ use itertools::Itertools; use language::{DiagnosticEntry, Language, LanguageRegistry}; use lsp::DiagnosticSeverity; -use markdown::{Markdown, MarkdownStyle}; +use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use multi_buffer::{MultiOrSingleBufferOffsetRange, ToOffset}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart}; use settings::Settings; @@ -310,7 +310,7 @@ fn show_hover( let mut background_color: Option = None; let parsed_content = cx - .new_window_entity(|window, cx| { + .new_window_entity(|_window, cx| { let status_colors = cx.theme().status(); match local_diagnostic.diagnostic.severity { @@ -335,32 +335,8 @@ fn show_hover( border_color = Some(status_colors.ignored_border); } }; - let settings = ThemeSettings::get_global(cx); - let mut base_text_style = window.text_style(); - base_text_style.refine(&TextStyleRefinement { - font_family: Some(settings.ui_font.family.clone()), - font_fallbacks: settings.ui_font.fallbacks.clone(), - font_size: Some(settings.ui_font_size(cx).into()), - color: Some(cx.theme().colors().editor_foreground), - background_color: Some(gpui::transparent_black()), - ..Default::default() - }); - let markdown_style = MarkdownStyle { - base_text_style, - selection_background_color: { cx.theme().players().local().selection }, - link: TextStyleRefinement { - underline: Some(gpui::UnderlineStyle { - thickness: px(1.), - color: Some(cx.theme().colors().editor_foreground), - wavy: false, - }), - ..Default::default() - }, - ..Default::default() - }; - Markdown::new_text(SharedString::new(text), markdown_style.clone(), cx) - .open_url(open_markdown_url) + Markdown::new_text(SharedString::new(text), cx).open_url(open_markdown_url) }) .ok(); @@ -563,10 +539,9 @@ async fn parse_blocks( .join("\n\n"); let rendered_block = cx - .new_window_entity(|window, cx| { + .new_window_entity(|_window, cx| { Markdown::new( combined_text.into(), - hover_markdown_style(window, cx), Some(language_registry.clone()), fallback_language_name, cx, @@ -704,6 +679,7 @@ impl HoverState { snapshot: &EditorSnapshot, visible_rows: Range, max_size: Size, + window: &mut Window, cx: &mut Context, ) -> Option<(DisplayPoint, Vec)> { // If there is a diagnostic, position the popovers based on that. @@ -738,10 +714,10 @@ impl HoverState { let mut elements = Vec::new(); if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() { - elements.push(diagnostic_popover.render(max_size, cx)); + elements.push(diagnostic_popover.render(max_size, window, cx)); } for info_popover in &mut self.info_popovers { - elements.push(info_popover.render(max_size, cx)); + elements.push(info_popover.render(max_size, window, cx)); } Some((point, elements)) @@ -781,6 +757,7 @@ impl InfoPopover { pub(crate) fn render( &mut self, max_size: Size, + window: &mut Window, cx: &mut Context, ) -> AnyElement { let keyboard_grace = Rc::clone(&self.keyboard_grace); @@ -806,7 +783,10 @@ impl InfoPopover { .max_h(max_size.height) .p_2() .track_scroll(&self.scroll_handle) - .child(markdown.clone()), + .child(MarkdownElement::new( + markdown.clone(), + hover_markdown_style(window, cx), + )), ) .child(self.render_vertical_scrollbar(cx)); } @@ -868,11 +848,41 @@ pub struct DiagnosticPopover { } impl DiagnosticPopover { - pub fn render(&self, max_size: Size, cx: &mut Context) -> AnyElement { + pub fn render( + &self, + max_size: Size, + window: &mut Window, + cx: &mut Context, + ) -> AnyElement { let keyboard_grace = Rc::clone(&self.keyboard_grace); let mut markdown_div = div().py_1().px_2(); if let Some(markdown) = &self.parsed_content { - markdown_div = markdown_div.child(markdown.clone()); + let settings = ThemeSettings::get_global(cx); + let mut base_text_style = window.text_style(); + base_text_style.refine(&TextStyleRefinement { + font_family: Some(settings.ui_font.family.clone()), + font_fallbacks: settings.ui_font.fallbacks.clone(), + font_size: Some(settings.ui_font_size(cx).into()), + color: Some(cx.theme().colors().editor_foreground), + background_color: Some(gpui::transparent_black()), + ..Default::default() + }); + let markdown_style = MarkdownStyle { + base_text_style, + selection_background_color: { cx.theme().players().local().selection }, + link: TextStyleRefinement { + underline: Some(gpui::UnderlineStyle { + thickness: px(1.), + color: Some(cx.theme().colors().editor_foreground), + wavy: false, + }), + ..Default::default() + }, + ..Default::default() + }; + + markdown_div = + markdown_div.child(MarkdownElement::new(markdown.clone(), markdown_style)); } if let Some(background_color) = &self.background_color { diff --git a/crates/git_ui/src/blame_ui.rs b/crates/git_ui/src/blame_ui.rs index ae608cfe1a..a86e4cab8c 100644 --- a/crates/git_ui/src/blame_ui.rs +++ b/crates/git_ui/src/blame_ui.rs @@ -95,14 +95,13 @@ impl BlameRenderer for GitBlameRenderer { ) } }) - .hoverable_tooltip(move |window, cx| { + .hoverable_tooltip(move |_window, cx| { cx.new(|cx| { CommitTooltip::blame_entry( &blame_entry, details.clone(), repository.clone(), workspace.clone(), - window, cx, ) }) @@ -145,14 +144,13 @@ impl BlameRenderer for GitBlameRenderer { .child(Icon::new(IconName::FileGit).color(Color::Hint)) .child(text) .gap_2() - .hoverable_tooltip(move |window, cx| { + .hoverable_tooltip(move |_window, cx| { let tooltip = cx.new(|cx| { CommitTooltip::blame_entry( &blame_entry, details.clone(), repository.clone(), workspace.clone(), - window, cx, ) }); diff --git a/crates/git_ui/src/commit_tooltip.rs b/crates/git_ui/src/commit_tooltip.rs index 45871a020e..a01c6bf1a9 100644 --- a/crates/git_ui/src/commit_tooltip.rs +++ b/crates/git_ui/src/commit_tooltip.rs @@ -8,7 +8,7 @@ use gpui::{ App, Asset, ClipboardItem, Element, Entity, MouseButton, ParentElement, Render, ScrollHandle, StatefulInteractiveElement, WeakEntity, prelude::*, }; -use markdown::Markdown; +use markdown::{Markdown, MarkdownElement}; use project::git_store::Repository; use settings::Settings; use std::hash::Hash; @@ -118,7 +118,6 @@ impl CommitTooltip { details: Option, repository: Entity, workspace: WeakEntity, - window: &mut Window, cx: &mut Context, ) -> Self { let commit_time = blame @@ -140,7 +139,6 @@ impl CommitTooltip { }, repository, workspace, - window, cx, ) } @@ -149,13 +147,8 @@ impl CommitTooltip { commit: CommitDetails, repository: Entity, workspace: WeakEntity, - window: &mut Window, cx: &mut Context, ) -> Self { - let mut style = hover_markdown_style(window, cx); - if let Some(code_block) = &style.code_block.text { - style.base_text_style.refine(code_block); - } let markdown = cx.new(|cx| { Markdown::new( commit @@ -163,7 +156,6 @@ impl CommitTooltip { .as_ref() .map(|message| message.message.clone()) .unwrap_or_default(), - style, None, None, cx, @@ -199,12 +191,19 @@ impl Render for CommitTooltip { OffsetDateTime::now_utc(), time_format::TimestampFormat::MediumAbsolute, ); + let markdown_style = { + let mut style = hover_markdown_style(window, cx); + if let Some(code_block) = &style.code_block.text { + style.base_text_style.refine(code_block); + } + style + }; let message = self .commit .message .as_ref() - .map(|_| self.markdown.clone().into_any_element()) + .map(|_| MarkdownElement::new(self.markdown.clone(), markdown_style).into_any()) .unwrap_or("".into_any()); let pull_request = self diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 8bc727cb19..1abd3e7e44 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -3927,9 +3927,9 @@ impl GitPanelMessageTooltip { }), }; - this.update_in(cx, |this: &mut GitPanelMessageTooltip, window, cx| { + this.update(cx, |this: &mut GitPanelMessageTooltip, cx| { this.commit_tooltip = Some(cx.new(move |cx| { - CommitTooltip::new(commit_details, repository, workspace, window, cx) + CommitTooltip::new(commit_details, repository, workspace, cx) })); cx.notify(); }) diff --git a/crates/markdown/examples/markdown.rs b/crates/markdown/examples/markdown.rs index a6014705c3..d3e0fdd326 100644 --- a/crates/markdown/examples/markdown.rs +++ b/crates/markdown/examples/markdown.rs @@ -1,7 +1,7 @@ use assets::Assets; use gpui::{Application, Entity, KeyBinding, StyleRefinement, WindowOptions, prelude::*, rgb}; use language::{LanguageRegistry, language_settings::AllLanguageSettings}; -use markdown::{Markdown, MarkdownStyle}; +use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use node_runtime::NodeRuntime; use settings::SettingsStore; use std::sync::Arc; @@ -47,54 +47,7 @@ pub fn main() { cx.activate(true); cx.open_window(WindowOptions::default(), |_, cx| { - cx.new(|cx| { - let markdown_style = MarkdownStyle { - base_text_style: gpui::TextStyle { - font_family: "Zed Plex Sans".into(), - color: cx.theme().colors().terminal_ansi_black, - ..Default::default() - }, - code_block: StyleRefinement::default() - .font_family("Zed Plex Mono") - .m(rems(1.)) - .bg(rgb(0xAAAAAAA)), - inline_code: gpui::TextStyleRefinement { - font_family: Some("Zed Mono".into()), - color: Some(cx.theme().colors().editor_foreground), - background_color: Some(cx.theme().colors().editor_background), - ..Default::default() - }, - rule_color: Color::Muted.color(cx), - block_quote_border_color: Color::Muted.color(cx), - block_quote: gpui::TextStyleRefinement { - color: Some(Color::Muted.color(cx)), - ..Default::default() - }, - link: gpui::TextStyleRefinement { - color: Some(Color::Accent.color(cx)), - underline: Some(gpui::UnderlineStyle { - thickness: px(1.), - color: Some(Color::Accent.color(cx)), - wavy: false, - }), - ..Default::default() - }, - syntax: cx.theme().syntax().clone(), - selection_background_color: { - let mut selection = cx.theme().players().local().selection; - selection.fade_out(0.7); - selection - }, - ..Default::default() - }; - - MarkdownExample::new( - MARKDOWN_EXAMPLE.into(), - markdown_style, - language_registry, - cx, - ) - }) + cx.new(|cx| MarkdownExample::new(MARKDOWN_EXAMPLE.into(), language_registry, cx)) }) .unwrap(); }); @@ -105,16 +58,10 @@ struct MarkdownExample { } impl MarkdownExample { - pub fn new( - text: SharedString, - style: MarkdownStyle, - language_registry: Arc, - cx: &mut App, - ) -> Self { + pub fn new(text: SharedString, language_registry: Arc, cx: &mut App) -> Self { let markdown = cx.new(|cx| { Markdown::new( text, - style, Some(language_registry), Some("TypeScript".to_string()), cx, @@ -125,7 +72,47 @@ impl MarkdownExample { } impl Render for MarkdownExample { - fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let markdown_style = MarkdownStyle { + base_text_style: gpui::TextStyle { + font_family: "Zed Plex Sans".into(), + color: cx.theme().colors().terminal_ansi_black, + ..Default::default() + }, + code_block: StyleRefinement::default() + .font_family("Zed Plex Mono") + .m(rems(1.)) + .bg(rgb(0xAAAAAAA)), + inline_code: gpui::TextStyleRefinement { + font_family: Some("Zed Mono".into()), + color: Some(cx.theme().colors().editor_foreground), + background_color: Some(cx.theme().colors().editor_background), + ..Default::default() + }, + rule_color: Color::Muted.color(cx), + block_quote_border_color: Color::Muted.color(cx), + block_quote: gpui::TextStyleRefinement { + color: Some(Color::Muted.color(cx)), + ..Default::default() + }, + link: gpui::TextStyleRefinement { + color: Some(Color::Accent.color(cx)), + underline: Some(gpui::UnderlineStyle { + thickness: px(1.), + color: Some(Color::Accent.color(cx)), + wavy: false, + }), + ..Default::default() + }, + syntax: cx.theme().syntax().clone(), + selection_background_color: { + let mut selection = cx.theme().players().local().selection; + selection.fade_out(0.7); + selection + }, + ..Default::default() + }; + div() .id("markdown-example") .debug_selector(|| "foo".into()) @@ -134,6 +121,6 @@ impl Render for MarkdownExample { .size_full() .p_4() .overflow_y_scroll() - .child(self.markdown.clone()) + .child(MarkdownElement::new(self.markdown.clone(), markdown_style)) } } diff --git a/crates/markdown/examples/markdown_as_child.rs b/crates/markdown/examples/markdown_as_child.rs index b30f2e2130..62a35629b1 100644 --- a/crates/markdown/examples/markdown_as_child.rs +++ b/crates/markdown/examples/markdown_as_child.rs @@ -1,7 +1,7 @@ use assets::Assets; use gpui::{Application, Entity, KeyBinding, Length, StyleRefinement, WindowOptions, rgb}; use language::{LanguageRegistry, language_settings::AllLanguageSettings}; -use markdown::{Markdown, MarkdownStyle}; +use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use node_runtime::NodeRuntime; use settings::SettingsStore; use std::sync::Arc; @@ -37,58 +37,7 @@ pub fn main() { cx.activate(true); let _ = cx.open_window(WindowOptions::default(), |_, cx| { cx.new(|cx| { - let markdown_style = MarkdownStyle { - base_text_style: gpui::TextStyle { - font_family: "Zed Mono".into(), - color: cx.theme().colors().text, - ..Default::default() - }, - code_block: StyleRefinement { - text: Some(gpui::TextStyleRefinement { - font_family: Some("Zed Mono".into()), - background_color: Some(cx.theme().colors().editor_background), - ..Default::default() - }), - margin: gpui::EdgesRefinement { - top: Some(Length::Definite(rems(4.).into())), - left: Some(Length::Definite(rems(4.).into())), - right: Some(Length::Definite(rems(4.).into())), - bottom: Some(Length::Definite(rems(4.).into())), - }, - ..Default::default() - }, - inline_code: gpui::TextStyleRefinement { - font_family: Some("Zed Mono".into()), - background_color: Some(cx.theme().colors().editor_background), - ..Default::default() - }, - rule_color: Color::Muted.color(cx), - block_quote_border_color: Color::Muted.color(cx), - block_quote: gpui::TextStyleRefinement { - color: Some(Color::Muted.color(cx)), - ..Default::default() - }, - link: gpui::TextStyleRefinement { - color: Some(Color::Accent.color(cx)), - underline: Some(gpui::UnderlineStyle { - thickness: px(1.), - color: Some(Color::Accent.color(cx)), - wavy: false, - }), - ..Default::default() - }, - syntax: cx.theme().syntax().clone(), - selection_background_color: { - let mut selection = cx.theme().players().local().selection; - selection.fade_out(0.7); - selection - }, - heading: Default::default(), - ..Default::default() - }; - let markdown = cx.new(|cx| { - Markdown::new(MARKDOWN_EXAMPLE.into(), markdown_style, None, None, cx) - }); + let markdown = cx.new(|cx| Markdown::new(MARKDOWN_EXAMPLE.into(), None, None, cx)); HelloWorld { markdown } }) @@ -100,7 +49,57 @@ struct HelloWorld { } impl Render for HelloWorld { - fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let markdown_style = MarkdownStyle { + base_text_style: gpui::TextStyle { + font_family: "Zed Mono".into(), + color: cx.theme().colors().text, + ..Default::default() + }, + code_block: StyleRefinement { + text: Some(gpui::TextStyleRefinement { + font_family: Some("Zed Mono".into()), + background_color: Some(cx.theme().colors().editor_background), + ..Default::default() + }), + margin: gpui::EdgesRefinement { + top: Some(Length::Definite(rems(4.).into())), + left: Some(Length::Definite(rems(4.).into())), + right: Some(Length::Definite(rems(4.).into())), + bottom: Some(Length::Definite(rems(4.).into())), + }, + ..Default::default() + }, + inline_code: gpui::TextStyleRefinement { + font_family: Some("Zed Mono".into()), + background_color: Some(cx.theme().colors().editor_background), + ..Default::default() + }, + rule_color: Color::Muted.color(cx), + block_quote_border_color: Color::Muted.color(cx), + block_quote: gpui::TextStyleRefinement { + color: Some(Color::Muted.color(cx)), + ..Default::default() + }, + link: gpui::TextStyleRefinement { + color: Some(Color::Accent.color(cx)), + underline: Some(gpui::UnderlineStyle { + thickness: px(1.), + color: Some(Color::Accent.color(cx)), + wavy: false, + }), + ..Default::default() + }, + syntax: cx.theme().syntax().clone(), + selection_background_color: { + let mut selection = cx.theme().players().local().selection; + selection.fade_out(0.7); + selection + }, + heading: Default::default(), + ..Default::default() + }; + div() .flex() .bg(rgb(0x2e7d32)) @@ -112,6 +111,10 @@ impl Render for HelloWorld { .border_color(rgb(0x0000ff)) .text_xl() .text_color(rgb(0xffffff)) - .child(div().child(self.markdown.clone()).p_20()) + .child( + div() + .child(MarkdownElement::new(self.markdown.clone(), markdown_style)) + .p_20(), + ) } } diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 902ac352d9..7247f3edc0 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -14,7 +14,7 @@ use std::time::Duration; use gpui::{ AnyElement, App, BorderStyle, Bounds, ClipboardItem, CursorStyle, DispatchPhase, Edges, Entity, FocusHandle, Focusable, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla, KeyContext, - Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point, Render, Stateful, + Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point, Stateful, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun, TextStyle, TextStyleRefinement, actions, point, quad, }; @@ -74,7 +74,6 @@ pub struct Markdown { selection: Selection, pressed_link: Option, autoscroll_request: Option, - style: MarkdownStyle, parsed_markdown: ParsedMarkdown, should_reparse: bool, pending_parse: Option>>, @@ -97,7 +96,6 @@ actions!(markdown, [Copy]); impl Markdown { pub fn new( source: SharedString, - style: MarkdownStyle, language_registry: Option>, fallback_code_block_language: Option, cx: &mut Context, @@ -108,7 +106,6 @@ impl Markdown { selection: Selection::default(), pressed_link: None, autoscroll_request: None, - style, should_reparse: false, parsed_markdown: ParsedMarkdown::default(), pending_parse: None, @@ -136,14 +133,13 @@ impl Markdown { } } - pub fn new_text(source: SharedString, style: MarkdownStyle, cx: &mut Context) -> Self { + pub fn new_text(source: SharedString, cx: &mut Context) -> Self { let focus_handle = cx.focus_handle(); let mut this = Self { source, selection: Selection::default(), pressed_link: None, autoscroll_request: None, - style, should_reparse: false, parsed_markdown: ParsedMarkdown::default(), pending_parse: None, @@ -275,12 +271,6 @@ impl Markdown { } } -impl Render for Markdown { - fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - MarkdownElement::new(cx.entity().clone(), self.style.clone()) - } -} - impl Focusable for Markdown { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.focus_handle.clone() @@ -341,7 +331,7 @@ pub struct MarkdownElement { } impl MarkdownElement { - fn new(markdown: Entity, style: MarkdownStyle) -> Self { + pub fn new(markdown: Entity, style: MarkdownStyle) -> Self { Self { markdown, style } } @@ -638,6 +628,10 @@ impl Element for MarkdownElement { // If the path actually exists in the project, render a link to it. if let Some(project_path) = window.root::().flatten().and_then(|workspace| { + if path_range.path.is_absolute() { + return None; + } + workspace .read(cx) .project() diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 846aab2985..ef54fcf837 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -13,7 +13,7 @@ use gpui::{ }; use language::CursorShape; -use markdown::{Markdown, MarkdownStyle}; +use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use release_channel::ReleaseChannel; use remote::ssh_session::{ConnectionIdentifier, SshPortForwardOption}; use remote::{SshConnectionOptions, SshPlatform, SshRemoteClient}; @@ -182,7 +182,6 @@ impl SshPrompt { ) { let theme = ThemeSettings::get_global(cx); - let mut text_style = window.text_style(); let refinement = TextStyleRefinement { font_family: Some(theme.buffer_font.family.clone()), font_features: Some(FontFeatures::disable_ligatures()), @@ -192,7 +191,6 @@ impl SshPrompt { ..Default::default() }; - text_style.refine(&refinement); self.editor.update(cx, |editor, cx| { if prompt.contains("yes/no") { editor.set_masked(false, cx); @@ -202,12 +200,8 @@ impl SshPrompt { editor.set_text_style_refinement(refinement); editor.set_cursor_shape(CursorShape::Block, cx); }); - let markdown_style = MarkdownStyle { - base_text_style: text_style, - selection_background_color: cx.theme().players().local().selection, - ..Default::default() - }; - let markdown = cx.new(|cx| Markdown::new_text(prompt.into(), markdown_style, cx)); + + let markdown = cx.new(|cx| Markdown::new_text(prompt.into(), cx)); self.prompt = Some((markdown, tx)); self.status_message.take(); window.focus(&self.editor.focus_handle(cx)); @@ -231,7 +225,26 @@ impl SshPrompt { } impl Render for SshPrompt { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let theme = ThemeSettings::get_global(cx); + + let mut text_style = window.text_style(); + let refinement = TextStyleRefinement { + font_family: Some(theme.buffer_font.family.clone()), + font_features: Some(FontFeatures::disable_ligatures()), + font_size: Some(theme.buffer_font_size(cx).into()), + color: Some(cx.theme().colors().editor_foreground), + background_color: Some(gpui::transparent_black()), + ..Default::default() + }; + + text_style.refine(&refinement); + let markdown_style = MarkdownStyle { + base_text_style: text_style, + selection_background_color: cx.theme().players().local().selection, + ..Default::default() + }; + v_flex() .key_context("PasswordPrompt") .py_2() @@ -266,7 +279,7 @@ impl Render for SshPrompt { div() .size_full() .overflow_hidden() - .child(prompt.0.clone()) + .child(MarkdownElement::new(prompt.0.clone(), markdown_style)) .child(self.editor.clone()), ) }) diff --git a/crates/ui_prompt/src/ui_prompt.rs b/crates/ui_prompt/src/ui_prompt.rs index a4602c95f0..dfec2221b3 100644 --- a/crates/ui_prompt/src/ui_prompt.rs +++ b/crates/ui_prompt/src/ui_prompt.rs @@ -4,7 +4,7 @@ use gpui::{ Refineable, Render, RenderablePromptHandle, SharedString, Styled, TextStyleRefinement, Window, div, }; -use markdown::{Markdown, MarkdownStyle}; +use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use settings::{Settings, SettingsStore}; use theme::ThemeSettings; use ui::{ @@ -47,24 +47,9 @@ fn zed_prompt_renderer( actions: actions.iter().map(ToString::to_string).collect(), focus: cx.focus_handle(), active_action_id: 0, - detail: detail.filter(|text| !text.is_empty()).map(|text| { - cx.new(|cx| { - let settings = ThemeSettings::get_global(cx); - let mut base_text_style = window.text_style(); - base_text_style.refine(&TextStyleRefinement { - font_family: Some(settings.ui_font.family.clone()), - font_size: Some(settings.ui_font_size(cx).into()), - color: Some(ui::Color::Muted.color(cx)), - ..Default::default() - }); - let markdown_style = MarkdownStyle { - base_text_style, - selection_background_color: { cx.theme().players().local().selection }, - ..Default::default() - }; - Markdown::new(SharedString::new(text), markdown_style, None, None, cx) - }) - }), + detail: detail + .filter(|text| !text.is_empty()) + .map(|text| cx.new(|cx| Markdown::new(SharedString::new(text), None, None, cx))), } }); @@ -127,7 +112,7 @@ impl ZedPromptRenderer { } impl Render for ZedPromptRenderer { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let settings = ThemeSettings::get_global(cx); let font_family = settings.ui_font.family.clone(); let prompt = v_flex() @@ -153,11 +138,26 @@ impl Render for ZedPromptRenderer { .child(self.message.clone()) .text_color(ui::Color::Default.color(cx)), ) - .children( - self.detail - .clone() - .map(|detail| div().w_full().text_xs().child(detail)), - ) + .children(self.detail.clone().map(|detail| { + div() + .w_full() + .text_xs() + .child(MarkdownElement::new(detail, { + let settings = ThemeSettings::get_global(cx); + let mut base_text_style = window.text_style(); + base_text_style.refine(&TextStyleRefinement { + font_family: Some(settings.ui_font.family.clone()), + font_size: Some(settings.ui_font_size(cx).into()), + color: Some(ui::Color::Muted.color(cx)), + ..Default::default() + }); + MarkdownStyle { + base_text_style, + selection_background_color: { cx.theme().players().local().selection }, + ..Default::default() + } + })) + })) .child(h_flex().justify_end().gap_2().children( self.actions.iter().enumerate().rev().map(|(ix, action)| { ui::Button::new(ix, action.clone())