From 3577d7a0e582fe34d5e9f647df2b0cd7ac63f013 Mon Sep 17 00:00:00 2001 From: ZiChuanShanFeng <270144683+WMU-Demo@users.noreply.github.com> Date: Thu, 24 Sep 2026 00:28:44 +0800 Subject: [PATCH] feat(launcher): show configured SSH host names in launcher rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new-tab launcher, Quick Jump and the command-palette SSH rows used to render the raw destination (user@host) as their label, ignoring the user-configured "主机名称" (host name) from SSH settings. - Add ssh_host_labels() alongside ssh_host_icon_ids() and pass the label map into shell_palette_rows(). - Share a single ssh_host_display() rule across the launcher, Quick Jump and palette rows: a named host shows its alias as the label with the real destination as the metadata hint; unnamed hosts keep the previous label=destination, hint="SSH" presentation. - Search now matches both the alias and the destination. Tests: launcher rows with and without a configured name, including the existing no-label fallback. --- nebula_app/src/gpui_shell/workspace.rs | 10 +++++ .../workspace/launcher_menu/tests.rs | 1 + .../src/gpui_shell/workspace/palette.rs | 13 ++++-- .../src/gpui_shell/workspace/quick_jump.rs | 10 +++-- .../src/gpui_shell/workspace/shell_picker.rs | 24 +++++++++-- nebula_app/src/gpui_shell/workspace/tests.rs | 40 ++++++++++++++++++- 6 files changed, 88 insertions(+), 10 deletions(-) diff --git a/nebula_app/src/gpui_shell/workspace.rs b/nebula_app/src/gpui_shell/workspace.rs index 818accb..567d976 100644 --- a/nebula_app/src/gpui_shell/workspace.rs +++ b/nebula_app/src/gpui_shell/workspace.rs @@ -636,6 +636,15 @@ fn ssh_host_icon_ids(data_dir: &Path) -> std::collections::HashMap std::collections::HashMap { + crate::ssh_profiles::SshProfiles::load(&data_dir.join("ssh_profiles.json")) + .map(|profiles| profiles.labels()) + .unwrap_or_default() +} + /// 标签的用户可编辑元数据:重命名与色标(旧壳 `TabEntry::custom_name` / /// `custom_color` 的对应物,字段与共享 session v4 的 `TabSession` 同名同义, /// 所以导出/恢复不需要转换层)。 @@ -2229,6 +2238,7 @@ impl NebulaWorkspace { .map(|store| store.as_config_profiles()) .unwrap_or_default(), crate::gpui_shell::ssh_hosts::SshHostLists::load().merged(), + &ssh_host_labels(&crate::display::nebula_data_dir()), &default_shell_id, language, window.scale_factor().max(0.5), diff --git a/nebula_app/src/gpui_shell/workspace/launcher_menu/tests.rs b/nebula_app/src/gpui_shell/workspace/launcher_menu/tests.rs index 7fe6cc3..3a4d0d1 100644 --- a/nebula_app/src/gpui_shell/workspace/launcher_menu/tests.rs +++ b/nebula_app/src/gpui_shell/workspace/launcher_menu/tests.rs @@ -38,6 +38,7 @@ fn open(cx: &mut TestAppContext) -> (Entity, VisualTestContext) vec![shell("cmd"), shell("pwsh")], vec![], ["test-host".into()], + &std::collections::HashMap::new(), "cmd", workspace_ui_language(), 1.0, diff --git a/nebula_app/src/gpui_shell/workspace/palette.rs b/nebula_app/src/gpui_shell/workspace/palette.rs index 6fe7e0d..b4ad9e1 100644 --- a/nebula_app/src/gpui_shell/workspace/palette.rs +++ b/nebula_app/src/gpui_shell/workspace/palette.rs @@ -99,6 +99,7 @@ impl NebulaWorkspace { // 启动器混排(旧壳 ⌘K 裁定):SSH 主机与命令同列,置顶/隐藏 // 次序由共享 merge 权威裁定。 let ssh_icons = ssh_host_icon_ids(&crate::display::nebula_data_dir()); + let ssh_labels = ssh_host_labels(&crate::display::nebula_data_dir()); rows.extend( crate::gpui_shell::ssh_hosts::SshHostLists::load().merged().into_iter().map( |host| { @@ -106,13 +107,19 @@ impl NebulaWorkspace { ssh_icons.get(&host).map(String::as_str), ) .glyph; + let (label, hint) = shell_picker::ssh_host_display( + ssh_labels.get(&host).map(String::as_str), + &host, + ); + let search = format!("{label} {host} ssh host remote lianjie 连接") + .to_lowercase(); WorkspacePaletteRow { group_order: usize::MAX, group: language.pick("SSH 主机", "SSH HOSTS").to_owned(), - label: host.clone(), - hint: "SSH".to_owned(), + label, + hint, hint_style: WorkspacePaletteHintStyle::Metadata, - search: format!("{host} ssh host remote lianjie 连接").to_lowercase(), + search, action: WorkspacePaletteAction::LaunchSshHost(host), icon: None, icon_glyph: Some(glyph), diff --git a/nebula_app/src/gpui_shell/workspace/quick_jump.rs b/nebula_app/src/gpui_shell/workspace/quick_jump.rs index 6417753..4ab3d5d 100644 --- a/nebula_app/src/gpui_shell/workspace/quick_jump.rs +++ b/nebula_app/src/gpui_shell/workspace/quick_jump.rs @@ -142,19 +142,23 @@ fn directory_rows(language: crate::display::UiLanguage) -> Vec Vec { let group = language.pick("SSH 主机", "SSH hosts"); let icons = ssh_host_icon_ids(&crate::display::nebula_data_dir()); + let labels = ssh_host_labels(&crate::display::nebula_data_dir()); crate::gpui_shell::ssh_hosts::SshHostLists::load() .merged() .into_iter() .map(|host| { let glyph = crate::display::ui::os_icons::resolve(icons.get(&host).map(String::as_str)).glyph; + let (label, hint) = + super::shell_picker::ssh_host_display(labels.get(&host).map(String::as_str), &host); + let search = format!("{label} {host} ssh host remote 远程 连接").to_lowercase(); WorkspacePaletteRow { group_order: 3, group: group.to_owned(), - label: host.clone(), - hint: "SSH".to_owned(), + label, + hint, hint_style: super::WorkspacePaletteHintStyle::Metadata, - search: format!("{host} ssh host remote 远程 连接").to_lowercase(), + search, action: WorkspacePaletteAction::LaunchSshHost(host), icon: None, icon_glyph: Some(glyph), diff --git a/nebula_app/src/gpui_shell/workspace/shell_picker.rs b/nebula_app/src/gpui_shell/workspace/shell_picker.rs index d4dde13..c0a9b8a 100644 --- a/nebula_app/src/gpui_shell/workspace/shell_picker.rs +++ b/nebula_app/src/gpui_shell/workspace/shell_picker.rs @@ -15,6 +15,20 @@ fn fallback_shell_glyph(id: &str, has_brand: bool) -> Option { crate::shell_detect::icon_for_id(id).chars().next() } +/// SSH 主机的展示名与次要信息,与旧壳 `SshRow` 同口径: +/// +/// - 用户在 SSH 设置里起过"主机名称":label 显示别名,hint 给真实连接地址; +/// - 没起名:回落地址本身当 label,hint 保持 "SSH" 类型标签,避免重复。 +pub(super) fn ssh_host_display( + label: Option<&str>, + host: &str, +) -> (String, String) { + match label { + Some(label) if !label.trim().is_empty() => (label.trim().to_owned(), host.to_owned()), + _ => (host.to_owned(), "SSH".to_owned()), + } +} + /// 新建终端弹窗的行:已检测 shell + SSH 主机,分组对照旧壳 /// `CommandPalette::open_profiles`(推荐 / 所有 Shell / SSH 主机)。 /// 三点菜单与 Ctrl+K 打开的是这份列表,不是通用命令面板。 @@ -22,6 +36,7 @@ pub(super) fn shell_palette_rows( shells: Vec, profiles: Vec, ssh_hosts: impl IntoIterator, + ssh_labels: &std::collections::HashMap, default_shell_id: &str, language: crate::display::UiLanguage, scale_factor: f32, @@ -88,13 +103,16 @@ pub(super) fn shell_palette_rows( rows.extend(ssh_hosts.into_iter().map(|host| { let glyph = crate::display::ui::os_icons::resolve(ssh_icons.get(&host).map(String::as_str)).glyph; + let (label, hint) = + ssh_host_display(ssh_labels.get(&host).map(String::as_str), &host); + let search = format!("{label} {host} ssh host remote lianjie 连接").to_lowercase(); WorkspacePaletteRow { group_order: 2, group: ssh_group.to_owned(), - label: host.clone(), - hint: "SSH".to_owned(), + label, + hint, hint_style: WorkspacePaletteHintStyle::Metadata, - search: format!("{host} ssh host remote lianjie 连接").to_lowercase(), + search, action: WorkspacePaletteAction::LaunchSshHost(host), icon: None, icon_glyph: Some(glyph), diff --git a/nebula_app/src/gpui_shell/workspace/tests.rs b/nebula_app/src/gpui_shell/workspace/tests.rs index a9da3d8..0c0bc03 100644 --- a/nebula_app/src/gpui_shell/workspace/tests.rs +++ b/nebula_app/src/gpui_shell/workspace/tests.rs @@ -557,6 +557,7 @@ fn shell_palette_puts_the_default_shell_first() { shells.clone(), Vec::new(), ["box.example".to_owned()], + &std::collections::HashMap::new(), "nu", language, 1.0, @@ -583,7 +584,7 @@ fn shell_palette_puts_the_default_shell_first() { assert_eq!(rows[4].group, "SSH 主机"); // 默认 id 没在检测结果里(WSL 发行版被卸载等):不置顶也不 panic。 - let rows = shell_palette_rows(shells, Vec::new(), None::, "wsl:Ghost", language, 1.0); + let rows = shell_palette_rows(shells, Vec::new(), None::, &std::collections::HashMap::new(), "wsl:Ghost", language, 1.0); assert!(matches!( &rows[0].action, WorkspacePaletteAction::LaunchShell(shell) if shell.id == "pwsh" @@ -591,6 +592,41 @@ fn shell_palette_puts_the_default_shell_first() { assert_eq!(rows[0].group, "所有 Shell"); } +/// 启动器 SSH 行的"主机名称":起过名的显示别名、hint 给真实地址;没起名 +/// 回落地址本身、hint 保持 "SSH"。搜索词必须同时覆盖别名与地址。 +#[test] +fn shell_palette_ssh_rows_show_the_configured_host_name() { + let labels = [("root@box.example".to_owned(), "生产机".to_owned())].into(); + let rows = shell_palette_rows( + Vec::new(), + Vec::new(), + ["root@box.example".to_owned(), "plain.example".to_owned()], + &labels, + "cmd", + crate::display::UiLanguage::ZhCn, + 1.0, + ); + assert_eq!(rows.len(), 2); + + let named = &rows[0]; + assert_eq!(named.label, "生产机"); + assert_eq!(named.hint, "root@box.example"); + assert!(named.search.contains("生产机")); + assert!(named.search.contains("root@box.example")); + assert!(matches!( + &named.action, + WorkspacePaletteAction::LaunchSshHost(host) if host == "root@box.example" + )); + + let plain = &rows[1]; + assert_eq!(plain.label, "plain.example"); + assert_eq!(plain.hint, "SSH"); + assert!(matches!( + &plain.action, + WorkspacePaletteAction::LaunchSshHost(host) if host == "plain.example" + )); +} + /// 回归锁:新建终端弹窗里,没有品牌贴图的 shell 不能空着——必须回落到按 id /// 取字的 Nerd Font 字形。之前这一列只填了 `icon`,于是 zsh、csh、ksh 这些 /// 整行左边什么都没有。 @@ -609,6 +645,7 @@ fn shell_palette_falls_back_to_an_id_glyph_when_brand_art_is_absent() { shells, Vec::new(), None::, + &std::collections::HashMap::new(), "zsh", crate::display::UiLanguage::ZhCn, 1.0, @@ -650,6 +687,7 @@ fn shell_palette_includes_imported_terminal_profiles() { Vec::new(), vec![profile], None::, + &std::collections::HashMap::new(), &id, crate::display::UiLanguage::ZhCn, 1.0,