agent: Display full terminal output without scrolling (#31922)
The terminal tool card used a fixed height and scrolling, but this meant that it was too tall for commands that only outputted a few lines, and the nested scrolling was undesirable. This PR makes the card be as too as needed to fit the entire output (no scrolling), and allows the user to collapse it to fewer lines when applicable. Making it work the same way as the edit tool card. In fact, both tools now use a shared UI component. https://github.com/user-attachments/assets/1127e21d-1d41-4a4b-a99f-7cd70fccbb56 Release Notes: - Agent: Display full terminal output - Agent: Allow collapsing terminal output
This commit is contained in:
parent
01a77bb231
commit
b7abc9d493
9 changed files with 275 additions and 144 deletions
|
@ -264,7 +264,6 @@ async fn deserialize_pane_group(
|
|||
workspace.clone(),
|
||||
Some(workspace_id),
|
||||
project.downgrade(),
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine};
|
||||
use gpui::{
|
||||
AnyElement, App, AvailableSpace, Bounds, ContentMask, Context, DispatchPhase, Element,
|
||||
ElementId, Entity, FocusHandle, Focusable, Font, FontStyle, FontWeight, GlobalElementId,
|
||||
HighlightStyle, Hitbox, Hsla, InputHandler, InteractiveElement, Interactivity, IntoElement,
|
||||
LayoutId, ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine,
|
||||
ElementId, Entity, FocusHandle, Font, FontStyle, FontWeight, GlobalElementId, HighlightStyle,
|
||||
Hitbox, Hsla, InputHandler, InteractiveElement, Interactivity, IntoElement, LayoutId,
|
||||
ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine,
|
||||
StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle, UTF16Selection,
|
||||
UnderlineStyle, WeakEntity, WhiteSpace, Window, WindowTextSystem, div, fill, point, px,
|
||||
relative, size,
|
||||
|
@ -32,7 +32,7 @@ use workspace::Workspace;
|
|||
use std::mem;
|
||||
use std::{fmt::Debug, ops::RangeInclusive, rc::Rc};
|
||||
|
||||
use crate::{BlockContext, BlockProperties, TerminalView};
|
||||
use crate::{BlockContext, BlockProperties, TerminalMode, TerminalView};
|
||||
|
||||
/// The information generated during layout that is necessary for painting.
|
||||
pub struct LayoutState {
|
||||
|
@ -160,7 +160,7 @@ pub struct TerminalElement {
|
|||
focused: bool,
|
||||
cursor_visible: bool,
|
||||
interactivity: Interactivity,
|
||||
embedded: bool,
|
||||
mode: TerminalMode,
|
||||
block_below_cursor: Option<Rc<BlockProperties>>,
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ impl TerminalElement {
|
|||
focused: bool,
|
||||
cursor_visible: bool,
|
||||
block_below_cursor: Option<Rc<BlockProperties>>,
|
||||
embedded: bool,
|
||||
mode: TerminalMode,
|
||||
) -> TerminalElement {
|
||||
TerminalElement {
|
||||
terminal,
|
||||
|
@ -191,7 +191,7 @@ impl TerminalElement {
|
|||
focus: focus.clone(),
|
||||
cursor_visible,
|
||||
block_below_cursor,
|
||||
embedded,
|
||||
mode,
|
||||
interactivity: Default::default(),
|
||||
}
|
||||
.track_focus(&focus)
|
||||
|
@ -511,21 +511,20 @@ impl TerminalElement {
|
|||
},
|
||||
),
|
||||
);
|
||||
self.interactivity.on_scroll_wheel({
|
||||
let terminal_view = self.terminal_view.downgrade();
|
||||
move |e, window, cx| {
|
||||
terminal_view
|
||||
.update(cx, |terminal_view, cx| {
|
||||
if !terminal_view.embedded
|
||||
|| terminal_view.focus_handle(cx).is_focused(window)
|
||||
{
|
||||
|
||||
if !matches!(self.mode, TerminalMode::Embedded { .. }) {
|
||||
self.interactivity.on_scroll_wheel({
|
||||
let terminal_view = self.terminal_view.downgrade();
|
||||
move |e, _window, cx| {
|
||||
terminal_view
|
||||
.update(cx, |terminal_view, cx| {
|
||||
terminal_view.scroll_wheel(e, cx);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Mouse mode handlers:
|
||||
// All mouse modes need the extra click handlers
|
||||
|
@ -606,16 +605,6 @@ impl Element for TerminalElement {
|
|||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
if self.embedded {
|
||||
let scrollable = {
|
||||
let term = self.terminal.read(cx);
|
||||
!term.scrolled_to_top() && !term.scrolled_to_bottom() && self.focused
|
||||
};
|
||||
if scrollable {
|
||||
self.interactivity.occlude_mouse();
|
||||
}
|
||||
}
|
||||
|
||||
let layout_id = self.interactivity.request_layout(
|
||||
global_id,
|
||||
inspector_id,
|
||||
|
@ -623,8 +612,29 @@ impl Element for TerminalElement {
|
|||
cx,
|
||||
|mut style, window, cx| {
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = relative(1.).into();
|
||||
// style.overflow = point(Overflow::Hidden, Overflow::Hidden);
|
||||
|
||||
match &self.mode {
|
||||
TerminalMode::Scrollable => {
|
||||
style.size.height = relative(1.).into();
|
||||
}
|
||||
TerminalMode::Embedded { max_lines } => {
|
||||
let rem_size = window.rem_size();
|
||||
let line_height = window.text_style().font_size.to_pixels(rem_size)
|
||||
* TerminalSettings::get_global(cx)
|
||||
.line_height
|
||||
.value()
|
||||
.to_pixels(rem_size)
|
||||
.0;
|
||||
|
||||
let mut line_count = self.terminal.read(cx).total_lines();
|
||||
if !self.focused {
|
||||
if let Some(max_lines) = max_lines {
|
||||
line_count = line_count.min(*max_lines);
|
||||
}
|
||||
}
|
||||
style.size.height = (line_count * line_height).into();
|
||||
}
|
||||
}
|
||||
|
||||
window.request_layout(style, None, cx)
|
||||
},
|
||||
|
@ -679,12 +689,13 @@ impl Element for TerminalElement {
|
|||
|
||||
let line_height = terminal_settings.line_height.value();
|
||||
|
||||
let font_size = if self.embedded {
|
||||
window.text_style().font_size.to_pixels(window.rem_size())
|
||||
} else {
|
||||
terminal_settings
|
||||
let font_size = match &self.mode {
|
||||
TerminalMode::Embedded { .. } => {
|
||||
window.text_style().font_size.to_pixels(window.rem_size())
|
||||
}
|
||||
TerminalMode::Scrollable => terminal_settings
|
||||
.font_size
|
||||
.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx))
|
||||
.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx)),
|
||||
};
|
||||
|
||||
let theme = cx.theme().clone();
|
||||
|
|
|
@ -439,7 +439,6 @@ impl TerminalPanel {
|
|||
weak_workspace.clone(),
|
||||
database_id,
|
||||
project.downgrade(),
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
@ -677,7 +676,6 @@ impl TerminalPanel {
|
|||
workspace.weak_handle(),
|
||||
workspace.database_id(),
|
||||
workspace.project().downgrade(),
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
@ -718,7 +716,6 @@ impl TerminalPanel {
|
|||
workspace.weak_handle(),
|
||||
workspace.database_id(),
|
||||
workspace.project().downgrade(),
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
|
@ -116,7 +116,7 @@ pub struct TerminalView {
|
|||
context_menu: Option<(Entity<ContextMenu>, gpui::Point<Pixels>, Subscription)>,
|
||||
cursor_shape: CursorShape,
|
||||
blink_state: bool,
|
||||
embedded: bool,
|
||||
mode: TerminalMode,
|
||||
blinking_terminal_enabled: bool,
|
||||
cwd_serialized: bool,
|
||||
blinking_paused: bool,
|
||||
|
@ -137,6 +137,15 @@ pub struct TerminalView {
|
|||
_terminal_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub enum TerminalMode {
|
||||
#[default]
|
||||
Scrollable,
|
||||
Embedded {
|
||||
max_lines: Option<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HoverTarget {
|
||||
tooltip: String,
|
||||
|
@ -176,7 +185,6 @@ impl TerminalView {
|
|||
workspace: WeakEntity<Workspace>,
|
||||
workspace_id: Option<WorkspaceId>,
|
||||
project: WeakEntity<Project>,
|
||||
embedded: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
|
@ -215,7 +223,7 @@ impl TerminalView {
|
|||
blink_epoch: 0,
|
||||
hover: None,
|
||||
hover_tooltip_update: Task::ready(()),
|
||||
embedded,
|
||||
mode: TerminalMode::Scrollable,
|
||||
workspace_id,
|
||||
show_breadcrumbs: TerminalSettings::get_global(cx).toolbar.breadcrumbs,
|
||||
block_below_cursor: None,
|
||||
|
@ -236,6 +244,21 @@ impl TerminalView {
|
|||
}
|
||||
}
|
||||
|
||||
/// Enable 'embedded' mode where the terminal displays the full content with an optional limit of lines.
|
||||
pub fn set_embedded_mode(&mut self, max_lines: Option<usize>, cx: &mut Context<Self>) {
|
||||
self.mode = TerminalMode::Embedded { max_lines };
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn is_content_limited(&self, window: &Window) -> bool {
|
||||
match &self.mode {
|
||||
TerminalMode::Scrollable => false,
|
||||
TerminalMode::Embedded { max_lines } => {
|
||||
!self.focus_handle.is_focused(window) && max_lines.is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the marked (pre-edit) text from the IME.
|
||||
pub(crate) fn set_marked_text(
|
||||
&mut self,
|
||||
|
@ -820,6 +843,7 @@ impl TerminalView {
|
|||
fn render_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
||||
if !Self::should_show_scrollbar(cx)
|
||||
|| !(self.show_scrollbar || self.scrollbar_state.is_dragging())
|
||||
|| matches!(self.mode, TerminalMode::Embedded { .. })
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
@ -1467,7 +1491,7 @@ impl Render for TerminalView {
|
|||
focused,
|
||||
self.should_show_cursor(focused, cx),
|
||||
self.block_below_cursor.clone(),
|
||||
self.embedded,
|
||||
self.mode.clone(),
|
||||
))
|
||||
.when_some(self.render_scrollbar(cx), |div, scrollbar| {
|
||||
div.child(scrollbar)
|
||||
|
@ -1593,7 +1617,6 @@ impl Item for TerminalView {
|
|||
self.workspace.clone(),
|
||||
workspace_id,
|
||||
self.project.clone(),
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
@ -1751,7 +1774,6 @@ impl SerializableItem for TerminalView {
|
|||
workspace,
|
||||
Some(workspace_id),
|
||||
project.downgrade(),
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue