diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index dadbd394bbf9b5..59c2c895cddcb6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1217,7 +1217,10 @@ impl Project { server.ssh_connection_string.is_some() } - pub fn ssh_connection_string(&self, cx: &ModelContext) -> Option { + pub fn ssh_connection_string(&self, cx: &AppContext) -> Option { + if let Some(ssh_state) = &self.ssh_client { + return Some(ssh_state.connection_string().into()); + } let dev_server_id = self.dev_server_project_id()?; dev_server_projects::Store::global(cx) .read(cx) @@ -1226,6 +1229,10 @@ impl Project { .clone() } + pub fn ssh_is_connected(&self) -> Option { + Some(!self.ssh_client.as_ref()?.is_reconnect_underway()) + } + pub fn replica_id(&self) -> ReplicaId { match self.client_state { ProjectClientState::Remote { replica_id, .. } => replica_id, diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index d0fffc031f0bff..1aff16a4a44f71 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -317,6 +317,7 @@ impl SshClientDelegate { if release_channel == ReleaseChannel::Dev && platform.arch == std::env::consts::ARCH && platform.os == std::env::consts::OS + && false { use smol::process::{Command, Stdio}; diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index 6bca9938baac71..89ec5db949aa70 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -36,6 +36,7 @@ use std::{ time::Instant, }; use tempfile::TempDir; +use util::maybe; #[derive( Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize, @@ -48,7 +49,7 @@ pub struct SshSocket { socket_path: PathBuf, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct SshConnectionOptions { pub host: String, pub username: Option, @@ -250,6 +251,7 @@ struct SshRemoteClientState { pub struct SshRemoteClient { client: Arc, inner_state: Mutex>, + connection_options: SshConnectionOptions, } impl SshRemoteClient { @@ -265,6 +267,7 @@ impl SshRemoteClient { let this = Arc::new(Self { client, inner_state: Mutex::new(None), + connection_options: connection_options.clone(), }); let inner_state = { @@ -272,8 +275,7 @@ impl SshRemoteClient { ChannelForwarder::new(incoming_tx, outgoing_rx, cx); let (ssh_connection, ssh_process) = - Self::establish_connection(connection_options.clone(), delegate.clone(), cx) - .await?; + Self::establish_connection(connection_options, delegate.clone(), cx).await?; let multiplex_task = Self::multiplex( Arc::downgrade(&this), @@ -505,6 +507,13 @@ impl SshRemoteClient { self.client.clone().into() } + pub fn connection_string(&self) -> String { + self.connection_options.connection_string() + } + + pub fn is_reconnect_underway(&self) -> bool { + maybe!({ Some(self.inner_state.try_lock()?.is_none()) }).unwrap_or_default() + } #[cfg(any(test, feature = "test-support"))] pub fn fake( client_cx: &mut gpui::TestAppContext, @@ -519,6 +528,7 @@ impl SshRemoteClient { Arc::new(Self { client, inner_state: Mutex::new(None), + connection_options: SshConnectionOptions::default(), }) }), server_cx.update(|cx| ChannelClient::new(client_to_server_rx, server_to_client_tx, cx)), diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index d6cc839cfdb7bc..81f908ce797902 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -18,7 +18,7 @@ use gpui::{ StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView, }; use project::{Project, RepositoryEntry}; -use recent_projects::RecentProjects; +use recent_projects::{OpenRemote, RecentProjects}; use rpc::proto::{self, DevServerStatus}; use smallvec::SmallVec; use std::sync::Arc; @@ -262,6 +262,46 @@ impl TitleBar { self } + fn render_ssh_project_host(&self, cx: &mut ViewContext) -> Option { + let host = self.project.read(cx).ssh_connection_string(cx)?; + let meta = SharedString::from(format!("Connected to: {host}")); + let indicator_color = if self.project.read(cx).ssh_is_connected()? { + Color::Success + } else { + Color::Warning + }; + let indicator = div() + .absolute() + .w_1_4() + .h_1_4() + .right_0p5() + .bottom_0p5() + .p_1() + .rounded_2xl() + .bg(indicator_color.color(cx)); + + Some( + div() + .child( + IconButton::new("ssh-server-icon", IconName::Server) + .tooltip(move |cx| { + Tooltip::with_meta( + "Remote Project", + Some(&OpenRemote), + meta.clone(), + cx, + ) + }) + .shape(ui::IconButtonShape::Square) + .on_click(|_, cx| { + cx.dispatch_action(OpenRemote.boxed_clone()); + }), + ) + .child(indicator) + .into_any_element(), + ) + } + pub fn render_project_host(&self, cx: &mut ViewContext) -> Option { if let Some(dev_server) = self.project @@ -296,6 +336,9 @@ impl TitleBar { .into_any_element(), ); } + if self.project.read(cx).is_via_ssh() { + return self.render_ssh_project_host(cx); + } if self.project.read(cx).is_disconnected() { return Some(