Detailed changes
@@ -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?;
@@ -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
})
}
}
@@ -87,6 +87,7 @@ impl CreateRemoteServer {
struct ProjectPicker {
connection_string: SharedString,
+ nickname: Option<SharedString>,
picker: View<Picker<OpenPathDelegate>>,
_path_task: Shared<Task<Option<()>>>,
}
@@ -191,7 +192,7 @@ impl FocusableView for ProjectPicker {
impl ProjectPicker {
fn new(
ix: usize,
- connection_string: SharedString,
+ connection: SshConnectionOptions,
project: Model<Project>,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<RemoteServerProjects>,
@@ -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::<SshConnectionModal>(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;
@@ -52,6 +52,23 @@ impl SshSettings {
})
.next()
}
+ pub fn nickname_for(
+ &self,
+ host: &str,
+ port: Option<u16>,
+ user: &Option<String>,
+ ) -> Option<SharedString> {
+ 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<SharedString>,
status_message: Option<SharedString>,
prompt: Option<(View<Markdown>, oneshot::Sender<Result<String>>)>,
editor: View<Editor>,
@@ -116,11 +134,13 @@ pub struct SshConnectionModal {
impl SshPrompt {
pub(crate) fn new(
connection_options: &SshConnectionOptions,
+ nickname: Option<SharedString>,
cx: &mut ViewContext<Self>,
) -> 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>) -> Self {
+ pub(crate) fn new(
+ connection_options: &SshConnectionOptions,
+ nickname: Option<SharedString>,
+ cx: &mut ViewContext<Self>,
+ ) -> 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<Self>) -> 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<PathBuf>,
app_state: Arc<AppState>,
open_options: workspace::OpenOptions,
+ nickname: Option<SharedString>,
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::<SshConnectionModal>(cx)
.unwrap()
@@ -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
@@ -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<Self>) -> Option<AnyElement> {
- 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)
})
@@ -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
@@ -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