Improve handling of large output in embedded terminals (#32416)
#31922 made embedded terminals automatically grow to fit the content. We since found some issues with large output which this PR addresses by: - Only shaping / laying out lines that are visible in the viewport (based on `window.content_mask`) - Falling back to embedded scrolling after 1K lines. The perf fix above actually makes it possible to handle a lot of lines, but: - Alacrity uses a `u16` for rows internally, so we needed a limit to prevent overflow. - Scrolling through thousands of lines to get to the other side of a terminal tool call isn't great UX, so we might as well set the limit low. - We can consider raising the limit when we make card headers sticky. Release Notes: - Agent: Improve handling of large terminal output
This commit is contained in:
parent
ab70e524c8
commit
b103d7621b
4 changed files with 199 additions and 87 deletions
|
@ -140,12 +140,37 @@ pub struct TerminalView {
|
|||
#[derive(Default, Clone)]
|
||||
pub enum TerminalMode {
|
||||
#[default]
|
||||
Scrollable,
|
||||
Standalone,
|
||||
Embedded {
|
||||
max_lines: Option<usize>,
|
||||
max_lines_when_unfocused: Option<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ContentMode {
|
||||
Scrollable,
|
||||
Inline {
|
||||
displayed_lines: usize,
|
||||
total_lines: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl ContentMode {
|
||||
pub fn is_limited(&self) -> bool {
|
||||
match self {
|
||||
ContentMode::Scrollable => false,
|
||||
ContentMode::Inline {
|
||||
displayed_lines,
|
||||
total_lines,
|
||||
} => displayed_lines < total_lines,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_scrollable(&self) -> bool {
|
||||
matches!(self, ContentMode::Scrollable)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HoverTarget {
|
||||
tooltip: String,
|
||||
|
@ -223,7 +248,7 @@ impl TerminalView {
|
|||
blink_epoch: 0,
|
||||
hover: None,
|
||||
hover_tooltip_update: Task::ready(()),
|
||||
mode: TerminalMode::Scrollable,
|
||||
mode: TerminalMode::Standalone,
|
||||
workspace_id,
|
||||
show_breadcrumbs: TerminalSettings::get_global(cx).toolbar.breadcrumbs,
|
||||
block_below_cursor: None,
|
||||
|
@ -245,16 +270,46 @@ 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 };
|
||||
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,
|
||||
};
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn is_content_limited(&self, window: &Window) -> bool {
|
||||
const MAX_EMBEDDED_LINES: usize = 1_000;
|
||||
|
||||
/// 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 {
|
||||
match &self.mode {
|
||||
TerminalMode::Scrollable => false,
|
||||
TerminalMode::Embedded { max_lines } => {
|
||||
!self.focus_handle.is_focused(window) && max_lines.is_some()
|
||||
TerminalMode::Standalone => ContentMode::Scrollable,
|
||||
TerminalMode::Embedded {
|
||||
max_lines_when_unfocused,
|
||||
} => {
|
||||
let total_lines = self.terminal.read(cx).total_lines();
|
||||
|
||||
if total_lines > Self::MAX_EMBEDDED_LINES {
|
||||
ContentMode::Scrollable
|
||||
} else {
|
||||
let mut displayed_lines = total_lines;
|
||||
|
||||
if !self.focus_handle.is_focused(window) {
|
||||
if let Some(max_lines) = max_lines_when_unfocused {
|
||||
displayed_lines = displayed_lines.min(*max_lines)
|
||||
}
|
||||
}
|
||||
|
||||
ContentMode::Inline {
|
||||
displayed_lines,
|
||||
total_lines,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -840,10 +895,10 @@ impl TerminalView {
|
|||
}))
|
||||
}
|
||||
|
||||
fn render_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
||||
fn render_scrollbar(&self, window: &Window, 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 { .. })
|
||||
|| !self.content_mode(window, cx).is_scrollable()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
@ -1493,7 +1548,7 @@ impl Render for TerminalView {
|
|||
self.block_below_cursor.clone(),
|
||||
self.mode.clone(),
|
||||
))
|
||||
.when_some(self.render_scrollbar(cx), |div, scrollbar| {
|
||||
.when_some(self.render_scrollbar(window, cx), |div, scrollbar| {
|
||||
div.child(scrollbar)
|
||||
}),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue