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.
This commit is contained in:
Othavio Quiliao 2026-04-09 16:04:06 -03:00
parent a6cbc97651
commit 5e711fb7d3
1 changed files with 71 additions and 5 deletions

View File

@ -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);
}
}