@@ -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({
@@ -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<Markdown>,
working_dir: Option<PathBuf>,
entity_id: EntityId,
exit_status: Option<ExitStatus>,
@@ -404,7 +423,11 @@ struct TerminalToolCard {
}
impl TerminalToolCard {
- pub fn new(input_command: String, working_dir: Option<PathBuf>, entity_id: EntityId) -> Self {
+ pub fn new(
+ input_command: Entity<Markdown>,
+ working_dir: Option<PathBuf>,
+ 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<Workspace>,
cx: &mut Context<Self>,
) -> 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;
@@ -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();
}