Skip to main content

ui/components/ai/
terminal_output_block.rs

1use gpui::StyledText;
2
3use crate::prelude::*;
4
5/// Monospace font for captured terminal output (mirrors `code_editor.rs`'s
6/// `CODE_FONT_FAMILY` constant, duplicated locally since that constant is
7/// private to its module).
8const TERMINAL_OUTPUT_FONT_FAMILY: &str = "IBM Plex Mono";
9const TERMINAL_OUTPUT_FONT_SIZE: Pixels = px(12.5);
10
11/// Renders a tool call's captured terminal output as static text (Decision
12/// #2b: no live PTY grid — see this component's call sites for that
13/// trade-off's rationale). Sourced directly from already-captured
14/// command/raw-output strings; this has no cursor, resize, or live-stream
15/// semantics.
16#[derive(IntoElement, RegisterComponent)]
17pub struct TerminalOutputBlock {
18    id: ElementId,
19    command: Option<SharedString>,
20    raw_output: SharedString,
21}
22
23impl TerminalOutputBlock {
24    pub fn new(id: impl Into<ElementId>, raw_output: impl Into<SharedString>) -> Self {
25        Self {
26            id: id.into(),
27            command: None,
28            raw_output: raw_output.into(),
29        }
30    }
31
32    /// The command line that produced `raw_output`, shown as a small header.
33    pub fn command(mut self, command: impl Into<SharedString>) -> Self {
34        self.command = Some(command.into());
35        self
36    }
37}
38
39impl RenderOnce for TerminalOutputBlock {
40    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
41        v_flex()
42            .id(self.id)
43            .w_full()
44            .gap_1()
45            .p_2()
46            .rounded_md()
47            .bg(cx.theme().colors().editor_background)
48            .when_some(self.command, |this, command| {
49                this.child(
50                    h_flex()
51                        .gap_1()
52                        .child(
53                            Icon::new(IconName::ToolTerminal)
54                                .size(IconSize::XSmall)
55                                .color(Color::Muted),
56                        )
57                        .child(
58                            div()
59                                .font_family(TERMINAL_OUTPUT_FONT_FAMILY)
60                                .text_size(TERMINAL_OUTPUT_FONT_SIZE)
61                                .text_color(cx.theme().colors().text_muted)
62                                .child(command),
63                        ),
64                )
65            })
66            .child(
67                div()
68                    .font_family(TERMINAL_OUTPUT_FONT_FAMILY)
69                    .text_size(TERMINAL_OUTPUT_FONT_SIZE)
70                    .text_color(cx.theme().colors().text)
71                    .child(StyledText::new(self.raw_output)),
72            )
73    }
74}
75
76impl Component for TerminalOutputBlock {
77    fn scope() -> ComponentScope {
78        ComponentScope::Agent
79    }
80
81    fn description() -> Option<&'static str> {
82        Some(
83            "Renders captured terminal output as static text (no PTY grid) \
84             for a tool-call's terminal content, with an optional command header.",
85        )
86    }
87
88    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
89        Some(
90            v_flex()
91                .w_96()
92                .gap_4()
93                .child(single_example(
94                    "With command",
95                    TerminalOutputBlock::new(
96                        "term-preview-1",
97                        "Compiling boltz-ui v0.2.11\n    Finished dev profile in 3.21s",
98                    )
99                    .command("cargo build -p boltz-ui")
100                    .into_any_element(),
101                ))
102                .into_any_element(),
103        )
104    }
105}