diff options
| author | Yifeng He <yfhe.prsn@gmail.com> | 2026-04-28 03:13:53 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-28 13:13:53 +0300 |
| commit | 22d7bef5680a1d55c28ff6246518d96487e95d5e (patch) | |
| tree | 7e793059880f7f5c2aba9afc45cfdf578c4c16e9 /src | |
| parent | b6308ef3bec7ebd46ac830b3d36910171e94edf2 (diff) | |
fix: resize w claude code no flicker (#61)
* fix: mouse resize panel failed with claude-code no flicker
* test: run pane mouse reporting drag tests inside tokio runtime
Switch the new horizontal/vertical drag tests to `#[tokio::test]` so
`PaneRuntime::test_with_screen_bytes` has a reactor when it calls
`tokio::spawn`. CI was panicking with "there is no reactor running".
* test: refresh git state in workspace drop slot test
Workspace::test_new caches the parent repo's git branch at construction
time. On master CI the herdr checkout has ref: refs/heads/master in HEAD,
so the cache is populated and workspace_row_height returns 2. On PR CI
actions/checkout uses a detached merge ref, git_branch returns None, row
height becomes 1, and the drop-slot tie-break flips workspace_drop_index_at_row(2)
from Some(0) to Some(1). Refresh git state after pointing identity_cwd at
the per-test temp repo so the cache reflects the temp repo's HEAD.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/input/mouse.rs | 164 | ||||
| -rw-r--r-- | src/app/input/sidebar.rs | 2 |
2 files changed, 155 insertions, 11 deletions
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs index 0349d236..79237f52 100644 --- a/src/app/input/mouse.rs +++ b/src/app/input/mouse.rs @@ -361,10 +361,12 @@ impl AppState { return None; } - if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() { - if self.forward_pane_mouse_button(&info, mouse) { - self.selection = None; - return None; + if self.drag.is_none() { + if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() { + if self.forward_pane_mouse_button(&info, mouse) { + self.selection = None; + return None; + } } } @@ -489,13 +491,15 @@ impl AppState { return None; } - if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() { - if self.forward_pane_mouse_button(&info, mouse) { - self.selection = None; - self.workspace_press = None; - self.tab_press = None; - self.drag = None; - return None; + if self.drag.is_none() { + if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() { + if self.forward_pane_mouse_button(&info, mouse) { + self.selection = None; + self.workspace_press = None; + self.tab_press = None; + self.drag = None; + return None; + } } } @@ -1211,6 +1215,144 @@ mod tests { assert_ne!(root_layout_ratio(&before), root_layout_ratio(&after)); } + #[tokio::test] + async fn dragging_vertical_pane_split_still_resizes_when_pane_mouse_reporting_is_enabled() { + 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(Direction::Vertical); + + app.state.workspaces = vec![ws]; + app.state.active = Some(0); + app.state.selected = 0; + + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); + + let pane_infos = app.state.view.pane_infos.clone(); + let first_info = pane_infos + .iter() + .find(|info| info.id == first_pane) + .expect("first pane info") + .clone(); + let second_info = pane_infos + .iter() + .find(|info| info.id == second_pane) + .expect("second pane info") + .clone(); + + app.state.workspaces[0].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"\x1b[?1002h", + ), + ); + app.state.workspaces[0].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", + ), + ); + + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); + let border = app + .state + .view + .split_borders + .iter() + .find(|border| border.direction == Direction::Vertical) + .expect("vertical split border") + .clone(); + let before = capture_snapshot(&app.state); + let drag_col = border.area.x.saturating_add(1); + + app.handle_mouse(mouse( + MouseEventKind::Down(MouseButton::Left), + drag_col, + border.pos, + )); + app.handle_mouse(mouse( + MouseEventKind::Drag(MouseButton::Left), + drag_col, + border.pos.saturating_add(4), + )); + + let after = capture_snapshot(&app.state); + assert_ne!(root_layout_ratio(&before), root_layout_ratio(&after)); + } + + #[tokio::test] + async fn dragging_horizontal_pane_split_still_resizes_when_pane_mouse_reporting_is_enabled() { + 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(Direction::Horizontal); + + app.state.workspaces = vec![ws]; + app.state.active = Some(0); + app.state.selected = 0; + + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); + + let pane_infos = app.state.view.pane_infos.clone(); + let first_info = pane_infos + .iter() + .find(|info| info.id == first_pane) + .expect("first pane info") + .clone(); + let second_info = pane_infos + .iter() + .find(|info| info.id == second_pane) + .expect("second pane info") + .clone(); + + app.state.workspaces[0].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"\x1b[?1002h", + ), + ); + app.state.workspaces[0].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", + ), + ); + + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); + let border = app + .state + .view + .split_borders + .iter() + .find(|border| border.direction == Direction::Horizontal) + .expect("horizontal split border") + .clone(); + let before = capture_snapshot(&app.state); + let drag_row = border.area.y.saturating_add(1); + + app.handle_mouse(mouse( + MouseEventKind::Down(MouseButton::Left), + border.pos, + drag_row, + )); + app.handle_mouse(mouse( + MouseEventKind::Drag(MouseButton::Left), + border.pos.saturating_add(6), + drag_row, + )); + + let after = capture_snapshot(&app.state); + assert_ne!(root_layout_ratio(&before), root_layout_ratio(&after)); + } + #[test] fn wheel_routing_prefers_mouse_reporting() { let input_state = crate::pane::InputState { diff --git a/src/app/input/sidebar.rs b/src/app/input/sidebar.rs index b806271e..f3ae2b06 100644 --- a/src/app/input/sidebar.rs +++ b/src/app/input/sidebar.rs @@ -1121,6 +1121,7 @@ mod tests { first.tabs[0] .pane_cwds .insert(first_root, first_repo.clone()); + first.refresh_git_ahead_behind(); let mut second = Workspace::test_new("b"); let second_root = second.tabs[0].root_pane; @@ -1128,6 +1129,7 @@ mod tests { second.tabs[0] .pane_cwds .insert(second_root, second_repo.clone()); + second.refresh_git_ahead_behind(); app.state.workspaces = vec![first, second]; crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); |
