From b580103b956ef9cdf39798947a46ce4e8b78c322 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sun, 19 Jul 2026 04:49:57 +0300 Subject: fix: preserve terminal live follow through resize --- src/pane/terminal.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index 8e33b1b3..2c83f8f2 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -2390,7 +2390,12 @@ fn ghostty_set_scroll_offset_from_bottom( return; }; let max_offset = scrollbar.total.saturating_sub(scrollbar.len); - terminal.scroll_viewport_row(max_offset.saturating_sub(offset_from_bottom)); + let offset_from_bottom = offset_from_bottom.min(max_offset); + if offset_from_bottom == 0 { + terminal.scroll_viewport_bottom(); + } else { + terminal.scroll_viewport_row(max_offset - offset_from_bottom); + } } fn ghostty_extract_selection( @@ -4192,6 +4197,49 @@ mod tests { assert!(pane.visible_text().contains("000000")); } + #[test] + fn empty_or_short_resize_keeps_following_bottom_when_output_creates_scrollback() { + for initial in [b"".as_slice(), b"seed\r\n".as_slice()] { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(10, 3, 100).unwrap(); + terminal.write(initial); + let pane = GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(); + let pane_id = PaneId::from_raw(1); + + pane.resize(3, 10, 0, 0); + pane.process_pty_bytes( + pane_id, + 0, + b"000000\r\n000001\r\n000002\r\n000003\r\n000004", + &tx, + ); + + let metrics = pane.scroll_metrics().expect("scroll metrics after output"); + assert_eq!(metrics.offset_from_bottom, 0); + assert!(pane.visible_text().contains("000004")); + } + } + + #[test] + fn resize_that_removes_scrollback_restores_live_follow() { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(10, 3, 100).unwrap(); + terminal.write(b"000000\r\n000001\r\n000002\r\n000003\r\n000004"); + let pane = GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(); + let pane_id = PaneId::from_raw(1); + + pane.set_scroll_offset_from_bottom(1); + pane.resize(5, 10, 0, 0); + let resized = pane.scroll_metrics().expect("scroll metrics after resize"); + assert_eq!(resized.max_offset_from_bottom, 0); + + pane.process_pty_bytes(pane_id, 0, b"\r\n000005\r\n000006", &tx); + + let metrics = pane.scroll_metrics().expect("scroll metrics after output"); + assert_eq!(metrics.offset_from_bottom, 0); + assert!(pane.visible_text().contains("000006")); + } + #[test] fn detection_text_stays_at_bottom_when_viewport_is_scrolled() { let (tx, _rx) = mpsc::channel(4); -- cgit v1.2.3