Cargo.lock 🔗
@@ -8043,6 +8043,7 @@ name = "recent_projects"
version = "0.1.0"
dependencies = [
"anyhow",
+ "client",
"dev_server_projects",
"editor",
"feature_flags",
gcp-cherry-pick-bot[bot] , Conrad Irwin , and Bennet created
Cherry-picked reconnect ssh (#12147)
Release Notes:
- N/A
---------
Co-authored-by: Bennet <bennet@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Bennet <bennet@zed.dev>
Cargo.lock | 1
crates/recent_projects/Cargo.toml | 1
crates/recent_projects/src/dev_servers.rs | 209 +++++++++++++-------
crates/recent_projects/src/recent_projects.rs | 148 +++++++++-----
4 files changed, 232 insertions(+), 127 deletions(-)
@@ -8043,6 +8043,7 @@ name = "recent_projects"
version = "0.1.0"
dependencies = [
"anyhow",
+ "client",
"dev_server_projects",
"editor",
"feature_flags",
@@ -14,6 +14,7 @@ doctest = false
[dependencies]
anyhow.workspace = true
+client.workspace = true
editor.workspace = true
feature_flags.workspace = true
fuzzy.workspace = true
@@ -1,10 +1,12 @@
use std::time::Duration;
+use anyhow::anyhow;
use anyhow::Context;
use dev_server_projects::{DevServer, DevServerId, DevServerProject, DevServerProjectId};
use editor::Editor;
use feature_flags::FeatureFlagAppExt;
use feature_flags::FeatureFlagViewExt;
+use gpui::AsyncWindowContext;
use gpui::Subscription;
use gpui::Task;
use gpui::WeakView;
@@ -312,91 +314,58 @@ impl DevServerProjects {
});
let workspace = self.workspace.clone();
+ let store = dev_server_projects::Store::global(cx);
cx.spawn({
- let access_token = access_token.clone();
|this, mut cx| async move {
- let result = dev_server.await;
-
- match result {
- Ok(dev_server) => {
- if let Some(ssh_connection_string) = ssh_connection_string {
-
- let access_token = access_token.clone();
- this.update(&mut cx, |this, cx| {
- this.focus_handle.focus(cx);
- this.mode = Mode::CreateDevServer(CreateDevServer {
- creating: true,
- dev_server_id: Some(DevServerId(dev_server.dev_server_id)),
- access_token: Some(access_token.unwrap_or(dev_server.access_token.clone())),
- manual_setup: false,
- });
- cx.notify();
- })?;
- let terminal_panel = workspace
- .update(&mut cx, |workspace, cx| workspace.panel::<TerminalPanel>(cx))
- .ok()
- .flatten()
- .with_context(|| anyhow::anyhow!("No terminal panel"))?;
-
- let command = "sh".to_string();
- let args = vec!["-x".to_string(),"-c".to_string(),
- format!(r#"~/.local/bin/zed -v >/dev/stderr || (curl -sSL https://zed.dev/install.sh || wget -qO- https://zed.dev/install.sh) | bash && ~/.local/bin/zed --dev-server-token {}"#, dev_server.access_token)];
-
- let terminal = terminal_panel.update(&mut cx, |terminal_panel, cx| {
- terminal_panel.spawn_in_new_terminal(
- SpawnInTerminal {
- id: task::TaskId("ssh-remote".into()),
- full_label: "Install zed over ssh".into(),
- label: "Install zed over ssh".into(),
- command,
- args,
- command_label: ssh_connection_string.clone(),
- cwd: Some(TerminalWorkDir::Ssh { ssh_command: ssh_connection_string, path: None }),
- env: Default::default(),
- use_new_terminal: true,
- allow_concurrent_runs: false,
- reveal: RevealStrategy::Always,
- },
- cx,
+ let result = dev_server.await;
+
+ match result {
+ Ok(dev_server) => {
+ if let Some(ssh_connection_string) = ssh_connection_string {
+ spawn_ssh_task(
+ workspace
+ .upgrade()
+ .ok_or_else(|| anyhow!("workspace dropped"))?,
+ store,
+ DevServerId(dev_server.dev_server_id),
+ ssh_connection_string,
+ dev_server.access_token.clone(),
+ &mut cx,
)
- })?.await?;
-
- terminal.update(&mut cx, |terminal, cx| {
- terminal.wait_for_completed_task(cx)
- })?.await;
-
- // There's a race-condition between the task completing successfully, and the server sending us the online status. Make it less likely we'll show the error state.
- if this.update(&mut cx, |this, cx| {
- this.dev_server_store.read(cx).dev_server_status(DevServerId(dev_server.dev_server_id))
- })? == DevServerStatus::Offline {
- cx.background_executor().timer(Duration::from_millis(200)).await
+ .await
+ .log_err();
}
- }
- this.update(&mut cx, |this, cx| {
+ this.update(&mut cx, |this, cx| {
this.focus_handle.focus(cx);
this.mode = Mode::CreateDevServer(CreateDevServer {
creating: false,
dev_server_id: Some(DevServerId(dev_server.dev_server_id)),
access_token: Some(dev_server.access_token),
manual_setup,
- });
+ });
cx.notify();
- })?;
- Ok(())
- }
- Err(e) => {
- this.update(&mut cx, |this, cx| {
- this.mode = Mode::CreateDevServer(CreateDevServer { creating:false, dev_server_id: existing_id, access_token: None, manual_setup });
- cx.notify()
- })
- .log_err();
+ })?;
+ Ok(())
+ }
+ Err(e) => {
+ this.update(&mut cx, |this, cx| {
+ this.mode = Mode::CreateDevServer(CreateDevServer {
+ creating: false,
+ dev_server_id: existing_id,
+ access_token: None,
+ manual_setup,
+ });
+ cx.notify()
+ })
+ .log_err();
- return Err(e)
- }
+ return Err(e);
+ }
+ }
}
- }})
+ })
.detach_and_prompt_err("Failed to create server", cx, |_, _| None);
self.mode = Mode::CreateDevServer(CreateDevServer {
@@ -1021,3 +990,103 @@ impl Render for DevServerProjects {
})
}
}
+
+pub fn reconnect_to_dev_server(
+ workspace: View<Workspace>,
+ dev_server: DevServer,
+ cx: &mut WindowContext,
+) -> Task<anyhow::Result<()>> {
+ let Some(ssh_connection_string) = dev_server.ssh_connection_string else {
+ return Task::ready(Err(anyhow!("can't reconnect, no ssh_connection_string")));
+ };
+ let dev_server_store = dev_server_projects::Store::global(cx);
+ let get_access_token = dev_server_store.update(cx, |store, cx| {
+ store.regenerate_dev_server_token(dev_server.id, cx)
+ });
+
+ cx.spawn(|mut cx| async move {
+ let access_token = get_access_token.await?.access_token;
+
+ spawn_ssh_task(
+ workspace,
+ dev_server_store,
+ dev_server.id,
+ ssh_connection_string.to_string(),
+ access_token,
+ &mut cx,
+ )
+ .await
+ })
+}
+
+pub async fn spawn_ssh_task(
+ workspace: View<Workspace>,
+ dev_server_store: Model<dev_server_projects::Store>,
+ dev_server_id: DevServerId,
+ ssh_connection_string: String,
+ access_token: String,
+ cx: &mut AsyncWindowContext,
+) -> anyhow::Result<()> {
+ let terminal_panel = workspace
+ .update(cx, |workspace, cx| workspace.panel::<TerminalPanel>(cx))
+ .ok()
+ .flatten()
+ .with_context(|| anyhow!("No terminal panel"))?;
+
+ let command = "sh".to_string();
+ let args = vec![
+ "-x".to_string(),
+ "-c".to_string(),
+ format!(
+ r#"~/.local/bin/zed -v >/dev/stderr || (curl -sSL https://zed.dev/install.sh || wget -qO- https://zed.dev/install.sh) | bash && ~/.local/bin/zed --dev-server-token {}"#,
+ access_token
+ ),
+ ];
+
+ let ssh_connection_string = ssh_connection_string.to_string();
+
+ let terminal = terminal_panel
+ .update(cx, |terminal_panel, cx| {
+ terminal_panel.spawn_in_new_terminal(
+ SpawnInTerminal {
+ id: task::TaskId("ssh-remote".into()),
+ full_label: "Install zed over ssh".into(),
+ label: "Install zed over ssh".into(),
+ command,
+ args,
+ command_label: ssh_connection_string.clone(),
+ cwd: Some(TerminalWorkDir::Ssh {
+ ssh_command: ssh_connection_string,
+ path: None,
+ }),
+ env: Default::default(),
+ use_new_terminal: true,
+ allow_concurrent_runs: false,
+ reveal: RevealStrategy::Always,
+ },
+ cx,
+ )
+ })?
+ .await?;
+
+ terminal
+ .update(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
+ .await;
+
+ // There's a race-condition between the task completing successfully, and the server sending us the online status. Make it less likely we'll show the error state.
+ if dev_server_store.update(cx, |this, _| this.dev_server_status(dev_server_id))?
+ == DevServerStatus::Offline
+ {
+ cx.background_executor()
+ .timer(Duration::from_millis(200))
+ .await
+ }
+
+ if dev_server_store.update(cx, |this, _| this.dev_server_status(dev_server_id))?
+ == DevServerStatus::Offline
+ {
+ return Err(anyhow!("couldn't reconnect"))?;
+ }
+
+ Ok(())
+}
@@ -1,5 +1,7 @@
mod dev_servers;
+use client::ProjectId;
+use dev_servers::reconnect_to_dev_server;
pub use dev_servers::DevServerProjects;
use feature_flags::FeatureFlagAppExt;
use fuzzy::{StringMatch, StringMatchCandidate};
@@ -17,6 +19,7 @@ use serde::Deserialize;
use std::{
path::{Path, PathBuf},
sync::Arc,
+ time::Duration,
};
use ui::{
prelude::*, tooltip_container, ButtonLike, IconWithIndicator, Indicator, KeyBinding, ListItem,
@@ -313,73 +316,59 @@ impl PickerDelegate for RecentProjectsDelegate {
}
}
SerializedWorkspaceLocation::DevServer(dev_server_project) => {
- let store = dev_server_projects::Store::global(cx).read(cx);
- let Some(project_id) = store
+ let store = dev_server_projects::Store::global(cx);
+ let Some(project_id) = store.read(cx)
.dev_server_project(dev_server_project.id)
.and_then(|p| p.project_id)
else {
- let dev_server_name = dev_server_project.dev_server_name.clone();
- return cx.spawn(|workspace, mut cx| async move {
- let response =
- cx.prompt(gpui::PromptLevel::Warning,
- "Dev Server is offline",
- Some(format!("Cannot connect to {}. To debug open the remote project settings.", dev_server_name).as_str()),
- &["Ok", "Open Settings"]
- ).await?;
- if response == 1 {
- workspace.update(&mut cx, |workspace, cx| {
- let handle = cx.view().downgrade();
- workspace.toggle_modal(cx, |cx| DevServerProjects::new(cx, handle))
- })?;
- } else {
- workspace.update(&mut cx, |workspace, cx| {
- RecentProjects::open(workspace, true, cx);
- })?;
- }
- Ok(())
- })
- };
- if let Some(app_state) = AppState::global(cx).upgrade() {
- let handle = if replace_current_window {
- cx.window_handle().downcast::<Workspace>()
- } else {
- None
- };
-
- if let Some(handle) = handle {
- cx.spawn(move |workspace, mut cx| async move {
- let continue_replacing = workspace
- .update(&mut cx, |workspace, cx| {
- workspace.
- prepare_to_close(true, cx)
- })?
- .await?;
- if continue_replacing {
- workspace
- .update(&mut cx, |_workspace, cx| {
- workspace::join_dev_server_project(project_id, app_state, Some(handle), cx)
- })?
- .await?;
+ let server = store.read(cx).dev_server_for_project(dev_server_project.id);
+ if server.is_some_and(|server| server.ssh_connection_string.is_some()) {
+ let reconnect = reconnect_to_dev_server(cx.view().clone(), server.unwrap().clone(), cx);
+ let id = dev_server_project.id;
+ return cx.spawn(|workspace, mut cx| async move {
+ reconnect.await?;
+
+ cx.background_executor().timer(Duration::from_millis(1000)).await;
+
+ if let Some(project_id) = store.update(&mut cx, |store, _| {
+ store.dev_server_project(id)
+ .and_then(|p| p.project_id)
+ })? {
+ workspace.update(&mut cx, move |_, cx| {
+ open_dev_server_project(replace_current_window, project_id, cx)
+ })?.await?;
}
- Ok(())
- })
- }
- else {
- let task =
- workspace::join_dev_server_project(project_id, app_state, None, cx);
- cx.spawn(|_, _| async move {
- task.await?;
+ Ok(())
+ })
+ } else {
+ let dev_server_name = dev_server_project.dev_server_name.clone();
+ return cx.spawn(|workspace, mut cx| async move {
+ let response =
+ cx.prompt(gpui::PromptLevel::Warning,
+ "Dev Server is offline",
+ Some(format!("Cannot connect to {}. To debug open the remote project settings.", dev_server_name).as_str()),
+ &["Ok", "Open Settings"]
+ ).await?;
+ if response == 1 {
+ workspace.update(&mut cx, |workspace, cx| {
+ let handle = cx.view().downgrade();
+ workspace.toggle_modal(cx, |cx| DevServerProjects::new(cx, handle))
+ })?;
+ } else {
+ workspace.update(&mut cx, |workspace, cx| {
+ RecentProjects::open(workspace, true, cx);
+ })?;
+ }
Ok(())
})
}
- } else {
- Task::ready(Err(anyhow::anyhow!("App state not found")))
- }
- }
+ };
+ open_dev_server_project(replace_current_window, project_id, cx)
}
}
+ }
})
- .detach_and_log_err(cx);
+ .detach_and_log_err(cx);
cx.emit(DismissEvent);
}
}
@@ -563,6 +552,51 @@ impl PickerDelegate for RecentProjectsDelegate {
}
}
+fn open_dev_server_project(
+ replace_current_window: bool,
+ project_id: ProjectId,
+ cx: &mut ViewContext<Workspace>,
+) -> Task<anyhow::Result<()>> {
+ if let Some(app_state) = AppState::global(cx).upgrade() {
+ let handle = if replace_current_window {
+ cx.window_handle().downcast::<Workspace>()
+ } else {
+ None
+ };
+
+ if let Some(handle) = handle {
+ cx.spawn(move |workspace, mut cx| async move {
+ let continue_replacing = workspace
+ .update(&mut cx, |workspace, cx| {
+ workspace.prepare_to_close(true, cx)
+ })?
+ .await?;
+ if continue_replacing {
+ workspace
+ .update(&mut cx, |_workspace, cx| {
+ workspace::join_dev_server_project(
+ project_id,
+ app_state,
+ Some(handle),
+ cx,
+ )
+ })?
+ .await?;
+ }
+ Ok(())
+ })
+ } else {
+ let task = workspace::join_dev_server_project(project_id, app_state, None, cx);
+ cx.spawn(|_, _| async move {
+ task.await?;
+ Ok(())
+ })
+ }
+ } else {
+ Task::ready(Err(anyhow::anyhow!("App state not found")))
+ }
+}
+
// Compute the highlighted text for the name and path
fn highlights_for_path(
path: &Path,