Fix scrollback not accumulating for top-aligned scroll regions#33
Fix scrollback not accumulating for top-aligned scroll regions#33L-jasmine wants to merge 1 commit into
Conversation
scroll_up used `!scroll_region_active()` to decide whether a scrolled-off row is pushed to scrollback, dropping the row whenever ANY scroll region was set. Inline TUIs that insert finalized history via SetScrollRegion(1..N) + \r\n (e.g. codex's standard history-insertion mode) therefore never built any scrollback. Per xterm, a line leaving the screen top enters scrollback as long as the scroll region includes the top of the screen (scroll_top==0), regardless of scroll_bottom. Restricted the condition accordingly; full-screen scrolling is unchanged.
…C[6n vt100: patch Grid::scroll_up via [patch.crates-io] -> second-state/vt100-rust @ a34c13a, so a scrolled-off row enters the scrollback when the scroll region includes the screen top (scroll_top == 0). Upstream's `!scroll_region_active()` dropped it whenever any scroll region was set, so codex's inline history insertion (SetScrollRegion(1..viewport_top) + \r\n / reverse-index) never built scrollback — nothing to scroll back to. Upstream PR: doy/vt100-rust#33. pty: only answer the ESC[6n cursor-position report that Windows ConPTY sends in its first output chunk (ConPTY blocks without a reply). Drop the full DA1/DA2/DSR query answering — empirically codex and other TUIs render fine without those replies. Co-Authored-By: Claude <noreply@anthropic.com>
|
I independently reproduced this issue and verified both the diagnosis and the proposed fix. ContextI maintain fleetcom, a PTY supervisor built on this crate. Codex CLI sessions running under fleetcom retained no scrollback. I traced the behavior to the same condition in It then emits one Minimal ReproductionThis reproduces the problem against // Codex-style history insertion: DECSTBM 1..20 on a 24-row screen,
// cursor parked at the region bottom, one \r\n per history line.
let mut p = vt100::Parser::new(24, 80, 10_000);
p.process(b"\x1b[1;20r\x1b[20;1H");
for i in 0..30 {
p.process(format!("\r\nhistory {i}").as_bytes());
}
p.process(b"\x1b[r");
p.screen_mut().set_scrollback(usize::MAX);
assert_eq!(p.screen().scrollback(), 0); // expected 30Stock Real-Session VerificationAs a sanity check, I captured 18 KB of raw output while resuming a long Codex session under a 40×120 PTY. The capture contained:
Replaying the same capture produced the following results:
Emulator BehaviorThe proposed rule is also consistent with other terminal emulators: retain history when the scroll region begins at row 0. The bottom margin is irrelevant.
As a result, a |
|
@ReagentX — thank you for the detailed writeup! The independent reproduction, the real-session capture (0 → 85 rows), and the cross-references to xterm.js / Alacritty / WezTerm are all super valuable. It is great to have the fix validated against a real fleetcom workload, and the Codex case is a strong concrete motivator. As a fun aside: this bug was actually tracked down and the patch written within about half an hour, pairing GLM-5.2 with Claude Code — so it is especially nice to see it hold up under your independent test. |
Problem
Grid::scroll_updiscards scrolled-off rows whenever a scroll region is set, so they never reach the scrollback buffer:scroll_region_active()istruewhenever the region isn't the full screen (scroll_top != 0 || scroll_bottom != rows - 1). So any scroll-region-scoped scrolling — even when the region includes the top of the screen — drops the removed row instead of saving it.Per xterm, a line that leaves the top of the screen should enter the scrollback whenever the scroll region includes the top of the screen (
scroll_top == 0), regardless ofscroll_bottom.How I hit this
Discovered while running OpenAI Codex CLI inside a terminal built on this crate. Codex's inline TUI pushes finalized chat history into the host terminal's scrollback using a top-aligned scroll region —
SetScrollRegion(1..viewport_top)followed by\r\nand reverse-index (ESC M) — seecodex-rs/tui/src/insert_history.rs, and the related discussion in openai/codex#10331. With this crate that history was silently dropped and the scrollback stayed empty — there was nothing to scroll back to.Plain full-screen output (e.g.
cat) is unaffected, since no scroll region is set.Fix
Gate the save on "the region includes the screen top":
scroll_top == 0holds, identical to before).scroll_top == 0,scroll_bottom < last(the inline-TUI case): now saved to scrollback (previously dropped).scroll_top > 0(doesn't reach the screen top): still not saved (lines scroll into the area above the region, not scrollback) — unchanged.The now-unused private helper
scroll_region_activeis removed.Tests
Adds
scrollback_in_top_aligned_regionintests/scroll.rs: sets a1..4scroll region, writes lines with newlines, and asserts 3 rows land in the scrollback. It fails onmainand passes with this change. The fullcargo testsuite is green.🤖 Generated with Claude Code