Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions assets/settings/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,14 @@
//
// Default: true
"show_merge_conflict_indicator": true,
// Maximum number of lines a tool output, file diff, or terminal block in
// the agent thread shows before it is collapsed behind an expand control.
// Both the collapsed and expanded states are height-capped, so a very
// large buffer never renders at full height. Set to 0 to disable the cap
// and render blocks at full height.
//
// Default: 10
"tool_output_max_lines": 10,
},
// Whether the screen sharing icon is shown in the os status bar.
"show_call_status_icon": true,
Expand Down
1 change: 1 addition & 0 deletions crates/agent/src/tool_permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ mod tests {
message_editor_min_lines: 1,
tool_permissions,
sandbox_permissions: Default::default(),
tool_output_max_lines: 24,
show_turn_stats: false,
show_merge_conflict_indicator: true,
sidebar_side: Default::default(),
Expand Down
2 changes: 2 additions & 0 deletions crates/agent_settings/src/agent_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ pub struct AgentSettings {
pub show_merge_conflict_indicator: bool,
pub tool_permissions: ToolPermissions,
pub sandbox_permissions: SandboxPermissions,
pub tool_output_max_lines: usize,
}

impl AgentSettings {
Expand Down Expand Up @@ -770,6 +771,7 @@ impl Settings for AgentSettings {
show_merge_conflict_indicator: agent.show_merge_conflict_indicator.unwrap(),
tool_permissions: compile_tool_permissions(agent.tool_permissions),
sandbox_permissions: compile_sandbox_permissions(agent.sandbox_permissions),
tool_output_max_lines: agent.tool_output_max_lines.unwrap_or(10),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/agent_ui/src/agent_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,7 @@ mod tests {
message_editor_min_lines: 1,
tool_permissions: Default::default(),
sandbox_permissions: Default::default(),
tool_output_max_lines: 24,
show_turn_stats: false,
show_merge_conflict_indicator: true,
sidebar_side: Default::default(),
Expand Down
390 changes: 366 additions & 24 deletions crates/agent_ui/src/conversation_view/thread_view.rs

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion crates/agent_ui/src/entry_view_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,11 @@ fn create_terminal(
window,
cx,
);
view.set_embedded_mode(Some(1000), cx);
// Cap the embedded terminal to the configured number of lines (showing
// the most recent output). The agent thread toggles this cap via a
// chevron to expand/collapse. `0` disables the cap.
let max_lines = agent_settings::AgentSettings::get_global(cx).tool_output_max_lines;
view.set_embedded_mode((max_lines != 0).then_some(max_lines), cx);
view
})
}
Expand Down
12 changes: 12 additions & 0 deletions crates/settings_content/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,18 @@ pub struct AgentSettingsContent {
/// These are populated when choosing "Allow always" from a sandbox
/// escalation prompt.
pub sandbox_permissions: Option<SandboxPermissionsContent>,

/// Maximum number of lines a tool output, file diff, or terminal block in
/// the agent thread shows before it is collapsed behind an expand control.
/// Both the collapsed and expanded states are height-capped, so a very
/// large buffer never renders at full height (which can cause UI
/// performance problems). Expanding a block raises the cap; it does not
/// remove it.
///
/// Set to 0 to disable the cap and render blocks at full height.
///
/// Default: 10
pub tool_output_max_lines: Option<usize>,
}

impl AgentSettingsContent {
Expand Down
19 changes: 19 additions & 0 deletions crates/terminal_view/src/terminal_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,25 @@ impl Element for TerminalElement {

let mut size = bounds.size;
size.width -= gutter;
// For capped embedded terminals, grow the grid one row taller
// than the cap so the trailing blank cursor line the grid keeps
// after each newline sits just below the visible (clipped) box.
// The element bounds stay at the cap height, so painting clips
// that extra row and the box hugs the real output with no
// padding above or below the text. Skipped on the alternate
// screen, where a full-screen TUI owns the whole grid.
if let TerminalMode::Embedded {
max_lines: Some(max_lines),
} = &self.mode
&& !self
.terminal
.read(cx)
.last_content()
.mode
.contains(Modes::ALT_SCREEN)
{
size.height = px((*max_lines + 1) as f32 * line_height);
}
let available_height = size.height;

// https://github.com/zed-industries/zed/issues/2750
Expand Down
78 changes: 50 additions & 28 deletions crates/terminal_view/src/terminal_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,10 @@ pub enum TerminalMode {
#[default]
Standalone,
Embedded {
max_lines_when_unfocused: Option<usize>,
/// Caps the number of lines rendered inline (the most recent lines are
/// shown). `None` means no cap. Applies whether or not the terminal is
/// focused, so the displayed height is deterministic.
max_lines: Option<usize>,
},
}

Expand Down Expand Up @@ -303,15 +306,10 @@ 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_when_unfocused: Option<usize>,
cx: &mut Context<Self>,
) {
self.mode = TerminalMode::Embedded {
max_lines_when_unfocused,
};
/// Enable 'embedded' mode where the terminal displays its content inline,
/// optionally capped to the most recent `max_lines` lines.
pub fn set_embedded_mode(&mut self, max_lines: Option<usize>, cx: &mut Context<Self>) {
self.mode = TerminalMode::Embedded { max_lines };
cx.notify();
}

Expand All @@ -320,29 +318,53 @@ impl TerminalView {
/// Returns the current `ContentMode` depending on the set `TerminalMode` and the current number of lines
///
/// Note: Even in embedded mode, the terminal will fallback to scrollable when its content exceeds `MAX_EMBEDDED_LINES`
pub fn content_mode(&self, window: &Window, cx: &App) -> ContentMode {
pub fn content_mode(&self, _window: &Window, cx: &App) -> ContentMode {
match &self.mode {
TerminalMode::Standalone => ContentMode::Scrollable,
TerminalMode::Embedded {
max_lines_when_unfocused,
} => {
let total_lines = self.terminal.read(cx).total_lines();
TerminalMode::Embedded { max_lines } => {
let terminal = self.terminal.read(cx);
let raw_total_lines = terminal.total_lines();

if total_lines > Self::MAX_EMBEDDED_LINES {
ContentMode::Scrollable
} else {
let mut displayed_lines = total_lines;
if raw_total_lines > Self::MAX_EMBEDDED_LINES {
return ContentMode::Scrollable;
}

if !self.focus_handle.is_focused(window)
&& let Some(max_lines) = max_lines_when_unfocused
{
displayed_lines = displayed_lines.min(*max_lines)
}
let Some(max_lines) = max_lines else {
// No cap configured: render every line inline, matching the
// raw grid (including the trailing cursor line).
return ContentMode::Inline {
displayed_lines: raw_total_lines,
total_lines: raw_total_lines,
};
};

ContentMode::Inline {
displayed_lines,
total_lines,
}
// On the alternate screen a full-screen TUI owns the whole grid and
// positions the cursor arbitrarily, so it is not a reliable marker
// of where content ends. Fall back to sizing from the raw grid.
if terminal.last_content().mode.contains(Modes::ALT_SCREEN) {
return ContentMode::Inline {
displayed_lines: raw_total_lines.min(*max_lines),
total_lines: raw_total_lines,
};
}

// Count the real output lines rather than the grid height. The
// grid keeps a trailing blank line under the cursor after each
// newline, and pads unused rows; using those would make the inline
// box render at full height with empty padding. Deriving the
// content extent from the cursor lets the box hug the output and
// grow up to the cap as it streams in.
let cursor = terminal.last_content().cursor.point;
let viewport_lines = terminal.viewport_lines();
let content_rows = ((cursor.line.max(0) as usize) + usize::from(cursor.column > 0))
.min(viewport_lines);
let history_lines = raw_total_lines.saturating_sub(viewport_lines);
let total_lines = history_lines + content_rows;
let displayed_lines = total_lines.min(*max_lines);

ContentMode::Inline {
displayed_lines,
total_lines,
}
}
}
Expand Down
Loading