diff --git a/nebula_app/src/gpui_shell/ssh_hosts.rs b/nebula_app/src/gpui_shell/ssh_hosts.rs index 6267a37..9b9c0be 100644 --- a/nebula_app/src/gpui_shell/ssh_hosts.rs +++ b/nebula_app/src/gpui_shell/ssh_hosts.rs @@ -64,6 +64,21 @@ impl SshHostLists { ) } + /// 与 [`Self::merged`] 同一顺序权威,同时带出用户在 SSH 设置里起的 + /// 主机名称(地址 → 别名;没起名的是空串,展示端回落地址本身)。 + /// profiles 在 [`Self::load`] 时已经读盘,这里不重复读 JSON;launcher / + /// Quick Jump / 命令面板三处 SSH 行共用,避免各自再解析一次。 + pub fn merged_with_labels(&self) -> Vec<(String, String)> { + let labels = self.profiles.labels(); + self.merged() + .into_iter() + .map(|host| { + let label = labels.get(&host).cloned().unwrap_or_default(); + (host, label) + }) + .collect() + } + /// 隐藏区(设置页"已隐藏"折叠列表的数据源)。 pub fn hidden_hosts(&self) -> Vec { self.hidden.iter().filter(|host| self.configured.contains(host)).cloned().collect() diff --git a/nebula_app/src/gpui_shell/workspace.rs b/nebula_app/src/gpui_shell/workspace.rs index 567d976..f0d7be7 100644 --- a/nebula_app/src/gpui_shell/workspace.rs +++ b/nebula_app/src/gpui_shell/workspace.rs @@ -636,15 +636,6 @@ 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` 同名同义, /// 所以导出/恢复不需要转换层)。 @@ -2237,8 +2228,7 @@ impl NebulaWorkspace { crate::terminal_profiles::TerminalProfiles::load() .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()), + crate::gpui_shell::ssh_hosts::SshHostLists::load().merged_with_labels(), &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 3a4d0d1..d8caafd 100644 --- a/nebula_app/src/gpui_shell/workspace/launcher_menu/tests.rs +++ b/nebula_app/src/gpui_shell/workspace/launcher_menu/tests.rs @@ -37,8 +37,7 @@ fn open(cx: &mut TestAppContext) -> (Entity, VisualTestContext) workspace.palette_override = Some(shell_palette_rows( vec![shell("cmd"), shell("pwsh")], vec![], - ["test-host".into()], - &std::collections::HashMap::new(), + [("test-host".into(), String::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 b4ad9e1..63b49c3 100644 --- a/nebula_app/src/gpui_shell/workspace/palette.rs +++ b/nebula_app/src/gpui_shell/workspace/palette.rs @@ -99,20 +99,19 @@ 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| { + crate::gpui_shell::ssh_hosts::SshHostLists::load() + .merged_with_labels() + .into_iter() + .map(|(host, label)| { let glyph = crate::display::ui::os_icons::resolve( 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(); + let named = (!label.is_empty()).then_some(label.as_str()); + let (label, hint) = shell_picker::ssh_host_display(named, &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(), @@ -125,8 +124,7 @@ impl NebulaWorkspace { icon_glyph: Some(glyph), icon_path: None, } - }, - ), + }), ); rows }); diff --git a/nebula_app/src/gpui_shell/workspace/quick_jump.rs b/nebula_app/src/gpui_shell/workspace/quick_jump.rs index 4ab3d5d..234f40e 100644 --- a/nebula_app/src/gpui_shell/workspace/quick_jump.rs +++ b/nebula_app/src/gpui_shell/workspace/quick_jump.rs @@ -142,15 +142,14 @@ 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() + .merged_with_labels() .into_iter() - .map(|host| { + .map(|(host, label)| { 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 named = (!label.is_empty()).then_some(label.as_str()); + let (label, hint) = shell_picker::ssh_host_display(named, &host); let search = format!("{label} {host} ssh host remote 远程 连接").to_lowercase(); WorkspacePaletteRow { group_order: 3, diff --git a/nebula_app/src/gpui_shell/workspace/shell_picker.rs b/nebula_app/src/gpui_shell/workspace/shell_picker.rs index c0a9b8a..4702c6d 100644 --- a/nebula_app/src/gpui_shell/workspace/shell_picker.rs +++ b/nebula_app/src/gpui_shell/workspace/shell_picker.rs @@ -19,10 +19,7 @@ fn fallback_shell_glyph(id: &str, has_brand: bool) -> Option { /// /// - 用户在 SSH 设置里起过"主机名称":label 显示别名,hint 给真实连接地址; /// - 没起名:回落地址本身当 label,hint 保持 "SSH" 类型标签,避免重复。 -pub(super) fn ssh_host_display( - label: Option<&str>, - host: &str, -) -> (String, String) { +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()), @@ -35,8 +32,7 @@ pub(super) fn ssh_host_display( pub(super) fn shell_palette_rows( shells: Vec, profiles: Vec, - ssh_hosts: impl IntoIterator, - ssh_labels: &std::collections::HashMap, + ssh_hosts: impl IntoIterator, default_shell_id: &str, language: crate::display::UiLanguage, scale_factor: f32, @@ -100,11 +96,12 @@ pub(super) fn shell_palette_rows( rows.insert(0, default_row); } let ssh_icons = ssh_host_icon_ids(&crate::display::nebula_data_dir()); - rows.extend(ssh_hosts.into_iter().map(|host| { + rows.extend(ssh_hosts.into_iter().map(|(host, label)| { 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); + // 空串 = 没起名:`ssh_host_display` 回落地址本身,hint 保持 "SSH"。 + let named = (!label.is_empty()).then_some(label.as_str()); + let (label, hint) = ssh_host_display(named, &host); let search = format!("{label} {host} ssh host remote lianjie 连接").to_lowercase(); WorkspacePaletteRow { group_order: 2, diff --git a/nebula_app/src/gpui_shell/workspace/tests.rs b/nebula_app/src/gpui_shell/workspace/tests.rs index 0c0bc03..fb76b4b 100644 --- a/nebula_app/src/gpui_shell/workspace/tests.rs +++ b/nebula_app/src/gpui_shell/workspace/tests.rs @@ -556,8 +556,7 @@ fn shell_palette_puts_the_default_shell_first() { let rows = shell_palette_rows( shells.clone(), Vec::new(), - ["box.example".to_owned()], - &std::collections::HashMap::new(), + [("box.example".to_owned(), String::new())], "nu", language, 1.0, @@ -584,7 +583,14 @@ 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::, &std::collections::HashMap::new(), "wsl:Ghost", language, 1.0); + let rows = shell_palette_rows( + shells, + Vec::new(), + None::<(String, String)>, + "wsl:Ghost", + language, + 1.0, + ); assert!(matches!( &rows[0].action, WorkspacePaletteAction::LaunchShell(shell) if shell.id == "pwsh" @@ -596,12 +602,13 @@ fn shell_palette_puts_the_default_shell_first() { /// 回落地址本身、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, + [ + ("root@box.example".to_owned(), "生产机".to_owned()), + ("plain.example".to_owned(), String::new()), + ], "cmd", crate::display::UiLanguage::ZhCn, 1.0, @@ -644,8 +651,7 @@ fn shell_palette_falls_back_to_an_id_glyph_when_brand_art_is_absent() { let rows = shell_palette_rows( shells, Vec::new(), - None::, - &std::collections::HashMap::new(), + None::<(String, String)>, "zsh", crate::display::UiLanguage::ZhCn, 1.0, @@ -686,8 +692,7 @@ fn shell_palette_includes_imported_terminal_profiles() { let rows = shell_palette_rows( Vec::new(), vec![profile], - None::, - &std::collections::HashMap::new(), + None::<(String, String)>, &id, crate::display::UiLanguage::ZhCn, 1.0,