diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index ae193c99f1b2135f55069ef670a7ee058ba15fb3..e8a16a20f722dd69488348fa944df3527c6806ee 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -1931,7 +1931,7 @@ impl RecentProjectsDelegate { .workspaces() .find(|ws| ws.read(cx).database_id() == Some(workspace_id)); if let Some(workspace) = workspace { - multi_workspace.remove(&workspace, window, cx); + multi_workspace.remove_group(&workspace, window, cx); } }) .log_err(); diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index d6ffe5aed70ff434818bf3ee9e3541eb350a9723..9e75b17a45dafe594cf37a5c2a9a56878892638a 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -412,7 +412,7 @@ impl Sidebar { this.subscribe_to_workspace(workspace, window, cx); this.update_entries(cx); } - MultiWorkspaceEvent::WorkspaceRemoved(_) => { + MultiWorkspaceEvent::WorkspaceRemoved => { this.update_entries(cx); } }, @@ -1592,7 +1592,7 @@ impl Sidebar { if let Some(mw) = multi_workspace_for_worktree.upgrade() { let ws = workspace_for_remove_worktree.clone(); mw.update(cx, |multi_workspace, cx| { - multi_workspace.remove(&ws, window, cx); + multi_workspace.remove_group(&ws, window, cx); }); } } else { @@ -1665,7 +1665,7 @@ impl Sidebar { if let Some(mw) = multi_workspace_for_remove.upgrade() { let ws = workspace_for_remove.clone(); mw.update(cx, |multi_workspace, cx| { - multi_workspace.remove(&ws, window, cx); + multi_workspace.remove_group(&ws, window, cx); }); } }) diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index c60a8ab42706e008f29eddbb356d52b70e32650e..86e6029e406343ebfdac7472a426454f25c4cc18 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -516,7 +516,7 @@ async fn test_workspace_lifecycle(cx: &mut TestAppContext) { // Remove the second workspace multi_workspace.update_in(cx, |mw, window, cx| { let workspace = mw.workspaces().nth(1).unwrap().clone(); - mw.remove(&workspace, window, cx); + mw.remove_group(&workspace, window, cx); }); cx.run_until_parked(); @@ -5061,7 +5061,7 @@ mod property_test { Operation::RemoveWorkspace { index } => { let removed = multi_workspace.update_in(cx, |mw, window, cx| { let workspace = mw.workspaces().nth(index).unwrap().clone(); - mw.remove(&workspace, window, cx) + mw.remove_group(&workspace, window, cx) }); if removed { state.workspace_paths.remove(index); diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index e6e5d51c3c24bf512380d836a5ca95c1d09bb8dc..376e44bd1fabf32429d5c1d99727aab61b1a7c9c 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -6,7 +6,6 @@ use gpui::{ actions, deferred, px, }; use project::DisableAiSettings; -#[cfg(any(test, feature = "test-support"))] use project::Project; use remote::RemoteConnectionOptions; use settings::Settings; @@ -88,7 +87,7 @@ pub fn sidebar_side_context_menu( pub enum MultiWorkspaceEvent { ActiveWorkspaceChanged, WorkspaceAdded(Entity), - WorkspaceRemoved(EntityId), + WorkspaceRemoved, } pub enum SidebarEvent { @@ -270,6 +269,9 @@ pub struct MultiWorkspace { _subscriptions: Vec, } +/// Represents a group of workspaces with the same project key (main worktree paths and host). +/// +/// Invariant: a project group always has at least one workspace. struct ProjectGroup { key: ProjectGroupKey, workspaces: Vec>, @@ -582,13 +584,12 @@ impl MultiWorkspace { let old_workspace = std::mem::replace(&mut self.active_workspace, workspace.clone()); - let old_entity_id = old_workspace.entity_id(); self.detach_workspace(&old_workspace, cx); Self::subscribe_to_workspace(&workspace, window, cx); self.sync_sidebar_to_workspace(&workspace, cx); - cx.emit(MultiWorkspaceEvent::WorkspaceRemoved(old_entity_id)); + cx.emit(MultiWorkspaceEvent::WorkspaceRemoved); cx.emit(MultiWorkspaceEvent::WorkspaceAdded(workspace)); cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged); self.serialize(cx); @@ -902,43 +903,55 @@ impl MultiWorkspace { }) } - pub fn remove( + /// Removes the group that contains this workspace. + pub fn remove_group( &mut self, workspace: &Entity, window: &mut Window, cx: &mut Context, ) -> bool { - let all_workspaces: Vec<_> = self.workspaces().collect(); - if all_workspaces.len() <= 1 { - return false; - } - - let Some(group) = self + let Some(group_ix) = self .project_groups .iter_mut() - .find(|g| g.workspaces.contains(workspace)) + .position(|g| g.workspaces.contains(workspace)) else { return false; }; - group.workspaces.retain(|w| w != workspace); - // Remove empty groups. - self.project_groups.retain(|g| !g.workspaces.is_empty()); + let removed_group = self.project_groups.remove(group_ix); // If we removed the active workspace, pick a new one. - if self.active_workspace == *workspace { - let workspace = self - .workspaces() - .next() - .expect("there is always at least one workspace after the len() > 1 check"); - self.active_workspace = workspace; + let app_state = workspace.read(cx).app_state().clone(); + if removed_group.workspaces.contains(&self.active_workspace) { + let workspace = self.workspaces().next(); + if let Some(workspace) = workspace { + self.active_workspace = workspace; + } else { + let project = Project::local( + app_state.client.clone(), + app_state.node_runtime.clone(), + app_state.user_store.clone(), + app_state.languages.clone(), + app_state.fs.clone(), + None, + project::LocalProjectFlags::default(), + cx, + ); + let empty_workspace = + cx.new(|cx| Workspace::new(None, project, app_state, window, cx)); + self.project_groups + .push(ProjectGroup::from_workspace(empty_workspace.clone(), cx)); + self.active_workspace = empty_workspace; + } } - self.detach_workspace(workspace, cx); + for workspace in removed_group.workspaces { + self.detach_workspace(&workspace, cx); + } self.serialize(cx); self.focus_active_workspace(window, cx); - cx.emit(MultiWorkspaceEvent::WorkspaceRemoved(workspace.entity_id())); + cx.emit(MultiWorkspaceEvent::WorkspaceRemoved); cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged); cx.notify(); @@ -952,7 +965,7 @@ impl MultiWorkspace { cx: &mut Context, ) { let workspace = workspace.clone(); - if !self.remove(&workspace, window, cx) { + if !self.remove_group(&workspace, window, cx) { return; } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 2ef6e7a0d0e15282963029cbe0a867b8edaf7b64..0ce27990fa7d6efae230d008bbf0dd8260f4ad73 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -2561,7 +2561,7 @@ mod tests { .nth(1) .expect("no workspace at index 1") .clone(); - mw.remove(&ws, window, cx); + mw.remove_group(&ws, window, cx); }); cx.run_until_parked(); @@ -4248,7 +4248,7 @@ mod tests { .nth(1) .expect("no workspace at index 1") .clone(); - mw.remove(&ws, window, cx); + mw.remove_group(&ws, window, cx); }); cx.run_until_parked(); @@ -4363,7 +4363,7 @@ mod tests { .nth(1) .expect("no workspace at index 1") .clone(); - mw.remove(&ws, window, cx); + mw.remove_group(&ws, window, cx); }); cx.run_until_parked(); @@ -4451,7 +4451,7 @@ mod tests { .nth(1) .expect("no workspace at index 1") .clone(); - mw.remove(&ws, window, cx); + mw.remove_group(&ws, window, cx); }); // Simulate the quit handler pattern: collect flush tasks + pending