diff --git a/crates/recent_projects/src/disconnected_overlay.rs b/crates/recent_projects/src/disconnected_overlay.rs index c2492f2d92..bba61757c6 100644 --- a/crates/recent_projects/src/disconnected_overlay.rs +++ b/crates/recent_projects/src/disconnected_overlay.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use dev_server_projects::DevServer; use gpui::{ClickEvent, DismissEvent, EventEmitter, FocusHandle, FocusableView, Render, WeakView}; use remote::SshConnectionOptions; +use settings::Settings; use ui::{ div, h_flex, rems, Button, ButtonCommon, ButtonStyle, Clickable, ElevationIndex, FluentBuilder, Headline, HeadlineSize, IconName, IconPosition, InteractiveElement, IntoElement, Label, Modal, @@ -12,7 +13,7 @@ use workspace::{notifications::DetachAndPromptErr, ModalView, OpenOptions, Works use crate::{ open_dev_server_project, open_ssh_project, remote_servers::reconnect_to_dev_server_project, - RemoteServerProjects, + RemoteServerProjects, SshSettings, }; enum Host { @@ -157,6 +158,16 @@ impl DisconnectedOverlay { let paths = ssh_project.paths.iter().map(PathBuf::from).collect(); cx.spawn(move |_, mut cx| async move { + let nickname = cx + .update(|cx| { + SshSettings::get_global(cx).nickname_for( + &connection_options.host, + connection_options.port, + &connection_options.username, + ) + }) + .ok() + .flatten(); open_ssh_project( connection_options, paths, @@ -165,6 +176,7 @@ impl DisconnectedOverlay { replace_window: Some(window), ..Default::default() }, + nickname, &mut cx, ) .await?; diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index f2e65fbd34..b31bc1b509 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -388,6 +388,7 @@ impl PickerDelegate for RecentProjectsDelegate { }; let args = SshSettings::get_global(cx).args_for(&ssh_project.host, ssh_project.port, &ssh_project.user); + let nickname = SshSettings::get_global(cx).nickname_for(&ssh_project.host, ssh_project.port, &ssh_project.user); let connection_options = SshConnectionOptions { host: ssh_project.host.clone(), username: ssh_project.user.clone(), @@ -399,7 +400,7 @@ impl PickerDelegate for RecentProjectsDelegate { let paths = ssh_project.paths.iter().map(PathBuf::from).collect(); cx.spawn(|_, mut cx| async move { - open_ssh_project(connection_options, paths, app_state, open_options, &mut cx).await + open_ssh_project(connection_options, paths, app_state, open_options, nickname, &mut cx).await }) } } diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 3f601a15f8..0709516ed5 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -87,6 +87,7 @@ impl CreateRemoteServer { struct ProjectPicker { connection_string: SharedString, + nickname: Option, picker: View>, _path_task: Shared>>, } @@ -191,7 +192,7 @@ impl FocusableView for ProjectPicker { impl ProjectPicker { fn new( ix: usize, - connection_string: SharedString, + connection: SshConnectionOptions, project: Model, workspace: WeakView, cx: &mut ViewContext, @@ -208,6 +209,12 @@ impl ProjectPicker { picker.set_query(query, cx); picker }); + let connection_string = connection.connection_string().into(); + let nickname = SshSettings::get_global(cx).nickname_for( + &connection.host, + connection.port, + &connection.username, + ); cx.new_view(|cx| { let _path_task = cx .spawn({ @@ -293,6 +300,7 @@ impl ProjectPicker { _path_task, picker, connection_string, + nickname, } }) } @@ -304,7 +312,7 @@ impl gpui::Render for ProjectPicker { .child( SshConnectionHeader { connection_string: self.connection_string.clone(), - nickname: None, + nickname: self.nickname.clone(), } .render(cx), ) @@ -380,7 +388,7 @@ impl RemoteServerProjects { let mut this = Self::new(cx, workspace.clone()); this.mode = Mode::ProjectPicker(ProjectPicker::new( ix, - connection_options.connection_string().into(), + connection_options, project, workspace, cx, @@ -408,7 +416,7 @@ impl RemoteServerProjects { return; } }; - let ssh_prompt = cx.new_view(|cx| SshPrompt::new(&connection_options, cx)); + let ssh_prompt = cx.new_view(|cx| SshPrompt::new(&connection_options, None, cx)); let connection = connect_over_ssh( connection_options.remote_server_identifier(), @@ -485,10 +493,13 @@ impl RemoteServerProjects { return; }; + let nickname = ssh_connection.nickname.clone(); let connection_options = ssh_connection.into(); workspace.update(cx, |_, cx| { cx.defer(move |workspace, cx| { - workspace.toggle_modal(cx, |cx| SshConnectionModal::new(&connection_options, cx)); + workspace.toggle_modal(cx, |cx| { + SshConnectionModal::new(&connection_options, nickname, cx) + }); let prompt = workspace .active_modal::(cx) .unwrap() @@ -737,11 +748,13 @@ impl RemoteServerProjects { let project = project.clone(); let server = server.clone(); cx.spawn(|_, mut cx| async move { + let nickname = server.nickname.clone(); let result = open_ssh_project( server.into(), project.paths.into_iter().map(PathBuf::from).collect(), app_state, OpenOptions::default(), + nickname, &mut cx, ) .await; diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 845f01c0f0..700c85b31d 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -52,6 +52,23 @@ impl SshSettings { }) .next() } + pub fn nickname_for( + &self, + host: &str, + port: Option, + user: &Option, + ) -> Option { + self.ssh_connections() + .filter_map(|conn| { + if conn.host == host && &conn.username == user && conn.port == port { + Some(conn.nickname) + } else { + None + } + }) + .next() + .flatten() + } } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] @@ -103,6 +120,7 @@ impl Settings for SshSettings { pub struct SshPrompt { connection_string: SharedString, + nickname: Option, status_message: Option, prompt: Option<(View, oneshot::Sender>)>, editor: View, @@ -116,11 +134,13 @@ pub struct SshConnectionModal { impl SshPrompt { pub(crate) fn new( connection_options: &SshConnectionOptions, + nickname: Option, cx: &mut ViewContext, ) -> Self { let connection_string = connection_options.connection_string().into(); Self { connection_string, + nickname, status_message: None, prompt: None, editor: cx.new_view(Editor::single_line), @@ -228,9 +248,13 @@ impl Render for SshPrompt { } impl SshConnectionModal { - pub fn new(connection_options: &SshConnectionOptions, cx: &mut ViewContext) -> Self { + pub(crate) fn new( + connection_options: &SshConnectionOptions, + nickname: Option, + cx: &mut ViewContext, + ) -> Self { Self { - prompt: cx.new_view(|cx| SshPrompt::new(connection_options, cx)), + prompt: cx.new_view(|cx| SshPrompt::new(connection_options, nickname, cx)), finished: false, } } @@ -297,6 +321,7 @@ impl RenderOnce for SshConnectionHeader { impl Render for SshConnectionModal { fn render(&mut self, cx: &mut ui::ViewContext) -> impl ui::IntoElement { + let nickname = self.prompt.read(cx).nickname.clone(); let connection_string = self.prompt.read(cx).connection_string.clone(); let theme = cx.theme(); @@ -313,7 +338,7 @@ impl Render for SshConnectionModal { .child( SshConnectionHeader { connection_string, - nickname: None, + nickname, } .render(cx), ) @@ -589,6 +614,7 @@ pub async fn open_ssh_project( paths: Vec, app_state: Arc, open_options: workspace::OpenOptions, + nickname: Option, cx: &mut AsyncAppContext, ) -> Result<()> { let window = if let Some(window) = open_options.replace_window { @@ -612,9 +638,12 @@ pub async fn open_ssh_project( loop { let delegate = window.update(cx, { let connection_options = connection_options.clone(); + let nickname = nickname.clone(); move |workspace, cx| { cx.activate_window(); - workspace.toggle_modal(cx, |cx| SshConnectionModal::new(&connection_options, cx)); + workspace.toggle_modal(cx, |cx| { + SshConnectionModal::new(&connection_options, nickname.clone(), cx) + }); let ui = workspace .active_modal::(cx) .unwrap() diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index e4d3d7fc5b..dcfe289ca0 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -44,6 +44,7 @@ recent_projects.workspace = true remote.workspace = true rpc.workspace = true serde.workspace = true +settings.workspace = true smallvec.workspace = true story = { workspace = true, optional = true } theme.workspace = true diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 3ffd8555a0..441807c1c4 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -18,8 +18,10 @@ use gpui::{ StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView, }; use project::{Project, RepositoryEntry}; -use recent_projects::{OpenRemote, RecentProjects}; +use recent_projects::{OpenRemote, RecentProjects, SshSettings}; +use remote::SshConnectionOptions; use rpc::proto::{self, DevServerStatus}; +use settings::Settings; use smallvec::SmallVec; use std::sync::Arc; use theme::ActiveTheme; @@ -27,7 +29,7 @@ use ui::{ h_flex, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconName, IconSize, IconWithIndicator, Indicator, PopoverMenu, Tooltip, }; -use util::ResultExt; +use util::{maybe, ResultExt}; use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu}; use workspace::{notifications::NotifyResultExt, Workspace}; @@ -263,7 +265,18 @@ impl TitleBar { } fn render_ssh_project_host(&self, cx: &mut ViewContext) -> Option { - let host = self.project.read(cx).ssh_connection_string(cx)?; + let options = self.project.read(cx).ssh_connection_options(cx)?; + let host: SharedString = options.connection_string().into(); + + let nickname = maybe!({ + SshSettings::get_global(cx) + .ssh_connections + .as_ref()? + .into_iter() + .find(|connection| SshConnectionOptions::from((*connection).clone()) == options) + .and_then(|connection| connection.nickname.clone()) + }) + .unwrap_or_else(|| host.clone()); let (indicator_color, meta) = match self.project.read(cx).ssh_connection_state(cx)? { remote::ConnectionState::Connecting => (Color::Info, format!("Connecting to: {host}")), @@ -295,12 +308,22 @@ impl TitleBar { ButtonLike::new("ssh-server-icon") .child( IconWithIndicator::new( - Icon::new(IconName::Server).color(icon_color), + Icon::new(IconName::Server) + .size(IconSize::XSmall) + .color(icon_color), Some(Indicator::dot().color(indicator_color)), ) .indicator_border_color(Some(cx.theme().colors().title_bar_background)) .into_any_element(), ) + .child( + div() + .max_w_32() + .overflow_hidden() + .truncate() + .text_ellipsis() + .child(Label::new(nickname.clone()).size(LabelSize::Small)), + ) .tooltip(move |cx| { Tooltip::with_meta("Remote Project", Some(&OpenRemote), meta.clone(), cx) }) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9ddb2982ec..5608d84776 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -713,6 +713,16 @@ fn handle_open_request( if let Some(connection_info) = request.ssh_connection { cx.spawn(|mut cx| async move { + let nickname = cx + .update(|cx| { + SshSettings::get_global(cx).nickname_for( + &connection_info.host, + connection_info.port, + &connection_info.username, + ) + }) + .ok() + .flatten(); let paths_with_position = derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await; open_ssh_project( @@ -720,6 +730,7 @@ fn handle_open_request( paths_with_position.into_iter().map(|p| p.path).collect(), app_state, workspace::OpenOptions::default(), + nickname, &mut cx, ) .await @@ -888,6 +899,12 @@ async fn restore_or_create_workspace( }) .ok() .flatten(); + let nickname = cx + .update(|cx| { + SshSettings::get_global(cx).nickname_for(&ssh.host, ssh.port, &ssh.user) + }) + .ok() + .flatten(); let connection_options = SshConnectionOptions { args, host: ssh.host.clone(), @@ -902,6 +919,7 @@ async fn restore_or_create_workspace( ssh.paths.into_iter().map(PathBuf::from).collect(), app_state, workspace::OpenOptions::default(), + nickname, &mut cx, ) .await diff --git a/crates/zed/src/zed/open_listener.rs b/crates/zed/src/zed/open_listener.rs index 64839f4625..bb217d6b16 100644 --- a/crates/zed/src/zed/open_listener.rs +++ b/crates/zed/src/zed/open_listener.rs @@ -437,12 +437,19 @@ async fn open_workspaces( port: ssh.port, password: None, }; + let nickname = cx + .update(|cx| { + SshSettings::get_global(cx).nickname_for(&ssh.host, ssh.port, &ssh.user) + }) + .ok() + .flatten(); cx.spawn(|mut cx| async move { open_ssh_project( connection_options, ssh.paths.into_iter().map(PathBuf::from).collect(), app_state, OpenOptions::default(), + nickname, &mut cx, ) .await