From 8be5ed22f6e01089cfa76f622fc07019642df24f Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Tue, 1 Apr 2025 14:05:28 +0530 Subject: [PATCH] workspace: Fix SSH remote restore on second open + Fix panel not opening automatically on new SSH remote (#27830) Closes #26902 - We used to serialize SSH remote only when opened via recent entries, and not on first time. This broke restore, when opening same folder for second time from recent entries. Once opened for second time, restoring used to. work correctly. This PR fixes this by serializing when opened for first time. - We didn't handle window replace post worktree creation in first time flow, this resulted in project panel not opening automatically like it does with recent entries, or local projects. This PR fixes it by following same flow as recent entries. Release Notes: - Fixed SSH remote not restoring when opening for second time. - Fixed project panel not opening when opening new SSH remote folder. --- crates/recent_projects/src/remote_servers.rs | 55 +++--- crates/recent_projects/src/ssh_connections.rs | 2 +- crates/workspace/src/workspace.rs | 174 +++++++++++------- 3 files changed, 137 insertions(+), 94 deletions(-) diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 392ee3048c..30671289ef 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -32,7 +32,10 @@ use util::ResultExt; use workspace::OpenOptions; use workspace::Toast; use workspace::notifications::NotificationId; -use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr}; +use workspace::{ + ModalView, Workspace, notifications::DetachAndPromptErr, + open_ssh_project_with_existing_connection, +}; use crate::OpenRemote; use crate::ssh_connections::RemoteSettingsContent; @@ -157,13 +160,8 @@ impl ProjectPicker { let app_state = workspace .update(cx, |workspace, _| workspace.app_state().clone()) .ok()?; - let options = cx - .update(|_, cx| (app_state.build_window_options)(None, cx)) - .log_err()?; - - cx.open_window(options, |window, cx| { - window.activate_window(); + cx.update(|_, cx| { let fs = app_state.fs.clone(); update_settings_file::(fs, cx, { let paths = paths @@ -180,32 +178,27 @@ impl ProjectPicker { } } }); - - let tasks = paths - .into_iter() - .map(|path| { - project.update(cx, |project, cx| { - project.find_or_create_worktree(&path, true, cx) - }) - }) - .collect::>(); - window - .spawn(cx, async move |_| { - for task in tasks { - task.await?; - } - Ok(()) - }) - .detach_and_prompt_err("Failed to open path", window, cx, |_, _, _| { - None - }); - - cx.new(|cx| { - telemetry::event!("SSH Project Created"); - Workspace::new(None, project.clone(), app_state.clone(), window, cx) - }) }) .log_err(); + + let options = cx + .update(|_, cx| (app_state.build_window_options)(None, cx)) + .log_err()?; + let window = cx + .open_window(options, |window, cx| { + cx.new(|cx| { + telemetry::event!("SSH Project Created"); + Workspace::new(None, project.clone(), app_state.clone(), window, cx) + }) + }) + .log_err()?; + + open_ssh_project_with_existing_connection( + connection, project, paths, app_state, window, cx, + ) + .await + .log_err(); + this.update(cx, |_, cx| { cx.emit(DismissEvent); }) diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 988f5880e0..846aab2985 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -599,7 +599,7 @@ pub async fn open_ssh_project( let did_open_ssh_project = cx .update(|cx| { - workspace::open_ssh_project( + workspace::open_ssh_project_with_new_connection( window, connection_options.clone(), cancel_rx, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8c0c33808b..843bbd5786 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -6279,7 +6279,7 @@ pub fn create_and_open_local_file( }) } -pub fn open_ssh_project( +pub fn open_ssh_project_with_new_connection( window: WindowHandle, connection_options: SshConnectionOptions, cancel_rx: oneshot::Receiver<()>, @@ -6320,70 +6320,120 @@ pub fn open_ssh_project( ) })?; - let toolchains = DB.toolchains(workspace_id).await?; - for (toolchain, worktree_id, path) in toolchains { - project - .update(cx, |this, cx| { - this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx) - })? - .await; - } - let mut project_paths_to_open = vec![]; - let mut project_path_errors = vec![]; - - for path in paths { - let result = cx - .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))? - .await; - match result { - Ok((_, project_path)) => { - project_paths_to_open.push((path.clone(), Some(project_path))); - } - Err(error) => { - project_path_errors.push(error); - } - }; - } - - if project_paths_to_open.is_empty() { - return Err(project_path_errors - .pop() - .unwrap_or_else(|| anyhow!("no paths given"))); - } - - cx.update_window(window.into(), |_, window, cx| { - window.replace_root(cx, |window, cx| { - telemetry::event!("SSH Project Opened"); - - let mut workspace = - Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx); - workspace.set_serialized_ssh_project(serialized_ssh_project); - workspace - }); - })?; - - window - .update(cx, |_, window, cx| { - window.activate_window(); - - open_items(serialized_workspace, project_paths_to_open, window, cx) - })? - .await?; - - window.update(cx, |workspace, _, cx| { - for error in project_path_errors { - if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist { - if let Some(path) = error.error_tag("path") { - workspace.show_error(&anyhow!("'{path}' does not exist"), cx) - } - } else { - workspace.show_error(&error, cx) - } - } - }) + open_ssh_project_inner( + project, + paths, + serialized_ssh_project, + workspace_id, + serialized_workspace, + app_state, + window, + cx, + ) + .await }) } +pub fn open_ssh_project_with_existing_connection( + connection_options: SshConnectionOptions, + project: Entity, + paths: Vec, + app_state: Arc, + window: WindowHandle, + cx: &mut AsyncApp, +) -> Task> { + cx.spawn(async move |cx| { + let (serialized_ssh_project, workspace_id, serialized_workspace) = + serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?; + + open_ssh_project_inner( + project, + paths, + serialized_ssh_project, + workspace_id, + serialized_workspace, + app_state, + window, + cx, + ) + .await + }) +} + +async fn open_ssh_project_inner( + project: Entity, + paths: Vec, + serialized_ssh_project: SerializedSshProject, + workspace_id: WorkspaceId, + serialized_workspace: Option, + app_state: Arc, + window: WindowHandle, + cx: &mut AsyncApp, +) -> Result<()> { + let toolchains = DB.toolchains(workspace_id).await?; + for (toolchain, worktree_id, path) in toolchains { + project + .update(cx, |this, cx| { + this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx) + })? + .await; + } + let mut project_paths_to_open = vec![]; + let mut project_path_errors = vec![]; + + for path in paths { + let result = cx + .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))? + .await; + match result { + Ok((_, project_path)) => { + project_paths_to_open.push((path.clone(), Some(project_path))); + } + Err(error) => { + project_path_errors.push(error); + } + }; + } + + if project_paths_to_open.is_empty() { + return Err(project_path_errors + .pop() + .unwrap_or_else(|| anyhow!("no paths given"))); + } + + cx.update_window(window.into(), |_, window, cx| { + window.replace_root(cx, |window, cx| { + telemetry::event!("SSH Project Opened"); + + let mut workspace = + Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx); + workspace.set_serialized_ssh_project(serialized_ssh_project); + workspace + }); + })?; + + window + .update(cx, |_, window, cx| { + window.activate_window(); + open_items(serialized_workspace, project_paths_to_open, window, cx) + })? + .await?; + + window.update(cx, |workspace, _, cx| { + for error in project_path_errors { + if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist { + if let Some(path) = error.error_tag("path") { + workspace.show_error(&anyhow!("'{path}' does not exist"), cx) + } + } else { + workspace.show_error(&error, cx) + } + } + })?; + + Ok(()) +} + fn serialize_ssh_project( connection_options: SshConnectionOptions, paths: Vec,