diff options
| author | Michael Hackner <mhackner@gmail.com> | 2026-08-02 18:08:47 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-03 04:08:47 +0300 |
| commit | eacea2daf0b72973173b728936b27478374f2cd2 (patch) | |
| tree | 4adff75de53a369b453db25d9c134734a34856b5 | |
| parent | 5eab32da81b403c2a277222ab97417a8bf90c35f (diff) | |
fix: page keys scroll pane scrollback at zsh and REPL prompts (#2191)
* fix: page keys scroll pane scrollback at zsh and REPL prompts
PageUp/PageDown were forwarded to the pane whenever DECCKM (application
cursor) was on, assuming only primary-screen pagers enable it, but zsh's
line editor also enables DECCKM, as do REPLs such as python3. The result
was PageUp scrolling shell history instead of Herdr scrollback.
Bracketed paste discriminates the two: it means the app accepts typed or
pasted text at a prompt, so line editors enable it and pagers do not.
* test: update page key state fixture
---------
Co-authored-by: Can Celik <ogulcancelik@gmail.com>
| -rw-r--r-- | src/app/input/terminal.rs | 38 | ||||
| -rw-r--r-- | src/pane/terminal.rs | 22 | ||||
| -rw-r--r-- | src/server/headless.rs | 16 |
3 files changed, 75 insertions, 1 deletions
diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs index 36d60e41..1ba2789c 100644 --- a/src/app/input/terminal.rs +++ b/src/app/input/terminal.rs @@ -1971,4 +1971,42 @@ mod tests { .expect("scroll metrics after PageUp"); assert_eq!(end_metrics.offset_from_bottom, 0); } + + #[tokio::test] + async fn page_up_scrolls_shell_like_decckm_with_bracketed_paste() { + let mut app = app_for_mouse_test(); + let mut ws = Workspace::test_new("test"); + let pane_id = ws.tabs[0].root_pane; + let pane_infos = ws.tabs[0].layout.panes(Rect::new(26, 2, 80, 18)); + let info = pane_infos[0].clone(); + // zsh enables DECCKM via smkx and bracketed paste together; bash/fish do not. + let mut bytes = b"\x1b[?1h\x1b[?2004h".to_vec(); + bytes.extend_from_slice(&numbered_lines_bytes(64)); + let (runtime, mut input_rx) = + crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes( + info.inner_rect.width, + info.inner_rect.height, + 16 * 1024, + &bytes, + 4, + ); + ws.tabs[0].runtimes.insert(pane_id, runtime); + + app.state.workspaces = vec![ws]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + app.state.view.pane_infos = pane_infos; + + app.handle_terminal_key_headless(TerminalKey::new(KeyCode::PageUp, KeyModifiers::empty())); + + assert!( + input_rx.try_recv().is_err(), + "PageUp should not reach the shell" + ); + assert_eq!( + pane_scroll_offset(&app, pane_id), + info.inner_rect.height as usize + ); + } } diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index 438c3037..b90c63f1 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -130,7 +130,11 @@ impl InputState { } pub fn plain_page_keys_use_host_scrollback(self) -> bool { - !self.alternate_screen && !self.mouse_reporting_enabled() && !self.application_cursor + !self.alternate_screen + && !self.mouse_reporting_enabled() + // Bracketed paste distinguishes zsh's line editor (where it's on) + // from e.g. less -X (where it's off). + && (!self.application_cursor || self.bracketed_paste) } } @@ -3073,6 +3077,22 @@ mod tests { use ratatui::{layout::Rect, style::Color}; use tokio::sync::mpsc; + #[test] + fn plain_page_keys_host_scroll_for_shell_like_decckm_with_bracketed_paste() { + assert!(InputState { + alternate_screen: false, + application_cursor: true, + bracketed_paste: true, + focus_reporting: false, + mouse_protocol_mode: crate::input::MouseProtocolMode::None, + mouse_protocol_encoding: crate::input::MouseProtocolEncoding::Default, + mouse_alternate_scroll: false, + modify_other_keys: false, + color_scheme_reporting: false, + } + .plain_page_keys_use_host_scrollback()); + } + fn text_cell(text: &str) -> crate::ghostty::ScreenTextCell { crate::ghostty::ScreenTextCell { wide: crate::ghostty::CellWide::Narrow, diff --git a/src/server/headless.rs b/src/server/headless.rs index ff85f253..f72d1a94 100644 --- a/src/server/headless.rs +++ b/src/server/headless.rs @@ -6469,6 +6469,22 @@ next_tab = "" } #[test] + fn terminal_attach_page_key_host_scrolls_shell_like_decckm_with_bracketed_paste() { + with_terminal_attach_page_key_runtime(b"\x1b[?1h\x1b[?2004h", 0, |runtime, input_rx| { + apply_terminal_attach_page_up(runtime); + + assert_eq!( + runtime + .scroll_metrics() + .expect("scroll metrics") + .offset_from_bottom, + 4 + ); + assert!(input_rx.try_recv().is_err()); + }); + } + + #[test] fn terminal_attach_page_key_forwards_in_alternate_screen_without_mouse_reporting() { with_terminal_attach_page_key_runtime(b"\x1b[?1049h", 3, |runtime, input_rx| { apply_terminal_attach_page_up(runtime); |
