Allow to reuse windows in open remote projects dialogue (#32138)
Closes https://github.com/zed-industries/zed/issues/26276 Same as other "open window" actions like "open recent", add a `"create_new_window": false` (default `false`) argument into the `projects::OpenRemote` action. Make all menus to use this default; allow users to change this in the keybindings. Same as with other actions, `cmd`/`ctrl` inverts the parameter value. <img width="554" alt="default" src="https://github.com/user-attachments/assets/156d50f0-6511-47b3-b650-7a5133ae9541" /> <img width="552" alt="override" src="https://github.com/user-attachments/assets/cf7d963b-86a3-4925-afec-fdb5414418e1" /> Release Notes: - Allowed to reuse windows in open remote projects dialogue
This commit is contained in:
parent
274a40b7e0
commit
9d533f9d30
7 changed files with 82 additions and 25 deletions
|
@ -512,14 +512,14 @@
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"alt-open": ["projects::OpenRecent", { "create_new_window": false }],
|
||||||
// Change the default action on `menu::Confirm` by setting the parameter
|
// Change the default action on `menu::Confirm` by setting the parameter
|
||||||
// "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }],
|
// "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }],
|
||||||
"alt-open": ["projects::OpenRecent", { "create_new_window": false }],
|
|
||||||
"alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": false }],
|
"alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": false }],
|
||||||
"alt-shift-open": "projects::OpenRemote",
|
"alt-shift-open": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
|
||||||
"alt-ctrl-shift-o": "projects::OpenRemote",
|
|
||||||
// Change to open path modal for existing remote connection by setting the parameter
|
// Change to open path modal for existing remote connection by setting the parameter
|
||||||
// "alt-ctrl-shift-o": "["projects::OpenRemote", { "from_existing_connection": true }]",
|
// "alt-ctrl-shift-o": "["projects::OpenRemote", { "from_existing_connection": true }]",
|
||||||
|
"alt-ctrl-shift-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
|
||||||
"alt-ctrl-shift-b": "branches::OpenRecent",
|
"alt-ctrl-shift-b": "branches::OpenRecent",
|
||||||
"alt-shift-enter": "toast::RunAction",
|
"alt-shift-enter": "toast::RunAction",
|
||||||
"ctrl-~": "workspace::NewTerminal",
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
|
|
|
@ -585,8 +585,8 @@
|
||||||
// Change the default action on `menu::Confirm` by setting the parameter
|
// Change the default action on `menu::Confirm` by setting the parameter
|
||||||
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
|
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
|
||||||
"alt-cmd-o": ["projects::OpenRecent", { "create_new_window": false }],
|
"alt-cmd-o": ["projects::OpenRecent", { "create_new_window": false }],
|
||||||
"ctrl-cmd-o": "projects::OpenRemote",
|
"ctrl-cmd-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
|
||||||
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true }],
|
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true, "create_new_window": false }],
|
||||||
"alt-cmd-b": "branches::OpenRecent",
|
"alt-cmd-b": "branches::OpenRecent",
|
||||||
"ctrl-~": "workspace::NewTerminal",
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
"cmd-s": "workspace::Save",
|
"cmd-s": "workspace::Save",
|
||||||
|
|
|
@ -50,6 +50,7 @@ pub fn init(cx: &mut App) {
|
||||||
});
|
});
|
||||||
cx.on_action(|open_remote: &OpenRemote, cx| {
|
cx.on_action(|open_remote: &OpenRemote, cx| {
|
||||||
let from_existing_connection = open_remote.from_existing_connection;
|
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| {
|
with_active_or_new_workspace(cx, move |workspace, window, cx| {
|
||||||
if from_existing_connection {
|
if from_existing_connection {
|
||||||
cx.propagate();
|
cx.propagate();
|
||||||
|
@ -58,7 +59,7 @@ pub fn init(cx: &mut App) {
|
||||||
let handle = cx.entity().downgrade();
|
let handle = cx.entity().downgrade();
|
||||||
let fs = workspace.project().read(cx).fs().clone();
|
let fs = workspace.project().read(cx).fs().clone();
|
||||||
workspace.toggle_modal(window, cx, |window, cx| {
|
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(
|
.key_binding(KeyBinding::for_action(
|
||||||
&OpenRemote {
|
&OpenRemote {
|
||||||
from_existing_connection: false,
|
from_existing_connection: false,
|
||||||
|
create_new_window: false,
|
||||||
},
|
},
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -488,6 +490,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
OpenRemote {
|
OpenRemote {
|
||||||
from_existing_connection: false,
|
from_existing_connection: false,
|
||||||
|
create_new_window: false,
|
||||||
}
|
}
|
||||||
.boxed_clone(),
|
.boxed_clone(),
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -13,6 +13,7 @@ use futures::FutureExt;
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use futures::future::Shared;
|
use futures::future::Shared;
|
||||||
use futures::select;
|
use futures::select;
|
||||||
|
use gpui::ClickEvent;
|
||||||
use gpui::ClipboardItem;
|
use gpui::ClipboardItem;
|
||||||
use gpui::Subscription;
|
use gpui::Subscription;
|
||||||
use gpui::Task;
|
use gpui::Task;
|
||||||
|
@ -69,6 +70,7 @@ pub struct RemoteServerProjects {
|
||||||
retained_connections: Vec<Entity<SshRemoteClient>>,
|
retained_connections: Vec<Entity<SshRemoteClient>>,
|
||||||
ssh_config_updates: Task<()>,
|
ssh_config_updates: Task<()>,
|
||||||
ssh_config_servers: BTreeSet<SharedString>,
|
ssh_config_servers: BTreeSet<SharedString>,
|
||||||
|
create_new_window: bool,
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +138,7 @@ impl Focusable for ProjectPicker {
|
||||||
|
|
||||||
impl ProjectPicker {
|
impl ProjectPicker {
|
||||||
fn new(
|
fn new(
|
||||||
|
create_new_window: bool,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
connection: SshConnectionOptions,
|
connection: SshConnectionOptions,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
@ -167,7 +170,13 @@ impl ProjectPicker {
|
||||||
let fs = workspace.project().read(cx).fs().clone();
|
let fs = workspace.project().read(cx).fs().clone();
|
||||||
let weak = cx.entity().downgrade();
|
let weak = cx.entity().downgrade();
|
||||||
workspace.toggle_modal(window, cx, |window, cx| {
|
workspace.toggle_modal(window, cx, |window, cx| {
|
||||||
RemoteServerProjects::new(fs, window, cx, weak)
|
RemoteServerProjects::new(
|
||||||
|
create_new_window,
|
||||||
|
fs,
|
||||||
|
window,
|
||||||
|
weak,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.log_err()?;
|
.log_err()?;
|
||||||
|
@ -361,19 +370,12 @@ impl Mode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl RemoteServerProjects {
|
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(
|
pub fn new(
|
||||||
|
create_new_window: bool,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
let mut read_ssh_config = SshSettings::get_global(cx).read_ssh_config;
|
let mut read_ssh_config = SshSettings::get_global(cx).read_ssh_config;
|
||||||
|
@ -410,11 +412,13 @@ impl RemoteServerProjects {
|
||||||
retained_connections: Vec::new(),
|
retained_connections: Vec::new(),
|
||||||
ssh_config_updates,
|
ssh_config_updates,
|
||||||
ssh_config_servers: BTreeSet::new(),
|
ssh_config_servers: BTreeSet::new(),
|
||||||
|
create_new_window,
|
||||||
_subscription,
|
_subscription,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project_picker(
|
pub fn project_picker(
|
||||||
|
create_new_window: bool,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
connection_options: remote::SshConnectionOptions,
|
connection_options: remote::SshConnectionOptions,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
@ -424,8 +428,9 @@ impl RemoteServerProjects {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let fs = project.read(cx).fs().clone();
|
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(
|
this.mode = Mode::ProjectPicker(ProjectPicker::new(
|
||||||
|
create_new_window,
|
||||||
ix,
|
ix,
|
||||||
connection_options,
|
connection_options,
|
||||||
project,
|
project,
|
||||||
|
@ -541,6 +546,7 @@ impl RemoteServerProjects {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let create_new_window = self.create_new_window;
|
||||||
let connection_options = ssh_connection.into();
|
let connection_options = ssh_connection.into();
|
||||||
workspace.update(cx, |_, cx| {
|
workspace.update(cx, |_, cx| {
|
||||||
cx.defer_in(window, move |workspace, window, cx| {
|
cx.defer_in(window, move |workspace, window, cx| {
|
||||||
|
@ -578,7 +584,7 @@ impl RemoteServerProjects {
|
||||||
let weak = cx.entity().downgrade();
|
let weak = cx.entity().downgrade();
|
||||||
let fs = workspace.project().read(cx).fs().clone();
|
let fs = workspace.project().read(cx).fs().clone();
|
||||||
workspace.toggle_modal(window, cx, |window, cx| {
|
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();
|
let weak = cx.entity().downgrade();
|
||||||
workspace.toggle_modal(window, cx, |window, cx| {
|
workspace.toggle_modal(window, cx, |window, cx| {
|
||||||
RemoteServerProjects::project_picker(
|
RemoteServerProjects::project_picker(
|
||||||
|
create_new_window,
|
||||||
ix,
|
ix,
|
||||||
connection_options,
|
connection_options,
|
||||||
project,
|
project,
|
||||||
|
@ -847,6 +854,7 @@ impl RemoteServerProjects {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
|
let create_new_window = self.create_new_window;
|
||||||
let is_from_zed = server.is_from_zed();
|
let is_from_zed = server.is_from_zed();
|
||||||
let element_id_base = SharedString::from(format!("remote-project-{server_ix}"));
|
let element_id_base = SharedString::from(format!("remote-project-{server_ix}"));
|
||||||
let container_element_id_base =
|
let container_element_id_base =
|
||||||
|
@ -854,8 +862,11 @@ impl RemoteServerProjects {
|
||||||
|
|
||||||
let callback = Rc::new({
|
let callback = Rc::new({
|
||||||
let project = project.clone();
|
let project = project.clone();
|
||||||
move |this: &mut Self, window: &mut Window, cx: &mut Context<Self>| {
|
move |remote_server_projects: &mut Self,
|
||||||
let Some(app_state) = this
|
secondary_confirm: bool,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>| {
|
||||||
|
let Some(app_state) = remote_server_projects
|
||||||
.workspace
|
.workspace
|
||||||
.read_with(cx, |workspace, _| workspace.app_state().clone())
|
.read_with(cx, |workspace, _| workspace.app_state().clone())
|
||||||
.log_err()
|
.log_err()
|
||||||
|
@ -865,17 +876,26 @@ impl RemoteServerProjects {
|
||||||
let project = project.clone();
|
let project = project.clone();
|
||||||
let server = server.connection().into_owned();
|
let server = server.connection().into_owned();
|
||||||
cx.emit(DismissEvent);
|
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| {
|
cx.spawn_in(window, async move |_, cx| {
|
||||||
let result = open_ssh_project(
|
let result = open_ssh_project(
|
||||||
server.into(),
|
server.into(),
|
||||||
project.paths.into_iter().map(PathBuf::from).collect(),
|
project.paths.into_iter().map(PathBuf::from).collect(),
|
||||||
app_state,
|
app_state,
|
||||||
OpenOptions::default(),
|
OpenOptions {
|
||||||
|
replace_window,
|
||||||
|
..OpenOptions::default()
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
log::error!("Failed to connect: {:?}", e);
|
log::error!("Failed to connect: {e:#}");
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
gpui::PromptLevel::Critical,
|
gpui::PromptLevel::Critical,
|
||||||
"Failed to connect",
|
"Failed to connect",
|
||||||
|
@ -897,7 +917,13 @@ impl RemoteServerProjects {
|
||||||
.on_action(cx.listener({
|
.on_action(cx.listener({
|
||||||
let callback = callback.clone();
|
let callback = callback.clone();
|
||||||
move |this, _: &menu::Confirm, window, cx| {
|
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(
|
.child(
|
||||||
|
@ -911,7 +937,10 @@ impl RemoteServerProjects {
|
||||||
.size(IconSize::Small),
|
.size(IconSize::Small),
|
||||||
)
|
)
|
||||||
.child(Label::new(project.paths.join(", ")))
|
.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| {
|
.when(is_from_zed, |server_list_item| {
|
||||||
server_list_item.end_hover_slot::<AnyElement>(Some(
|
server_list_item.end_hover_slot::<AnyElement>(Some(
|
||||||
div()
|
div()
|
||||||
|
@ -1493,10 +1522,30 @@ impl RemoteServerProjects {
|
||||||
}
|
}
|
||||||
let mut modal_section = modal_section.render(window, cx).into_any_element();
|
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)
|
Modal::new("remote-projects", None)
|
||||||
.header(
|
.header(
|
||||||
ModalHeader::new()
|
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(
|
||||||
Section::new().padded(false).child(
|
Section::new().padded(false).child(
|
||||||
|
|
|
@ -439,6 +439,7 @@ impl TitleBar {
|
||||||
"Remote Project",
|
"Remote Project",
|
||||||
Some(&OpenRemote {
|
Some(&OpenRemote {
|
||||||
from_existing_connection: false,
|
from_existing_connection: false,
|
||||||
|
create_new_window: false,
|
||||||
}),
|
}),
|
||||||
meta.clone(),
|
meta.clone(),
|
||||||
window,
|
window,
|
||||||
|
@ -449,6 +450,7 @@ impl TitleBar {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
OpenRemote {
|
OpenRemote {
|
||||||
from_existing_connection: false,
|
from_existing_connection: false,
|
||||||
|
create_new_window: false,
|
||||||
}
|
}
|
||||||
.boxed_clone(),
|
.boxed_clone(),
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -73,6 +73,7 @@ pub fn app_menus() -> Vec<Menu> {
|
||||||
MenuItem::action(
|
MenuItem::action(
|
||||||
"Open Remote...",
|
"Open Remote...",
|
||||||
zed_actions::OpenRemote {
|
zed_actions::OpenRemote {
|
||||||
|
create_new_window: false,
|
||||||
from_existing_connection: false,
|
from_existing_connection: false,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -254,6 +254,8 @@ pub struct OpenRecent {
|
||||||
pub struct OpenRemote {
|
pub struct OpenRemote {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub from_existing_connection: bool,
|
pub from_existing_connection: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub create_new_window: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_actions!(projects, [OpenRecent, OpenRemote]);
|
impl_actions!(projects, [OpenRecent, OpenRemote]);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue