From 8843bbb0c4dc4e6b52ca5d0a6592ce80be56a19c Mon Sep 17 00:00:00 2001 From: Can Celik Date: Sun, 26 Jul 2026 02:45:44 +0300 Subject: fix: improve sidebar worktree hierarchy and reordering (#1873) * fix: clarify sidebar worktree hierarchy refs #1694 * feat: reorder worktree groups as a unit refs #1694 --- src/app/actions.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) (limited to 'src/app/actions.rs') diff --git a/src/app/actions.rs b/src/app/actions.rs index 0709ed4c..10d28054 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -1390,6 +1390,73 @@ impl AppState { true } + pub fn move_workspace_block( + &mut self, + workspace_ids: &[String], + before_workspace_id: Option<&str>, + ) -> bool { + let moved_ids = workspace_ids + .iter() + .map(String::as_str) + .collect::>(); + if moved_ids.is_empty() + || moved_ids.len() != workspace_ids.len() + || !workspace_ids + .iter() + .all(|id| self.workspaces.iter().any(|workspace| workspace.id == *id)) + || before_workspace_id.is_some_and(|id| { + moved_ids.contains(id) + || !self.workspaces.iter().any(|workspace| workspace.id == id) + }) + { + return false; + } + + let mut desired_ids = self + .workspaces + .iter() + .filter(|workspace| !moved_ids.contains(workspace.id.as_str())) + .map(|workspace| workspace.id.clone()) + .collect::>(); + let insert_idx = before_workspace_id + .and_then(|id| desired_ids.iter().position(|candidate| candidate == id)) + .unwrap_or(desired_ids.len()); + desired_ids.splice(insert_idx..insert_idx, workspace_ids.iter().cloned()); + if self + .workspaces + .iter() + .map(|workspace| workspace.id.as_str()) + .eq(desired_ids.iter().map(String::as_str)) + { + return false; + } + + let active_id = self.active.map(|idx| self.workspaces[idx].id.clone()); + let selected_id = self + .workspaces + .get(self.selected) + .map(|workspace| workspace.id.clone()); + let desired_positions = desired_ids + .iter() + .enumerate() + .map(|(index, id)| (id.clone(), index)) + .collect::>(); + + self.mark_session_dirty(); + self.workspaces.sort_by_key(|workspace| { + desired_positions + .get(&workspace.id) + .copied() + .unwrap_or(usize::MAX) + }); + self.active = active_id.and_then(|id| self.workspaces.iter().position(|ws| ws.id == id)); + self.selected = selected_id + .and_then(|id| self.workspaces.iter().position(|ws| ws.id == id)) + .unwrap_or(0); + self.ensure_workspace_visible(self.selected); + true + } + pub fn scroll_tabs_left(&mut self) { self.tab_scroll_follow_active = false; self.tab_scroll = self.tab_scroll.saturating_sub(1); @@ -4433,6 +4500,59 @@ mod tests { assert_eq!(names, vec!["b", "c", "a"]); } + #[test] + fn move_workspace_block_collects_non_contiguous_members() { + let mut state = + app_with_workspaces(&["child-one", "normal", "parent", "child-two", "tail"]); + let parent_id = state.workspaces[2].id.clone(); + let child_one_id = state.workspaces[0].id.clone(); + let child_two_id = state.workspaces[3].id.clone(); + let tail_id = state.workspaces[4].id.clone(); + state.active = Some(0); + state.selected = 4; + + assert!(state.move_workspace_block( + &[parent_id, child_one_id.clone(), child_two_id], + Some(&tail_id), + )); + + let names = state + .workspaces + .iter() + .map(|workspace| workspace.display_name()) + .collect::>(); + assert_eq!( + names, + ["normal", "parent", "child-one", "child-two", "tail"] + ); + assert_eq!(state.workspaces[state.active.unwrap()].id, child_one_id); + assert_eq!(state.workspaces[state.selected].id, tail_id); + } + + #[test] + fn move_workspace_block_rejects_invalid_and_noop_orders() { + let mut state = app_with_workspaces(&["a", "b", "c"]); + let ids = state + .workspaces + .iter() + .map(|workspace| workspace.id.clone()) + .collect::>(); + + assert!(!state.move_workspace_block(&[], None)); + assert!(!state.move_workspace_block(&[ids[0].clone(), ids[0].clone()], None)); + assert!(!state.move_workspace_block(&["missing".into()], None)); + assert!(!state.move_workspace_block(&[ids[0].clone()], Some(&ids[0]))); + assert!(!state.move_workspace_block(&[ids[0].clone()], Some(&ids[1]))); + assert_eq!( + state + .workspaces + .iter() + .map(|workspace| workspace.display_name()) + .collect::>(), + ["a", "b", "c"] + ); + } + #[test] fn close_workspace_adjusts_indices() { let mut state = app_with_workspaces(&["a", "b", "c"]); -- cgit v1.2.3