diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-05 21:59:10 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-05 22:06:02 +0300 |
| commit | 556b16b2df7ea2c3e9b8ad13db4ad7370c1ad838 (patch) | |
| tree | 0a6f61701f2c8992c6cd1ee6d61171ef92a92533 /src/app/actions.rs | |
| parent | ae3c88ab8c64931740d89623f688f7d3f408da5e (diff) | |
feat: add pane navigation controls
refs #421
refs #330
related discussions:
https://github.com/ogulcancelik/herdr/discussions/422
https://github.com/ogulcancelik/herdr/discussions/438
https://github.com/ogulcancelik/herdr/discussions/442
Diffstat (limited to 'src/app/actions.rs')
| -rw-r--r-- | src/app/actions.rs | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs index 9bce43ed..8124be93 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -1371,6 +1371,41 @@ impl AppState { } } + pub fn swap_pane(&mut self, direction: NavDirection) -> bool { + let Some(ws_idx) = self.active else { + return false; + }; + let Some(tab) = self.workspaces.get(ws_idx).and_then(|ws| ws.active_tab()) else { + return false; + }; + let panes = if tab.zoomed { + tab.layout.panes(self.view.terminal_area) + } else { + self.view.pane_infos.clone() + }; + + let Some(focused) = panes.iter().find(|p| p.is_focused) else { + return false; + }; + let Some(target) = find_in_direction(focused, direction, &panes) else { + return false; + }; + let source = focused.id; + let Some(tab) = self + .workspaces + .get_mut(ws_idx) + .and_then(|ws| ws.active_tab_mut()) + else { + return false; + }; + if tab.layout.swap_panes(source, target) { + self.mark_session_dirty(); + true + } else { + false + } + } + pub fn resize_pane(&mut self, direction: NavDirection) { if let Some(first) = self.view.pane_infos.first() { let area = self @@ -4093,6 +4128,91 @@ mod tests { } #[test] + fn swap_pane_direction_preserves_focus_and_swaps_layout_cells() { + let mut state = app_with_workspaces(&["test"]); + let root = state.workspaces[0].tabs[0].root_pane; + let right = state.workspaces[0].test_split(Direction::Horizontal); + state.workspaces[0].layout.focus_pane(root); + crate::ui::compute_view(&mut state, ratatui::layout::Rect::new(0, 0, 100, 20)); + let before_root_rect = state + .view + .pane_infos + .iter() + .find(|info| info.id == root) + .unwrap() + .rect; + let before_right_rect = state + .view + .pane_infos + .iter() + .find(|info| info.id == right) + .unwrap() + .rect; + + assert!(state.swap_pane(NavDirection::Right)); + crate::ui::compute_view(&mut state, ratatui::layout::Rect::new(0, 0, 100, 20)); + + assert_eq!(state.workspaces[0].focused_pane_id(), Some(root)); + assert_eq!( + state + .view + .pane_infos + .iter() + .find(|info| info.id == root) + .unwrap() + .rect, + before_right_rect + ); + assert_eq!( + state + .view + .pane_infos + .iter() + .find(|info| info.id == right) + .unwrap() + .rect, + before_root_rect + ); + } + + #[test] + fn swap_pane_direction_stays_zoomed_and_mutates_hidden_layout() { + let mut state = app_with_workspaces(&["test"]); + let root = state.workspaces[0].tabs[0].root_pane; + let right = state.workspaces[0].test_split(Direction::Horizontal); + state.workspaces[0].layout.focus_pane(root); + state.workspaces[0].zoomed = true; + crate::ui::compute_view(&mut state, ratatui::layout::Rect::new(0, 0, 100, 20)); + + assert!(state.swap_pane(NavDirection::Right)); + crate::ui::compute_view(&mut state, ratatui::layout::Rect::new(0, 0, 100, 20)); + + assert!(state.workspaces[0].zoomed); + assert_eq!(state.workspaces[0].focused_pane_id(), Some(root)); + assert_eq!(state.view.pane_infos.len(), 1); + assert_eq!(state.view.pane_infos[0].id, root); + + state.workspaces[0].zoomed = false; + crate::ui::compute_view(&mut state, ratatui::layout::Rect::new(0, 0, 100, 20)); + let root_rect = state + .view + .pane_infos + .iter() + .find(|info| info.id == root) + .unwrap() + .rect; + let right_rect = state + .view + .pane_infos + .iter() + .find(|info| info.id == right) + .unwrap() + .rect; + + assert!(root_rect.x > right_rect.x); + } + + #[test] fn close_pane_removes_from_workspace() { let mut state = app_with_workspaces(&["test"]); state.workspaces[0].test_split(Direction::Horizontal); |
