aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorOthavio Quiliao <othavioaqa@gmail.com>2026-04-09 16:04:06 -0300
committerOthavio Quiliao <othavioaqa@gmail.com>2026-04-09 16:04:06 -0300
commit5e711fb7d3b6039afa7d3683d9ef0504f2de9cb2 (patch)
tree76e003f324b2ab27bbddf1194442d0f42c693a10 /src/app
parenta6cbc9765116844744140fe89ef0633b480568cc (diff)
fix: focus pane on left-click even when mouse reporting is active
The Down(Left) handler called forward_pane_mouse_button before focus_pane and early-returned when the forward succeeded, which happens for any pane running a TUI that has enabled mouse reporting (e.g. Claude Code). As a result, clicking such a pane forwarded the click to the inner TUI but never updated herdr's focus state, leaving the previous pane highlighted. Reorder the handler to focus first and forward after, matching the pattern already used by handle_terminal_wheel. Shells and TUIs without mouse reporting (e.g. Codex) were unaffected because their forward returned false and the focus update happened in the fallthrough branch. Add a regression test that installs a pane runtime with mouse reporting enabled via the '\x1b[?1002h' DECSET sequence and asserts that a Down(Left) event retargets focus.
Diffstat (limited to 'src/app')
-rw-r--r--src/app/input.rs76
1 files changed, 71 insertions, 5 deletions
diff --git a/src/app/input.rs b/src/app/input.rs
index cf9c9de9..6a770d06 100644
--- a/src/app/input.rs
+++ b/src/app/input.rs
@@ -2155,6 +2155,11 @@ impl AppState {
return None;
}
} else if let Some(info) = self.pane_at(mouse.column, mouse.row).cloned() {
+ self.focus_pane(info.id);
+ if self.mode != Mode::Terminal {
+ self.mode = Mode::Terminal;
+ }
+
if self.forward_pane_mouse_button(&info, mouse) {
self.selection = None;
return None;
@@ -2170,11 +2175,6 @@ impl AppState {
col,
self.pane_scroll_metrics(info.id),
));
-
- self.focus_pane(info.id);
- if self.mode != Mode::Terminal {
- self.mode = Mode::Terminal;
- }
} else if let Some(info) = self.view.pane_infos.iter().find(|p| {
mouse.column >= p.rect.x
&& mouse.column < p.rect.x + p.rect.width
@@ -4573,4 +4573,70 @@ mod tests {
assert_eq!(wheel_routing(input_state), WheelRouting::HostScroll);
}
+
+ #[tokio::test]
+ async fn clicking_unfocused_pane_with_mouse_reporting_focuses_it_via_left_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::Left),
+ second_info.inner_rect.x + 2,
+ second_info.inner_rect.y + 2,
+ ));
+
+ assert_eq!(
+ app.state.workspaces[0].tabs[0].layout.focused(),
+ second_pane,
+ "left-clicking a pane with mouse reporting should move focus to it"
+ );
+ assert_eq!(app.state.mode, Mode::Terminal);
+ }
+
}