Show hidden output line counts in compact terminal activity (#48761)

## What changed

Replace generic output disclosure labels with counts such as `+ 5 lines (ctrl+t to expand)` for compact command activity, both live and in history. Count retained output lines hidden or clipped by the preview, excluding output discarded by storage limits.

Use the configured `open_transcript` shortcut and omit the hint when it does not fit or the action is unbound. Invalidate cached layouts when keybindings change. Keep generic `Show details` labels for other hidden details and keep disclosure controls outside copyable and searchable text.

## Testing

Add regression tests for live/history consistency, remapped and disabled shortcuts, and exclusion of hints from source text. Add snapshot coverage for fully visible output, hidden and clipped lines, command-only details, and storage-truncated output.

GitOrigin-RevId: c468be9b88b69dc3ca1abdb6b7be023244d32a57
This commit is contained in:
Felipe Coury
2026-09-27 18:09:34 +00:00
committed by copyberry
parent 71b38795d8
commit e75b6b1e0c
12 changed files with 263 additions and 34 deletions
+30 -10
View File
@@ -2,6 +2,7 @@
use super::model::ExecCell;
use crate::exec_command::strip_bash_lc_and_escape;
use crate::history_cell::ActivityDisclosure;
use crate::history_cell::HistoryRenderMode;
use crate::history_cell::activity_preview::DETAIL_PREVIEW_LINES;
use crate::history_cell::activity_preview::clipped_line;
@@ -16,17 +17,33 @@ use ratatui::style::Stylize;
use ratatui::text::Line;
impl ExecCell {
pub(super) fn command_has_hidden_details(&self, width: u16) -> bool {
pub(super) fn command_disclosure(&self, width: u16) -> Option<ActivityDisclosure> {
let [call] = self.group.calls.as_slice() else {
return false;
return None;
};
// Interaction output is intentionally absent from the expanded transcript.
if !call.is_unified_exec_interaction()
&& let Some(output) = &call.output
{
let retained = output.line_counts().1;
let clipped = output
.lines()
.rev()
.take(DETAIL_PREVIEW_LINES)
.filter(|raw| ansi_escape_line(raw.as_ref()).width() + 4 > usize::from(width))
.count();
let hidden = retained.saturating_sub(DETAIL_PREVIEW_LINES) + clipped;
if hidden > 0 {
return Some(ActivityDisclosure::OutputLines(hidden));
}
}
if !self
.group
.details
.lines_after(/*after_calls*/ 1, width, HistoryRenderMode::Rich)
.is_empty()
{
return true;
return Some(ActivityDisclosure::Generic);
}
let script = strip_bash_lc_and_escape(&call.command);
if script.lines().nth(/*n*/ 1).is_some()
@@ -35,14 +52,17 @@ impl ExecCell {
.first()
.is_some_and(|header| header.width() > usize::from(width))
{
return true;
return Some(ActivityDisclosure::Generic);
}
call.output.as_ref().is_some_and(|output| {
output.line_counts().1 > DETAIL_PREVIEW_LINES
|| output
.transcript_lines()
.any(|line| ansi_escape_line(line.as_ref()).width() + 4 > usize::from(width))
})
call.output
.as_ref()
.is_some_and(|output| {
output.line_counts().1 > DETAIL_PREVIEW_LINES
|| output.transcript_lines().any(|line| {
ansi_escape_line(line.as_ref()).width() + 4 > usize::from(width)
})
})
.then_some(ActivityDisclosure::Generic)
}
pub(super) fn compact_command_lines(&self, width: u16) -> Vec<HyperlinkLine> {
+6 -4
View File
@@ -6,6 +6,7 @@ use super::model::CommandOutput;
use super::model::ExecCall;
use super::model::ExecCell;
use crate::exec_command::strip_bash_lc_and_escape;
use crate::history_cell::ActivityDisclosure;
use crate::history_cell::HistoryCell;
use crate::history_cell::HistoryRenderMode;
use crate::history_cell::plain_lines;
@@ -218,18 +219,19 @@ impl HistoryCell for ExecCell {
}
}
fn has_hidden_activity_details(&self, width: u16) -> bool {
fn activity_disclosure(&self, width: u16) -> Option<ActivityDisclosure> {
if self.is_exploring_cell() {
return true;
return Some(ActivityDisclosure::Generic);
}
if self.group.calls.iter().any(ExecCall::is_user_shell_command) {
// User-shell previews already retain wrapped commands and up to fifty output rows.
// Reuse its actual truncation decision instead of comparing differently styled text.
return self
.command_display_lines_with_hidden_details(width)
.hidden_details;
.hidden_details
.then_some(ActivityDisclosure::Generic);
}
self.command_has_hidden_details(width)
self.command_disclosure(width)
}
fn compact_hyperlink_lines(&self, width: u16) -> Vec<HyperlinkLine> {