fix: wrap long exec lines in transcript overlay (#7481)

What
-----
- Fix the Ctrl+T transcript overlay so that very long exec output lines
are soft‑wrapped to the viewport width instead of being rendered as a
single truncated row.
- Add a regression test to `TranscriptOverlay` to ensure long exec
outputs are rendered on multiple lines in the overlay.

Why
----
- Previously, the transcript overlay rendered extremely long single exec
lines as one on‑screen row and simply cut them off at the right edge,
with no horizontal scrolling.
- This made it impossible to inspect the full content of long tool/exec
outputs in the transcript view, even though the main TUI view already
wrapped those lines.
- Fixes #7454.

How
----
- Update `ExecCell::transcript_lines` to wrap exec output lines using
the existing `RtOptions`/`word_wrap_line` helpers so that transcript
rendering is width‑aware.
- Reuse the existing line utilities to expand the wrapped `Line` values
into the transcript overlay, preserving styling while respecting the
current viewport width.
- Add `transcript_overlay_wraps_long_exec_output_lines` test in
`pager_overlay.rs` that constructs a long single‑line exec output,
renders the transcript overlay into a small buffer, and asserts that the
long marker string spans multiple rendered lines.
This commit is contained in:
muyuanjin 2025-12-04 08:45:08 +08:00 committed by GitHub
parent 1cfc967eb8
commit 70b97790be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 1 deletions

View file

@ -219,7 +219,12 @@ impl HistoryCell for ExecCell {
if let Some(output) = call.output.as_ref() {
if !call.is_unified_exec_interaction() {
lines.extend(output.formatted_output.lines().map(ansi_escape_line));
let wrap_width = width.max(1) as usize;
let wrap_opts = RtOptions::new(wrap_width);
for unwrapped in output.formatted_output.lines().map(ansi_escape_line) {
let wrapped = word_wrap_line(&unwrapped, wrap_opts.clone());
push_owned_lines(&wrapped, &mut lines);
}
}
let duration = call
.duration

View file

@ -748,6 +748,49 @@ mod tests {
assert_snapshot!("transcript_overlay_apply_patch_scroll_vt100", snapshot);
}
#[test]
fn transcript_overlay_wraps_long_exec_output_lines() {
let marker = "Z";
let long_line = marker.repeat(200);
let mut exec_cell = crate::exec_cell::new_active_exec_command(
"exec-long".into(),
vec!["bash".into(), "-lc".into(), "echo long".into()],
vec![ParsedCommand::Unknown {
cmd: "echo long".into(),
}],
ExecCommandSource::Agent,
None,
false,
);
exec_cell.complete_call(
"exec-long",
CommandOutput {
exit_code: 0,
aggregated_output: format!("{long_line}\n"),
formatted_output: long_line,
},
Duration::from_millis(10),
);
let exec_cell: Arc<dyn HistoryCell> = Arc::new(exec_cell);
let mut overlay = TranscriptOverlay::new(vec![exec_cell]);
let area = Rect::new(0, 0, 20, 10);
let mut buf = Buffer::empty(area);
overlay.render(area, &mut buf);
let rendered = buffer_to_text(&buf, area);
let wrapped_lines = rendered
.lines()
.filter(|line| line.contains(marker))
.count();
assert!(
wrapped_lines >= 2,
"expected long exec output to wrap into multiple lines in transcript overlay, got:\n{rendered}"
);
}
#[test]
fn transcript_overlay_keeps_scroll_pinned_at_bottom() {
let mut overlay = TranscriptOverlay::new(