diff options
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/input/mod.rs | 4 | ||||
| -rw-r--r-- | src/app/mod.rs | 39 | ||||
| -rw-r--r-- | src/app/runtime.rs | 22 | ||||
| -rw-r--r-- | src/app/state.rs | 4 |
4 files changed, 67 insertions, 2 deletions
diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs index a2e6597b..3d20adcd 100644 --- a/src/app/input/mod.rs +++ b/src/app/input/mod.rs @@ -464,7 +464,9 @@ impl App { let Some(bytes) = bytes else { return; }; - rt.scroll_reset(); + if !matches!(mouse.kind, MouseEventKind::Moved) { + rt.scroll_reset(); + } if let Err(err) = rt.try_send_bytes(Bytes::from(bytes)) { warn!(err = %err, kind = ?mouse.kind, "failed to forward popup mouse event"); } diff --git a/src/app/mod.rs b/src/app/mod.rs index c2d479b3..e9aa24b6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -5762,6 +5762,45 @@ last_pane = "prefix+tab" } #[tokio::test] + async fn popup_mouse_motion_preserves_scrollback() { + let mut app = test_app(); + app.state.mode = Mode::Terminal; + app.state.view.terminal_area = ratatui::layout::Rect::new(0, 0, 80, 24); + let (popup_runtime, mut popup_rx) = TerminalRuntime::test_with_channel_and_scrollback_bytes( + 40, + 2, + 1024, + b"one\r\ntwo\r\nthree\r\n\x1b[?1003h\x1b[?1006h", + 4, + ); + popup_runtime.scroll_up(1); + assert!(popup_runtime + .scroll_metrics() + .is_some_and(|metrics| metrics.offset_from_bottom > 0)); + app.install_test_popup_runtime(popup_runtime); + let (_, inner) = + crate::ui::popup_pane_rects(&app.state, app.state.view.terminal_area).unwrap(); + + app.route_client_events( + vec![crate::raw_input::RawInputEvent::Mouse( + crossterm::event::MouseEvent { + kind: crossterm::event::MouseEventKind::Moved, + column: inner.x + 1, + row: inner.y, + modifiers: crossterm::event::KeyModifiers::NONE, + }, + )], + true, + ); + + assert!(popup_rx.try_recv().is_ok()); + assert!(app + .popup_runtime() + .and_then(TerminalRuntime::scroll_metrics) + .is_some_and(|metrics| metrics.offset_from_bottom > 0)); + } + + #[tokio::test] async fn route_client_events_routes_popup_mouse_when_global_capture_is_disabled() { let mut app = test_app(); let mut workspace = Workspace::test_new("tiled"); diff --git a/src/app/runtime.rs b/src/app/runtime.rs index 180c3cef..bd7830b9 100644 --- a/src/app/runtime.rs +++ b/src/app/runtime.rs @@ -167,13 +167,15 @@ impl App { true } crate::raw_input::RawInputEvent::Mouse(mouse) => { + let changes_view = !matches!(mouse.kind, crossterm::event::MouseEventKind::Moved) + || self.state.mode.mouse_motion_changes_view(); if self.state.popup_pane.is_some() || self.state.mouse_capture { self.handle_mouse(mouse); } else { self.state .handle_pane_mouse_only(&self.terminal_runtimes, mouse); } - true + changes_view } crate::raw_input::RawInputEvent::OuterFocusGained => { self.send_outer_focus_event(crate::ghostty::FocusEvent::Gained); @@ -809,6 +811,24 @@ mod tests { } #[tokio::test] + async fn passive_mouse_motion_does_not_request_monolithic_render() { + let (mut app, _) = test_app_with_pane(); + app.state.mode = crate::app::Mode::Terminal; + let motion = || { + crate::raw_input::RawInputEvent::Mouse(crossterm::event::MouseEvent { + kind: crossterm::event::MouseEventKind::Moved, + column: 10, + row: 5, + modifiers: crossterm::event::KeyModifiers::empty(), + }) + }; + + assert!(!app.handle_raw_input_event(motion()).await); + app.state.mode = crate::app::Mode::GlobalMenu; + assert!(app.handle_raw_input_event(motion()).await); + } + + #[tokio::test] async fn raw_input_batch_does_not_start_pending_agent_resume_before_render() { let (mut app, pane_id) = test_app_with_pane(); app.state.ensure_test_terminals(); diff --git a/src/app/state.rs b/src/app/state.rs index a3d15a8b..6069b8a6 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -814,6 +814,10 @@ pub enum Mode { } impl Mode { + pub(crate) fn mouse_motion_changes_view(self) -> bool { + matches!(self, Self::GlobalMenu | Self::ContextMenu | Self::Navigator) + } + /// Whether keys in this mode are commands/navigation (an ASCII input source is wanted) rather /// than free text. This is an explicit **allowlist** of the prefix command/navigation realm: /// any mode NOT listed defaults to leaving the user's IME alone (the safe default), so adding a |
