agent: Make terminal command render with Markdown in the tool card (#30430)
Closes https://github.com/zed-industries/zed/issues/30411 Rendering as markdown gives us text selection and copying for free. In the future, we may want to explore having these commands be actual editors, allowing you to step in, change the command, and re-run it right from there. Release Notes: - agent: Made the terminal command in the tool card selectable and copyable.
This commit is contained in:
parent
daa777440d
commit
39da72161f
6 changed files with 108 additions and 12 deletions
|
@ -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({
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue