Reopen windows concurrently (#33784)

Richard Feldman created

Closes #21130

Release Notes:

- Now when Zed reopens windows, they all reopen concurrently instead of
one after another.

Change summary

crates/zed/src/main.rs | 122 ++++++++++++++++++++++++++++++++++---------
1 file changed, 96 insertions(+), 26 deletions(-)

Detailed changes

crates/zed/src/main.rs 🔗

@@ -44,7 +44,10 @@ use theme::{
 use util::{ConnectionResult, ResultExt, TryFutureExt, maybe};
 use uuid::Uuid;
 use welcome::{BaseKeymap, FIRST_OPEN, show_welcome_view};
-use workspace::{AppState, SerializedWorkspaceLocation, WorkspaceSettings, WorkspaceStore};
+use workspace::{
+    AppState, SerializedWorkspaceLocation, Toast, Workspace, WorkspaceSettings, WorkspaceStore,
+    notifications::NotificationId,
+};
 use zed::{
     OpenListener, OpenRequest, RawOpenRequest, app_menus, build_window_options,
     derive_paths_with_position, handle_cli_connection, handle_keymap_file_changes,
@@ -887,40 +890,107 @@ async fn installation_id() -> Result<IdType> {
 
 async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp) -> Result<()> {
     if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
+        let mut tasks = Vec::new();
+
         for location in locations {
             match location {
                 SerializedWorkspaceLocation::Local(location, _) => {
-                    let task = cx.update(|cx| {
-                        workspace::open_paths(
-                            location.paths().as_ref(),
-                            app_state.clone(),
-                            workspace::OpenOptions::default(),
-                            cx,
-                        )
-                    })?;
-                    task.await?;
+                    let app_state = app_state.clone();
+                    let paths = location.paths().to_vec();
+                    let task = cx.spawn(async move |cx| {
+                        let open_task = cx.update(|cx| {
+                            workspace::open_paths(
+                                &paths,
+                                app_state,
+                                workspace::OpenOptions::default(),
+                                cx,
+                            )
+                        })?;
+                        open_task.await.map(|_| ())
+                    });
+                    tasks.push(task);
                 }
                 SerializedWorkspaceLocation::Ssh(ssh) => {
-                    let connection_options = cx.update(|cx| {
-                        SshSettings::get_global(cx)
-                            .connection_options_for(ssh.host, ssh.port, ssh.user)
-                    })?;
                     let app_state = app_state.clone();
-                    cx.spawn(async move |cx| {
-                        recent_projects::open_ssh_project(
-                            connection_options,
-                            ssh.paths.into_iter().map(PathBuf::from).collect(),
-                            app_state,
-                            workspace::OpenOptions::default(),
-                            cx,
-                        )
-                        .await
-                        .log_err();
-                    })
-                    .detach();
+                    let ssh_host = ssh.host.clone();
+                    let task = cx.spawn(async move |cx| {
+                        let connection_options = cx.update(|cx| {
+                            SshSettings::get_global(cx)
+                                .connection_options_for(ssh.host, ssh.port, ssh.user)
+                        });
+
+                        match connection_options {
+                            Ok(connection_options) => recent_projects::open_ssh_project(
+                                connection_options,
+                                ssh.paths.into_iter().map(PathBuf::from).collect(),
+                                app_state,
+                                workspace::OpenOptions::default(),
+                                cx,
+                            )
+                            .await
+                            .map_err(|e| anyhow::anyhow!(e)),
+                            Err(e) => Err(anyhow::anyhow!(
+                                "Failed to get SSH connection options for {}: {}",
+                                ssh_host,
+                                e
+                            )),
+                        }
+                    });
+                    tasks.push(task);
                 }
             }
         }
+
+        // Wait for all workspaces to open concurrently
+        let results = future::join_all(tasks).await;
+
+        // Show notifications for any errors that occurred
+        let mut error_count = 0;
+        for result in results {
+            if let Err(e) = result {
+                log::error!("Failed to restore workspace: {}", e);
+                error_count += 1;
+            }
+        }
+
+        if error_count > 0 {
+            let message = if error_count == 1 {
+                "Failed to restore 1 workspace. Check logs for details.".to_string()
+            } else {
+                format!(
+                    "Failed to restore {} workspaces. Check logs for details.",
+                    error_count
+                )
+            };
+
+            // Try to find an active workspace to show the toast
+            let toast_shown = cx
+                .update(|cx| {
+                    if let Some(window) = cx.active_window() {
+                        if let Some(workspace) = window.downcast::<Workspace>() {
+                            workspace
+                                .update(cx, |workspace, _, cx| {
+                                    workspace.show_toast(
+                                        Toast::new(NotificationId::unique::<()>(), message),
+                                        cx,
+                                    )
+                                })
+                                .ok();
+                            return true;
+                        }
+                    }
+                    false
+                })
+                .unwrap_or(false);
+
+            // If we couldn't show a toast (no windows opened successfully),
+            // we've already logged the errors above, so the user can check logs
+            if !toast_shown {
+                log::error!(
+                    "Failed to show notification for window restoration errors, because no workspace windows were available."
+                );
+            }
+        }
     } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
         cx.update(|cx| show_welcome_view(app_state, cx))?.await?;
     } else {