@@ -50,6 +50,7 @@ pub fn init(cx: &mut App) {
});
cx.on_action(|open_remote: &OpenRemote, cx| {
let from_existing_connection = open_remote.from_existing_connection;
+ let create_new_window = open_remote.create_new_window;
with_active_or_new_workspace(cx, move |workspace, window, cx| {
if from_existing_connection {
cx.propagate();
@@ -58,7 +59,7 @@ pub fn init(cx: &mut App) {
let handle = cx.entity().downgrade();
let fs = workspace.project().read(cx).fs().clone();
workspace.toggle_modal(window, cx, |window, cx| {
- RemoteServerProjects::new(fs, window, cx, handle)
+ RemoteServerProjects::new(create_new_window, fs, window, handle, cx)
})
});
});
@@ -480,6 +481,7 @@ impl PickerDelegate for RecentProjectsDelegate {
.key_binding(KeyBinding::for_action(
&OpenRemote {
from_existing_connection: false,
+ create_new_window: false,
},
window,
cx,
@@ -488,6 +490,7 @@ impl PickerDelegate for RecentProjectsDelegate {
window.dispatch_action(
OpenRemote {
from_existing_connection: false,
+ create_new_window: false,
}
.boxed_clone(),
cx,
@@ -13,6 +13,7 @@ use futures::FutureExt;
use futures::channel::oneshot;
use futures::future::Shared;
use futures::select;
+use gpui::ClickEvent;
use gpui::ClipboardItem;
use gpui::Subscription;
use gpui::Task;
@@ -69,6 +70,7 @@ pub struct RemoteServerProjects {
retained_connections: Vec<Entity<SshRemoteClient>>,
ssh_config_updates: Task<()>,
ssh_config_servers: BTreeSet<SharedString>,
+ create_new_window: bool,
_subscription: Subscription,
}
@@ -136,6 +138,7 @@ impl Focusable for ProjectPicker {
impl ProjectPicker {
fn new(
+ create_new_window: bool,
ix: usize,
connection: SshConnectionOptions,
project: Entity<Project>,
@@ -167,7 +170,13 @@ impl ProjectPicker {
let fs = workspace.project().read(cx).fs().clone();
let weak = cx.entity().downgrade();
workspace.toggle_modal(window, cx, |window, cx| {
- RemoteServerProjects::new(fs, window, cx, weak)
+ RemoteServerProjects::new(
+ create_new_window,
+ fs,
+ window,
+ weak,
+ cx,
+ )
});
})
.log_err()?;
@@ -361,19 +370,12 @@ impl Mode {
}
}
impl RemoteServerProjects {
- pub fn open(workspace: Entity<Workspace>, window: &mut Window, cx: &mut App) {
- workspace.update(cx, |workspace, cx| {
- let handle = cx.entity().downgrade();
- let fs = workspace.project().read(cx).fs().clone();
- workspace.toggle_modal(window, cx, |window, cx| Self::new(fs, window, cx, handle))
- })
- }
-
pub fn new(
+ create_new_window: bool,
fs: Arc<dyn Fs>,
window: &mut Window,
- cx: &mut Context<Self>,
workspace: WeakEntity<Workspace>,
+ cx: &mut Context<Self>,
) -> Self {
let focus_handle = cx.focus_handle();
let mut read_ssh_config = SshSettings::get_global(cx).read_ssh_config;
@@ -410,11 +412,13 @@ impl RemoteServerProjects {
retained_connections: Vec::new(),
ssh_config_updates,
ssh_config_servers: BTreeSet::new(),
+ create_new_window,
_subscription,
}
}
pub fn project_picker(
+ create_new_window: bool,
ix: usize,
connection_options: remote::SshConnectionOptions,
project: Entity<Project>,
@@ -424,8 +428,9 @@ impl RemoteServerProjects {
workspace: WeakEntity<Workspace>,
) -> Self {
let fs = project.read(cx).fs().clone();
- let mut this = Self::new(fs, window, cx, workspace.clone());
+ let mut this = Self::new(create_new_window, fs, window, workspace.clone(), cx);
this.mode = Mode::ProjectPicker(ProjectPicker::new(
+ create_new_window,
ix,
connection_options,
project,
@@ -541,6 +546,7 @@ impl RemoteServerProjects {
return;
};
+ let create_new_window = self.create_new_window;
let connection_options = ssh_connection.into();
workspace.update(cx, |_, cx| {
cx.defer_in(window, move |workspace, window, cx| {
@@ -578,7 +584,7 @@ impl RemoteServerProjects {
let weak = cx.entity().downgrade();
let fs = workspace.project().read(cx).fs().clone();
workspace.toggle_modal(window, cx, |window, cx| {
- RemoteServerProjects::new(fs, window, cx, weak)
+ RemoteServerProjects::new(create_new_window, fs, window, weak, cx)
});
});
};
@@ -606,6 +612,7 @@ impl RemoteServerProjects {
let weak = cx.entity().downgrade();
workspace.toggle_modal(window, cx, |window, cx| {
RemoteServerProjects::project_picker(
+ create_new_window,
ix,
connection_options,
project,
@@ -847,6 +854,7 @@ impl RemoteServerProjects {
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
+ let create_new_window = self.create_new_window;
let is_from_zed = server.is_from_zed();
let element_id_base = SharedString::from(format!("remote-project-{server_ix}"));
let container_element_id_base =
@@ -854,8 +862,11 @@ impl RemoteServerProjects {
let callback = Rc::new({
let project = project.clone();
- move |this: &mut Self, window: &mut Window, cx: &mut Context<Self>| {
- let Some(app_state) = this
+ move |remote_server_projects: &mut Self,
+ secondary_confirm: bool,
+ window: &mut Window,
+ cx: &mut Context<Self>| {
+ let Some(app_state) = remote_server_projects
.workspace
.read_with(cx, |workspace, _| workspace.app_state().clone())
.log_err()
@@ -865,17 +876,26 @@ impl RemoteServerProjects {
let project = project.clone();
let server = server.connection().into_owned();
cx.emit(DismissEvent);
+
+ let replace_window = match (create_new_window, secondary_confirm) {
+ (true, false) | (false, true) => None,
+ (true, true) | (false, false) => window.window_handle().downcast::<Workspace>(),
+ };
+
cx.spawn_in(window, async move |_, cx| {
let result = open_ssh_project(
server.into(),
project.paths.into_iter().map(PathBuf::from).collect(),
app_state,
- OpenOptions::default(),
+ OpenOptions {
+ replace_window,
+ ..OpenOptions::default()
+ },
cx,
)
.await;
if let Err(e) = result {
- log::error!("Failed to connect: {:?}", e);
+ log::error!("Failed to connect: {e:#}");
cx.prompt(
gpui::PromptLevel::Critical,
"Failed to connect",
@@ -897,7 +917,13 @@ impl RemoteServerProjects {
.on_action(cx.listener({
let callback = callback.clone();
move |this, _: &menu::Confirm, window, cx| {
- callback(this, window, cx);
+ callback(this, false, window, cx);
+ }
+ }))
+ .on_action(cx.listener({
+ let callback = callback.clone();
+ move |this, _: &menu::SecondaryConfirm, window, cx| {
+ callback(this, true, window, cx);
}
}))
.child(
@@ -911,7 +937,10 @@ impl RemoteServerProjects {
.size(IconSize::Small),
)
.child(Label::new(project.paths.join(", ")))
- .on_click(cx.listener(move |this, _, window, cx| callback(this, window, cx)))
+ .on_click(cx.listener(move |this, e: &ClickEvent, window, cx| {
+ let secondary_confirm = e.down.modifiers.platform;
+ callback(this, secondary_confirm, window, cx)
+ }))
.when(is_from_zed, |server_list_item| {
server_list_item.end_hover_slot::<AnyElement>(Some(
div()
@@ -1493,10 +1522,30 @@ impl RemoteServerProjects {
}
let mut modal_section = modal_section.render(window, cx).into_any_element();
+ let (create_window, reuse_window) = if self.create_new_window {
+ (
+ window.keystroke_text_for(&menu::Confirm),
+ window.keystroke_text_for(&menu::SecondaryConfirm),
+ )
+ } else {
+ (
+ window.keystroke_text_for(&menu::SecondaryConfirm),
+ window.keystroke_text_for(&menu::Confirm),
+ )
+ };
+ let placeholder_text = Arc::from(format!(
+ "{reuse_window} reuses this window, {create_window} opens a new one",
+ ));
+
Modal::new("remote-projects", None)
.header(
ModalHeader::new()
- .child(Headline::new("Remote Projects").size(HeadlineSize::XSmall)),
+ .child(Headline::new("Remote Projects").size(HeadlineSize::XSmall))
+ .child(
+ Label::new(placeholder_text)
+ .color(Color::Muted)
+ .size(LabelSize::XSmall),
+ ),
)
.section(
Section::new().padded(false).child(