From 31cb03a3960783306fa88498bb29587119353ee5 Mon Sep 17 00:00:00 2001 From: GeekMr <140129782+WilliamWang1721@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:12:10 +0800 Subject: [PATCH] feat(ssh): add host duplicate context action --- .../src/gpui_shell/ssh_settings/library.rs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/nebula_app/src/gpui_shell/ssh_settings/library.rs b/nebula_app/src/gpui_shell/ssh_settings/library.rs index 28072c6..8c7badd 100644 --- a/nebula_app/src/gpui_shell/ssh_settings/library.rs +++ b/nebula_app/src/gpui_shell/ssh_settings/library.rs @@ -121,6 +121,39 @@ impl SettingsPane { self.ssh_library.reset_scroll(); } + fn duplicate_ssh_host( + &mut self, + host: String, + window: &mut Window, + cx: &mut Context, + ) { + let label = self + .ssh_hosts + .profiles + .for_destination(&host) + .label + .filter(|label| !label.trim().is_empty()) + .unwrap_or_else(|| host.clone()); + let label = self.ssh_hosts.profiles.next_default_label(label.trim()); + + self.open_ssh_editor(Some(host), window, cx); + if let Some(editor) = self.ssh_editor.as_mut() { + editor.original_destination = None; + } + self.ssh_destination_input + .update(cx, |input, cx| input.set_value("", window, cx)); + self.ssh_label_input + .update(cx, |input, cx| input.set_value(label, window, cx)); + self.ssh_password_input.update(cx, |input, cx| { + input.set_placeholder( + crate::gpui_shell::config::ui_language(cx) + .pick("留空则连接时询问", "Leave empty to ask when connecting"), + window, + cx, + ); + }); + } + fn filtered_library_hosts(&self, cx: &gpui::App) -> Vec { let query = self.ssh_library.search.read(cx).value(); let mut hosts = if self.ssh_library.scope == HostScope::Recent { @@ -421,6 +454,8 @@ impl SettingsPane { // 行首 OS 图标(旧壳裁定 2026-08-09):id 取自 ssh_profiles 存储, // 未认出回落通用终端形状;mono 字体渲染 Nerd Font 字位。 let os_icon = crate::display::ui::os_icons::resolve(icons.get(&host).map(String::as_str)); + let context_owner = cx.entity().downgrade(); + let context_host = host.clone(); let connect_host = host.clone(); let edit_host = host.clone(); let pin_host = host.clone(); @@ -443,6 +478,64 @@ impl SettingsPane { .border_color(theme.border) .bg(theme.secondary.opacity(0.25)) .hover(move |row| row.bg(hover_bg)) + .context_menu(move |menu, _, _| { + let connect_owner = context_owner.clone(); + let edit_owner = context_owner.clone(); + let duplicate_owner = context_owner.clone(); + let delete_owner = context_owner.clone(); + let connect_host = context_host.clone(); + let edit_host = context_host.clone(); + let duplicate_host = context_host.clone(); + let delete_host = context_host.clone(); + menu.item( + PopupMenuItem::new(language.text(Message::LauncherConnect)).on_click( + move |_, _, cx| { + let _ = connect_owner.update(cx, |this, cx| { + cx.emit(SettingsPaneEvent::LaunchSsh(connect_host.clone())); + this.ssh_status = + Some(SshStatus::Opening(connect_host.clone())); + cx.notify(); + }); + }, + ), + ) + .item( + PopupMenuItem::new(language.text(Message::LauncherEdit)).on_click( + move |_, window, cx| { + let _ = edit_owner.update(cx, |this, cx| { + this.open_ssh_editor( + Some(edit_host.clone()), + window, + cx, + ); + }); + }, + ), + ) + .item( + PopupMenuItem::new(language.text(Message::CommonCopy)).on_click( + move |_, window, cx| { + let _ = duplicate_owner.update(cx, |this, cx| { + this.duplicate_ssh_host( + duplicate_host.clone(), + window, + cx, + ); + }); + }, + ), + ) + .item( + PopupMenuItem::new(language.text(Message::LauncherDelete)).on_click( + move |_, _, cx| { + let _ = delete_owner.update(cx, |this, cx| { + this.ssh_delete_confirm = Some(delete_host.clone()); + cx.notify(); + }); + }, + ), + ) + }) .child( crate::gpui_shell::widgets::device_icon_container(cx) .id(SharedString::from(format!("ssh-host-icon-{ix}")))