From 6ea68f9af2569ea87b7d243c5bbe4f3bc4c8fa44 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Fri, 3 Jul 2026 21:50:34 +0300 Subject: fix: forward page keys to primary-screen pagers refs #953 --- src/app/input/terminal.rs | 56 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 5 deletions(-) (limited to 'src/app/input') diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs index b7a23322..282fa263 100644 --- a/src/app/input/terminal.rs +++ b/src/app/input/terminal.rs @@ -88,17 +88,19 @@ impl App { self.state .runtime_for_pane_in_workspace(&self.terminal_runtimes, ws_idx, pane_id)?; - // Intercept plain PageUp/PageDown presses for pane scrollback when the - // focused pane doesn't handle its own scrolling (e.g., a plain shell - // with mouse off). Modified page keys are pane shortcuts, and release - // events should not produce a second host-scroll action. + // Intercept plain PageUp/PageDown presses for pane scrollback only + // when the focused pane looks like a shell transcript. Normal-screen + // pagers such as `less -X` keep the primary screen but enter + // application cursor mode while they own special keys. + // Modified page keys are pane shortcuts, and release events should not + // produce a second host-scroll action. // Only intercept when we know the pane state; if input_state is unknown, // fail-open and forward the key to the pane. if matches!(key_event.code, KeyCode::PageUp | KeyCode::PageDown) && key_event.modifiers.is_empty() { if let Some(input_state) = rt.input_state() { - if !input_state.alternate_screen && !input_state.mouse_reporting_enabled() { + if input_state.plain_page_keys_use_host_scrollback() { if key_event.kind == crossterm::event::KeyEventKind::Release { return None; } @@ -1191,4 +1193,48 @@ mod tests { // Forwarded to pane, so test runtime doesn't process it — scroll stays at bottom. assert_eq!(end_metrics.offset_from_bottom, 0); } + + #[tokio::test] + async fn page_up_forwarded_to_primary_screen_application_cursor_pane() { + 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(); + let mut bytes = b"\x1b[?1h".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; + + let start_metrics = app + .state + .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id) + .and_then(crate::terminal::TerminalRuntime::scroll_metrics) + .expect("initial scroll metrics"); + assert_eq!(start_metrics.offset_from_bottom, 0); + + app.handle_terminal_key_headless(TerminalKey::new(KeyCode::PageUp, KeyModifiers::empty())); + + let forwarded = input_rx.try_recv().expect("forwarded PageUp"); + assert_eq!(forwarded.as_ref(), b"\x1b[5~"); + let end_metrics = app + .state + .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id) + .and_then(crate::terminal::TerminalRuntime::scroll_metrics) + .expect("scroll metrics after PageUp"); + assert_eq!(end_metrics.offset_from_bottom, 0); + } } -- cgit v1.2.3