aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYifeng He <yfhe.prsn@gmail.com>2026-04-28 03:13:53 -0700
committerGitHub <noreply@github.com>2026-04-28 13:13:53 +0300
commit22d7bef5680a1d55c28ff6246518d96487e95d5e (patch)
tree7e793059880f7f5c2aba9afc45cfdf578c4c16e9 /src
parentb6308ef3bec7ebd46ac830b3d36910171e94edf2 (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.rs164
-rw-r--r--src/app/input/sidebar.rs2
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));