sidebar: Move to new window, going through sqlite (#53513)

Cameron Mcloughlin created

Re-adds the `multi_workspace::MoveProjectToNewWindow` action

It serializes, then closes, then re-opens the project to avoid issues
with pending tasks

Release Notes:

- N/A or Added/Fixed/Improved ...

Change summary

crates/sidebar/src/sidebar.rs           | 30 ++++++++++++
crates/workspace/src/multi_workspace.rs | 64 +++++++++++++++++++++++++-
crates/workspace/src/workspace.rs       | 10 ++--
3 files changed, 95 insertions(+), 9 deletions(-)

Detailed changes

crates/sidebar/src/sidebar.rs 🔗

@@ -1972,6 +1972,10 @@ impl Sidebar {
                 let multi_workspace = multi_workspace.clone();
                 let project_group_key = project_group_key.clone();
 
+                let has_multiple_projects = multi_workspace
+                    .read_with(cx, |mw, _| mw.project_group_keys().len() >= 2)
+                    .unwrap_or(false);
+
                 let menu =
                     ContextMenu::build_persistent(window, cx, move |menu, _window, menu_cx| {
                         let weak_menu = menu_cx.weak_entity();
@@ -2031,6 +2035,32 @@ impl Sidebar {
                             },
                         );
 
+                        let menu = if project_group_key.host().is_none() && has_multiple_projects {
+                            menu.entry(
+                                "Open Project in New Window",
+                                Some(Box::new(workspace::MoveProjectToNewWindow)),
+                                {
+                                    let project_group_key = project_group_key.clone();
+                                    let multi_workspace = multi_workspace.clone();
+                                    move |window, cx| {
+                                        multi_workspace
+                                            .update(cx, |multi_workspace, cx| {
+                                                multi_workspace
+                                                    .open_project_group_in_new_window(
+                                                        &project_group_key,
+                                                        window,
+                                                        cx,
+                                                    )
+                                                    .detach_and_log_err(cx);
+                                            })
+                                            .ok();
+                                    }
+                                },
+                            )
+                        } else {
+                            menu
+                        };
+
                         let project_group_key = project_group_key.clone();
                         let multi_workspace = multi_workspace.clone();
                         menu.separator()

crates/workspace/src/multi_workspace.rs 🔗

@@ -54,6 +54,8 @@ actions!(
         ShowFewerThreads,
         /// Creates a new thread in the current workspace.
         NewThread,
+        /// Moves the active project to a new window.
+        MoveProjectToNewWindow,
     ]
 );
 
@@ -1017,6 +1019,50 @@ impl MultiWorkspace {
         )
     }
 
+    /// Goes through sqlite: serialize -> close -> open new window
+    /// This avoids issues with pending tasks having the wrong window
+    pub fn open_project_group_in_new_window(
+        &mut self,
+        key: &ProjectGroupKey,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Task<Result<()>> {
+        let paths: Vec<PathBuf> = key.path_list().ordered_paths().cloned().collect();
+        if paths.is_empty() {
+            return Task::ready(Ok(()));
+        }
+
+        let app_state = self.workspace().read(cx).app_state().clone();
+
+        let workspaces: Vec<_> = self
+            .workspaces_for_project_group(key, cx)
+            .unwrap_or_default();
+        let mut serialization_tasks = Vec::new();
+        for workspace in &workspaces {
+            serialization_tasks.push(workspace.update(cx, |workspace, inner_cx| {
+                workspace.flush_serialization(window, inner_cx)
+            }));
+        }
+
+        let remove_task = self.remove_project_group(key, window, cx);
+
+        cx.spawn(async move |_this, cx| {
+            futures::future::join_all(serialization_tasks).await;
+
+            let removed = remove_task.await?;
+            if !removed {
+                return Ok(());
+            }
+
+            cx.update(|cx| {
+                Workspace::new_local(paths, app_state, None, None, None, OpenMode::NewWindow, cx)
+            })
+            .await?;
+
+            Ok(())
+        })
+    }
+
     /// Finds an existing workspace whose root paths and host exactly match.
     pub fn workspace_for_paths(
         &self,
@@ -1836,13 +1882,23 @@ impl Render for MultiWorkspace {
                             sidebar.cycle_thread(true, window, cx);
                         }
                     }))
-                    .on_action(cx.listener(
-                        |this: &mut Self, _: &PreviousThread, window, cx| {
+                    .on_action(
+                        cx.listener(|this: &mut Self, _: &PreviousThread, window, cx| {
                             if let Some(sidebar) = &this.sidebar {
                                 sidebar.cycle_thread(false, window, cx);
                             }
-                        },
-                    ))
+                        }),
+                    )
+                    .when(self.project_group_keys().len() >= 2, |el| {
+                        el.on_action(cx.listener(
+                            |this: &mut Self, _: &MoveProjectToNewWindow, window, cx| {
+                                let key =
+                                    this.project_group_key_for_workspace(this.workspace(), cx);
+                                this.open_project_group_in_new_window(&key, window, cx)
+                                    .detach_and_log_err(cx);
+                            },
+                        ))
+                    })
                 })
                 .when(
                     self.sidebar_open() && self.multi_workspace_enabled(cx),

crates/workspace/src/workspace.rs 🔗

@@ -31,11 +31,11 @@ mod workspace_settings;
 pub use crate::notifications::NotificationFrame;
 pub use dock::Panel;
 pub use multi_workspace::{
-    CloseWorkspaceSidebar, DraggedSidebar, FocusWorkspaceSidebar, MultiWorkspace,
-    MultiWorkspaceEvent, NewThread, NextProject, NextThread, PreviousProject, PreviousThread,
-    ProjectGroup, ProjectGroupKey, SerializedProjectGroupState, ShowFewerThreads, ShowMoreThreads,
-    Sidebar, SidebarEvent, SidebarHandle, SidebarRenderState, SidebarSide, ToggleWorkspaceSidebar,
-    sidebar_side_context_menu,
+    CloseWorkspaceSidebar, DraggedSidebar, FocusWorkspaceSidebar, MoveProjectToNewWindow,
+    MultiWorkspace, MultiWorkspaceEvent, NewThread, NextProject, NextThread, PreviousProject,
+    PreviousThread, ProjectGroup, ProjectGroupKey, SerializedProjectGroupState, ShowFewerThreads,
+    ShowMoreThreads, Sidebar, SidebarEvent, SidebarHandle, SidebarRenderState, SidebarSide,
+    ToggleWorkspaceSidebar, sidebar_side_context_menu,
 };
 pub use path_list::{PathList, SerializedPathList};
 pub use toast_layer::{ToastAction, ToastLayer, ToastView};