diff --git a/crates/agent/src/active_thread.rs b/crates/agent/src/active_thread.rs index 9f466883cc..841482e482 100644 --- a/crates/agent/src/active_thread.rs +++ b/crates/agent/src/active_thread.rs @@ -2400,6 +2400,7 @@ impl ActiveThread { markdown_element.code_block_renderer( markdown::CodeBlockRenderer::Default { copy_button: false, + copy_button_on_hover: false, border: true, }, ) @@ -2719,6 +2720,7 @@ impl ActiveThread { ) .code_block_renderer(markdown::CodeBlockRenderer::Default { copy_button: false, + copy_button_on_hover: false, border: false, }) .on_url_click({ @@ -2749,6 +2751,7 @@ impl ActiveThread { ) .code_block_renderer(markdown::CodeBlockRenderer::Default { copy_button: false, + copy_button_on_hover: false, border: false, }) .on_url_click({ diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index bbc92ea735..8c60f980da 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -637,7 +637,7 @@ impl ToolCard for EditFileToolCard { .p_3() .gap_1() .border_t_1() - .rounded_md() + .rounded_b_md() .border_color(border_color) .bg(cx.theme().colors().editor_background); diff --git a/crates/assistant_tools/src/terminal_tool.rs b/crates/assistant_tools/src/terminal_tool.rs index d8415415e9..5ca65741ac 100644 --- a/crates/assistant_tools/src/terminal_tool.rs +++ b/crates/assistant_tools/src/terminal_tool.rs @@ -2,13 +2,18 @@ use crate::schema::json_schema_for; use anyhow::{Context as _, Result, anyhow, bail}; use assistant_tool::{ActionLog, Tool, ToolCard, ToolResult, ToolUseStatus}; use futures::{FutureExt as _, future::Shared}; -use gpui::{AnyWindowHandle, App, AppContext, Empty, Entity, EntityId, Task, WeakEntity, Window}; +use gpui::{ + AnyWindowHandle, App, AppContext, Empty, Entity, EntityId, Task, TextStyleRefinement, + WeakEntity, Window, +}; use language::LineEnding; use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchemaFormat}; +use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use portable_pty::{CommandBuilder, PtySize, native_pty_system}; use project::{Project, terminals::TerminalKind}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use settings::Settings; use std::{ env, path::{Path, PathBuf}, @@ -17,6 +22,7 @@ use std::{ time::{Duration, Instant}, }; use terminal_view::TerminalView; +use theme::ThemeSettings; use ui::{Disclosure, Tooltip, prelude::*}; use util::{ get_system_shell, markdown::MarkdownInlineCode, size::format_file_size, @@ -211,8 +217,21 @@ impl Tool for TerminalTool { } }); + let command_markdown = cx.new(|cx| { + Markdown::new( + format!("```bash\n{}\n```", input.command).into(), + None, + None, + cx, + ) + }); + let card = cx.new(|cx| { - TerminalToolCard::new(input.command.clone(), working_dir.clone(), cx.entity_id()) + TerminalToolCard::new( + command_markdown.clone(), + working_dir.clone(), + cx.entity_id(), + ) }); let output = cx.spawn({ @@ -388,7 +407,7 @@ fn working_dir( } struct TerminalToolCard { - input_command: String, + input_command: Entity, working_dir: Option, entity_id: EntityId, exit_status: Option, @@ -404,7 +423,11 @@ struct TerminalToolCard { } impl TerminalToolCard { - pub fn new(input_command: String, working_dir: Option, entity_id: EntityId) -> Self { + pub fn new( + input_command: Entity, + working_dir: Option, + entity_id: EntityId, + ) -> Self { Self { input_command, working_dir, @@ -427,7 +450,7 @@ impl ToolCard for TerminalToolCard { fn render( &mut self, status: &ToolUseStatus, - _window: &mut Window, + window: &mut Window, _workspace: WeakEntity, cx: &mut Context, ) -> impl IntoElement { @@ -571,11 +594,25 @@ impl ToolCard for TerminalToolCard { .rounded_lg() .overflow_hidden() .child( - v_flex().p_2().gap_0p5().bg(header_bg).child(header).child( - Label::new(self.input_command.clone()) - .buffer_font(cx) - .size(LabelSize::Small), - ), + v_flex() + .p_2() + .gap_0p5() + .bg(header_bg) + .text_xs() + .child(header) + .child( + MarkdownElement::new( + self.input_command.clone(), + markdown_style(window, cx), + ) + .code_block_renderer( + markdown::CodeBlockRenderer::Default { + copy_button: false, + copy_button_on_hover: true, + border: false, + }, + ), + ), ) .when(self.preview_expanded && !should_hide_terminal, |this| { this.child( @@ -594,6 +631,27 @@ impl ToolCard for TerminalToolCard { } } +fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle { + let theme_settings = ThemeSettings::get_global(cx); + let buffer_font_size = TextSize::Default.rems(cx); + let mut text_style = window.text_style(); + + text_style.refine(&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()), + color: Some(cx.theme().colors().text), + ..Default::default() + }); + + MarkdownStyle { + base_text_style: text_style.clone(), + selection_background_color: cx.theme().players().local().selection, + ..Default::default() + } +} + #[cfg(test)] mod tests { use editor::EditorSettings; diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 84c8042d74..4f97ec04ef 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -640,6 +640,7 @@ impl CompletionsMenu { MarkdownElement::new(markdown.clone(), hover_markdown_style(window, cx)) .code_block_renderer(markdown::CodeBlockRenderer::Default { copy_button: false, + copy_button_on_hover: false, border: false, }) .on_url_click(open_markdown_url), diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 297abd5c37..d741a980c0 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -897,6 +897,7 @@ impl InfoPopover { MarkdownElement::new(markdown, hover_markdown_style(window, cx)) .code_block_renderer(markdown::CodeBlockRenderer::Default { copy_button: false, + copy_button_on_hover: false, border: false, }) .on_url_click(open_markdown_url), diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index f9d9c8871c..a3a8e7c456 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -113,6 +113,7 @@ struct Options { pub enum CodeBlockRenderer { Default { copy_button: bool, + copy_button_on_hover: bool, border: bool, }, Custom { @@ -444,6 +445,7 @@ impl MarkdownElement { style, code_block_renderer: CodeBlockRenderer::Default { copy_button: true, + copy_button_on_hover: false, border: false, }, on_url_click: None, @@ -815,7 +817,7 @@ impl Element for MarkdownElement { (CodeBlockRenderer::Default { .. }, _) | (_, true) => { // This is a parent container that we can position the copy button inside. builder.push_div( - div().relative().w_full(), + div().group("code_block").relative().w_full(), range, markdown_end, ); @@ -1066,6 +1068,37 @@ impl Element for MarkdownElement { }); } + if let CodeBlockRenderer::Default { + copy_button_on_hover: true, + .. + } = &self.code_block_renderer + { + builder.modify_current_div(|el| { + let content_range = parser::extract_code_block_content_range( + parsed_markdown.source()[range.clone()].trim(), + ); + let content_range = content_range.start + range.start + ..content_range.end + range.start; + + let code = parsed_markdown.source()[content_range].to_string(); + let codeblock = render_copy_code_block_button( + range.end, + code, + self.markdown.clone(), + cx, + ); + el.child( + div() + .absolute() + .top_0() + .right_0() + .w_5() + .visible_on_hover("code_block") + .child(codeblock), + ) + }); + } + // Pop the parent container. builder.pop_div(); }