From 42fa2648ee4b98775eee5df3ccf8ecdcbec88fb3 Mon Sep 17 00:00:00 2001 From: Othavio Quiliao Date: Thu, 9 Apr 2026 16:06:27 -0300 Subject: [PATCH] fix: always open pane context menu on right-click The Down(Right) handler forwarded the click to the inner pane runtime when mouse reporting was active, then early-returned before creating the context menu state. For panes running a TUI that enables mouse reporting (e.g. Claude Code), right-clicking focused the pane but never opened the herdr context menu, even though right-click on plain shells or Codex still worked. Stop forwarding Down(Right) to the pane runtime altogether. Right-click on a pane now always focuses it and opens the herdr context menu, matching how terminal emulators like Ghostty, Alacritty, Kitty and iTerm2 treat right-click. This means the previous context menu actions (close pane, split, fullscreen, etc.) now also reach the intended pane via focus_pane before the menu is displayed. Remove Up(Right) and Drag(Right) from the middle/right forward branch so the inner TUI never sees an Up or Drag without a matching Down. Middle button forwarding is preserved because it is still used for paste in many terminal protocols. Tighten the existing right-click regression test with assertions that Mode::ContextMenu is entered and context_menu is populated. Without these extra checks the earlier version of the test only verified focus and failed to catch this exact bug. --- src/app/input.rs | 81 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/src/app/input.rs b/src/app/input.rs index 6a770d06..b2412046 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -2336,10 +2336,7 @@ impl AppState { } } - MouseEventKind::Up(MouseButton::Middle) - | MouseEventKind::Up(MouseButton::Right) - | MouseEventKind::Drag(MouseButton::Middle) - | MouseEventKind::Drag(MouseButton::Right) + MouseEventKind::Up(MouseButton::Middle) | MouseEventKind::Drag(MouseButton::Middle) if !in_sidebar => { if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() { @@ -2440,9 +2437,7 @@ impl AppState { MouseEventKind::Down(MouseButton::Right) if !in_sidebar => { if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() { - if self.forward_pane_mouse_button(&info, mouse) { - return None; - } + self.focus_pane(info.id); self.context_menu = Some(ContextMenuState { kind: ContextMenuKind::Pane, x: mouse.column, @@ -4639,4 +4634,76 @@ mod tests { assert_eq!(app.state.mode, Mode::Terminal); } + #[tokio::test] + async fn clicking_unfocused_pane_with_mouse_reporting_focuses_it_via_right_button() { + let mut app = app_for_mouse_test(); + let mut ws = Workspace::test_new("test"); + let first_pane = ws.tabs[0].root_pane; + let second_pane = ws.test_split(ratatui::layout::Direction::Vertical); + + let terminal_area = Rect::new(26, 2, 80, 18); + let pane_infos = ws.tabs[0].layout.panes(terminal_area); + let first_info = pane_infos + .iter() + .find(|p| p.id == first_pane) + .expect("first pane info") + .clone(); + let second_info = pane_infos + .iter() + .find(|p| p.id == second_pane) + .expect("second pane info") + .clone(); + + ws.tabs[0].runtimes.insert( + first_pane, + crate::pane::PaneRuntime::test_with_screen_bytes( + first_info.inner_rect.width.max(1), + first_info.inner_rect.height.max(1), + b"", + ), + ); + ws.tabs[0].runtimes.insert( + second_pane, + crate::pane::PaneRuntime::test_with_screen_bytes( + second_info.inner_rect.width.max(1), + second_info.inner_rect.height.max(1), + b"\x1b[?1002h", + ), + ); + + ws.tabs[0].layout.focus_pane(first_pane); + + 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_eq!( + app.state.workspaces[0].tabs[0].layout.focused(), + first_pane, + "first pane should be focused before click" + ); + + app.handle_mouse(mouse( + MouseEventKind::Down(MouseButton::Right), + second_info.inner_rect.x + 2, + second_info.inner_rect.y + 2, + )); + + assert_eq!( + app.state.workspaces[0].tabs[0].layout.focused(), + second_pane, + "right-clicking a pane with mouse reporting should move focus to it" + ); + assert_eq!( + app.state.mode, + Mode::ContextMenu, + "right-click should enter ContextMenu mode" + ); + assert!( + app.state.context_menu.is_some(), + "right-click should populate context_menu state" + ); + } }