aboutsummaryrefslogtreecommitdiff
path: root/src/app/input
diff options
context:
space:
mode:
authorakbash <akbash@herdr.dev>2026-07-14 04:18:18 +0300
committerGitHub <noreply@github.com>2026-07-14 04:18:18 +0300
commit4c9a3f380d221fe02fa8c4d2d9ef94438fa1957f (patch)
tree0a9d03687ddd7cd7a656832b9e0949da3396fe0a /src/app/input
parent9e4fdd6102e6f68e42fbe419d447b758c161f0db (diff)
fix: forward horizontal wheel events to panes (#1402)
refs #1349 Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src/app/input')
-rw-r--r--src/app/input/mouse.rs105
1 files changed, 105 insertions, 0 deletions
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs
index 911e27c0..80a7be6d 100644
--- a/src/app/input/mouse.rs
+++ b/src/app/input/mouse.rs
@@ -941,6 +941,14 @@ impl AppState {
self.handle_terminal_wheel(terminal_runtimes, mouse);
}
+ MouseEventKind::ScrollLeft | MouseEventKind::ScrollRight
+ if self.mode == Mode::Terminal && !in_sidebar =>
+ {
+ if let Some(info) = self.pane_at(mouse.column, mouse.row).cloned() {
+ self.forward_pane_reported_wheel(terminal_runtimes, &info, mouse);
+ }
+ }
+
MouseEventKind::ScrollUp if in_sidebar => {
let agent_area = self.agent_panel_rect();
let over_agent_panel = agent_area != Rect::default()
@@ -1902,6 +1910,103 @@ mod tests {
}
#[tokio::test]
+ async fn mouse_dispatcher_forwards_horizontal_wheel_to_mouse_reporting_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 (runtime, mut input_rx) =
+ crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes(
+ info.inner_rect.width,
+ info.inner_rect.height,
+ 0,
+ b"\x1b[?1000h\x1b[?1006h",
+ 4,
+ );
+ ws.insert_test_runtime(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;
+ assert!(
+ app.state.mouse_capture,
+ "reproduction must use the default Herdr mouse dispatcher"
+ );
+
+ let outer_column = info.inner_rect.x + 2;
+ let outer_row = info.inner_rect.y + 3;
+ for (button, expected_kind, ingress) in [
+ (66, MouseEventKind::ScrollLeft, "monolithic"),
+ (67, MouseEventKind::ScrollRight, "headless"),
+ ] {
+ let input = format!("\x1b[<{button};{};{}M", outer_column + 1, outer_row + 1);
+ let mut events = crate::raw_input::parse_raw_input_bytes_sync(input.as_bytes());
+ let event = events
+ .pop()
+ .expect("horizontal SGR wheel input should parse");
+ let crate::raw_input::RawInputEvent::Mouse(mouse) = &event else {
+ panic!("expected parsed mouse event");
+ };
+ assert!(events.is_empty(), "expected one parsed mouse event");
+ assert_eq!(mouse.kind, expected_kind);
+
+ if ingress == "monolithic" {
+ assert!(app.handle_raw_input_event(event).await);
+ } else {
+ app.route_client_events(vec![event], false);
+ }
+
+ assert_eq!(
+ input_rx
+ .try_recv()
+ .expect("horizontal wheel should reach pane"),
+ Bytes::from(format!("\x1b[<{button};3;4M"))
+ );
+ }
+ assert!(input_rx.try_recv().is_err());
+ }
+
+ #[tokio::test]
+ async fn horizontal_wheel_stays_inert_for_non_mouse_reporting_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 (runtime, mut input_rx) =
+ crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes(
+ info.inner_rect.width,
+ info.inner_rect.height,
+ 0,
+ b"",
+ 1,
+ );
+ ws.insert_test_runtime(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 input = format!(
+ "\x1b[<66;{};{}M",
+ info.inner_rect.x + 3,
+ info.inner_rect.y + 4
+ );
+ let event = crate::raw_input::parse_raw_input_bytes_sync(input.as_bytes())
+ .pop()
+ .expect("horizontal SGR wheel input should parse");
+
+ assert!(app.handle_raw_input_event(event).await);
+
+ assert!(input_rx.try_recv().is_err());
+ }
+
+ #[tokio::test]
async fn configured_right_click_passthrough_forwards_full_gesture_to_pane() {
let mut app = app_for_mouse_test();
let mut ws = Workspace::test_new("test");