mirror of
https://github.com/feigeCode/navop.git
synced 2026-09-22 00:01:25 +00:00
feat(terminal): 本地终端下拉自动识别并列出 WSL 发行版,按发行版一键启动 (#182)
This commit is contained in:
@@ -27,6 +27,7 @@ pub mod types;
|
||||
mod windows_environment;
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
mod windows_shell_integration;
|
||||
mod wsl_distributions;
|
||||
pub mod zmodem;
|
||||
|
||||
pub use exec_supervisor::TerminalExecError;
|
||||
@@ -34,6 +35,7 @@ pub use local_shell::{
|
||||
local_config_from_custom_profile, local_config_from_settings,
|
||||
local_config_from_settings_with_profile,
|
||||
};
|
||||
pub use wsl_distributions::{WslDistribution, list_wsl_distributions, local_config_for_wsl_distro};
|
||||
pub use performance_metrics::{
|
||||
TERMINAL_PERFORMANCE_METRICS_ENV, TerminalActivity, TerminalInputMetricSource,
|
||||
TerminalPerformanceMetrics, TerminalPerformanceSnapshot, TerminalPerformanceWindow,
|
||||
|
||||
@@ -243,7 +243,7 @@ fn resolve_cmd() -> String {
|
||||
}
|
||||
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
fn resolve_wsl() -> String {
|
||||
pub(crate) fn resolve_wsl() -> String {
|
||||
system32_path(&["wsl.exe"])
|
||||
.or_else(|| find_in_path("wsl.exe"))
|
||||
.unwrap_or_else(|| "wsl.exe".to_string())
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
use anyhow::Result;
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::LocalConfig;
|
||||
|
||||
/// `wsl.exe --list --verbose` 报告的一个 WSL 发行版。
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct WslDistribution {
|
||||
/// 注册名(如 `Ubuntu-22.04`),用于 `wsl.exe -d <name>` 启动。
|
||||
pub name: String,
|
||||
/// 运行状态(如 `Running`/`Stopped`);仅用于展示与测试,允许缺失。
|
||||
pub state: Option<String>,
|
||||
/// WSL 版本(1/2);旧版 wsl.exe 输出可能缺失。
|
||||
pub version: Option<u8>,
|
||||
/// 是否为默认发行版(输出行首的 `*` 标记)。
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
impl WslDistribution {
|
||||
fn new(
|
||||
name: String,
|
||||
state: Option<String>,
|
||||
version: Option<u8>,
|
||||
is_default: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
state,
|
||||
version,
|
||||
is_default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 识别已安装的 WSL 发行版(`wsl.exe --list --verbose`)。
|
||||
///
|
||||
/// 仅在 Windows 上可用;未安装 WSL、无发行版或 wsl.exe 不可用时返回错误。
|
||||
pub fn list_wsl_distributions() -> Result<Vec<WslDistribution>> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
list_wsl_distributions_with(&crate::local_shell::resolve_wsl())
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
anyhow::bail!("WSL is only available on Windows")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
pub(crate) fn list_wsl_distributions_with(wsl: &str) -> Result<Vec<WslDistribution>> {
|
||||
let output = std::process::Command::new(wsl)
|
||||
.args(["--list", "--verbose"])
|
||||
.output()
|
||||
.with_context(|| format!("failed to run {wsl} --list --verbose"))?;
|
||||
if !output.status.success() {
|
||||
// 未安装 WSL 或没有发行版时 wsl.exe 以非零码退出并输出本地化错误文本,
|
||||
// 这里原样透出,由调用方决定按空结果还是提示处理。
|
||||
anyhow::bail!("{}", decode_wsl_output(&output.stdout));
|
||||
}
|
||||
Ok(parse_wsl_list_output(&output.stdout))
|
||||
}
|
||||
|
||||
/// 构造以指定发行版启动本地终端的配置(`wsl.exe -d <name>`)。
|
||||
pub fn local_config_for_wsl_distro(distro: &str) -> Result<LocalConfig> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
local_config_for_wsl_distro_with(crate::local_shell::resolve_wsl(), distro)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let _ = distro;
|
||||
anyhow::bail!("WSL is only available on Windows")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
pub(crate) fn local_config_for_wsl_distro_with(
|
||||
wsl: String,
|
||||
distro: &str,
|
||||
) -> Result<LocalConfig> {
|
||||
let distro = distro.trim();
|
||||
anyhow::ensure!(!distro.is_empty(), "WSL distribution name is required");
|
||||
Ok(LocalConfig {
|
||||
shell: Some(wsl),
|
||||
args: vec!["--distribution".into(), distro.into()],
|
||||
working_dir: None,
|
||||
..LocalConfig::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// 解析 `wsl.exe --list --verbose` 的输出。
|
||||
///
|
||||
/// 纯函数、跨平台可测:支持 wsl.exe 管道输出的 UTF-16LE(默认)与 UTF-8;
|
||||
/// 表头、错误文本等非数据行一律过滤为空结果。
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
pub(crate) fn parse_wsl_list_output(raw: &[u8]) -> Vec<WslDistribution> {
|
||||
decode_wsl_output(raw)
|
||||
.lines()
|
||||
.filter_map(parse_distribution_row)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 单行解析规则(issue feigeCode/navop#182):
|
||||
/// - 行首 `*` 标记默认发行版;
|
||||
/// - 名称与第二列(状态)以连续两个以上空格分隔;
|
||||
/// - 版本列必须能解析为数字,状态列是已知状态词时也接受缺失版本列——
|
||||
/// 版本/状态双判据用于把本地化表头与错误文本从数据行中稳定排除。
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
fn parse_distribution_row(line: &str) -> Option<WslDistribution> {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let (is_default, line) = match line.strip_prefix('*') {
|
||||
Some(rest) => (true, rest.trim()),
|
||||
None => (false, line),
|
||||
};
|
||||
let columns = split_columns(line);
|
||||
let name = columns.first()?.trim();
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let state = columns.get(1).map(|column| column.trim().to_string());
|
||||
let version = columns.get(2).and_then(|column| column.trim().parse::<u8>().ok());
|
||||
let recognized_state = state
|
||||
.as_deref()
|
||||
.is_some_and(|state| KNOWN_STATES.contains(&state));
|
||||
match (version, recognized_state) {
|
||||
(Some(version), _) => Some(WslDistribution::new(
|
||||
name.to_string(),
|
||||
state,
|
||||
Some(version),
|
||||
is_default,
|
||||
)),
|
||||
(None, true) => Some(WslDistribution::new(name.to_string(), state, None, is_default)),
|
||||
// 表头(版本列为 "VERSION" 等非数字)或错误文本行
|
||||
(None, false) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// wsl.exe 表格状态列的已知词(状态词不参与本地化,仅作旧输出回退判据)。
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
const KNOWN_STATES: [&str; 6] = [
|
||||
"Running",
|
||||
"Stopped",
|
||||
"Installing",
|
||||
"Uninstalling",
|
||||
"Converting",
|
||||
"Stopping",
|
||||
];
|
||||
|
||||
/// 按连续两个以上空格拆分表格列;名称中的单个空格不受影响。
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
fn split_columns(line: &str) -> Vec<&str> {
|
||||
line.split(" ")
|
||||
.map(str::trim)
|
||||
.filter(|column| !column.is_empty())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// wsl.exe 在 stdout 被重定向时输出 UTF-16LE(可能带 BOM);否则按 UTF-8 处理。
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
pub(crate) fn decode_wsl_output(raw: &[u8]) -> String {
|
||||
if looks_like_utf16le(raw) {
|
||||
let units = raw
|
||||
.chunks_exact(2)
|
||||
.map(|pair| u16::from_le_bytes([pair[0], pair[1]]))
|
||||
.collect::<Vec<_>>();
|
||||
String::from_utf16_lossy(&units)
|
||||
} else {
|
||||
String::from_utf8_lossy(raw).into_owned()
|
||||
}
|
||||
.chars()
|
||||
.filter(|ch| *ch != '\u{feff}' && *ch != '\0')
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 无 BOM 时以零字节占比判定:UTF-16LE 编码的文本在奇数位大量为零字节。
|
||||
#[cfg(any(test, target_os = "windows"))]
|
||||
fn looks_like_utf16le(raw: &[u8]) -> bool {
|
||||
if raw.starts_with(&[0xFF, 0xFE]) {
|
||||
return true;
|
||||
}
|
||||
let odd_zero_bytes = raw
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(index, byte)| index % 2 == 1 && **byte == 0)
|
||||
.count();
|
||||
raw.len() >= 2 && odd_zero_bytes > raw.len() / 4
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "wsl_distributions_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,107 @@
|
||||
use super::{WslDistribution, decode_wsl_output, local_config_for_wsl_distro_with, parse_wsl_list_output};
|
||||
|
||||
fn utf16le(text: &str) -> Vec<u8> {
|
||||
text.encode_utf16().flat_map(u16::to_le_bytes).collect()
|
||||
}
|
||||
|
||||
fn verbose_sample() -> String {
|
||||
[
|
||||
"\r\n",
|
||||
" NAME STATE VERSION\r\n",
|
||||
"* Ubuntu-22.04 Running 2\r\n",
|
||||
" Debian Stopped 1\r\n",
|
||||
" openEuler-24.03 Stopped 2\r\n",
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
|
||||
fn expected_distributions() -> Vec<WslDistribution> {
|
||||
vec![
|
||||
WslDistribution::new("Ubuntu-22.04".into(), Some("Running".into()), Some(2), true),
|
||||
WslDistribution::new("Debian".into(), Some("Stopped".into()), Some(1), false),
|
||||
WslDistribution::new("openEuler-24.03".into(), Some("Stopped".into()), Some(2), false),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_verbose_utf16_output_with_default_marker() {
|
||||
assert_eq!(expected_distributions(), parse_wsl_list_output(&utf16le(&verbose_sample())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_verbose_utf16_output_with_bom() {
|
||||
let mut raw = vec![0xFF, 0xFE];
|
||||
raw.extend(utf16le(&verbose_sample()));
|
||||
assert_eq!(expected_distributions(), parse_wsl_list_output(&raw));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_utf8_output_without_bom() {
|
||||
assert_eq!(expected_distributions(), parse_wsl_list_output(verbose_sample().as_bytes()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_rows_missing_the_version_column_when_state_is_known() {
|
||||
let distributions = parse_wsl_list_output(b"Ubuntu-22.04 Running\r\n".as_slice());
|
||||
assert_eq!(
|
||||
vec![WslDistribution::new("Ubuntu-22.04".into(), Some("Running".into()), None, false)],
|
||||
distributions
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn localized_header_and_error_text_yield_no_distributions() {
|
||||
// 本地化表头的版本列不是数字,被稳定过滤;数据行不受表头本地化影响。
|
||||
let localized = "\r\n 名称 状态 版本\r\n* Ubuntu-22.04 正在运行 2\r\n";
|
||||
assert_eq!(
|
||||
vec![WslDistribution::new("Ubuntu-22.04".into(), Some("正在运行".into()), Some(2), true)],
|
||||
parse_wsl_list_output(localized.as_bytes())
|
||||
);
|
||||
let error = "适用于 Linux 的 Windows 子系统没有已安装的分发版。\r\n";
|
||||
assert!(parse_wsl_list_output(&utf16le(error)).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_only_output_yields_no_distributions() {
|
||||
let header_only = "\r\n NAME STATE VERSION\r\n";
|
||||
assert!(parse_wsl_list_output(&utf16le(header_only)).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_and_blank_output_yield_no_distributions() {
|
||||
assert!(parse_wsl_list_output(&[]).is_empty());
|
||||
assert!(parse_wsl_list_output(b"\r\n").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distro_launch_config_uses_the_distribution_argument_form() {
|
||||
let config =
|
||||
local_config_for_wsl_distro_with("C:\\Windows\\System32\\wsl.exe".into(), "Ubuntu-22.04")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Some("C:\\Windows\\System32\\wsl.exe".to_string()),
|
||||
config.shell
|
||||
);
|
||||
assert_eq!(
|
||||
vec!["--distribution".to_string(), "Ubuntu-22.04".to_string()],
|
||||
config.args
|
||||
);
|
||||
assert!(config.working_dir.is_none());
|
||||
// 继承 LocalConfig::default() 的基础终端环境变量
|
||||
assert!(config
|
||||
.env
|
||||
.contains(&("TERM".to_string(), "xterm-256color".to_string())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distro_launch_config_trims_names_and_rejects_blank_names() {
|
||||
let config = local_config_for_wsl_distro_with("wsl.exe".into(), " Debian-12 ").unwrap();
|
||||
assert_eq!(vec!["--distribution".to_string(), "Debian-12".to_string()], config.args);
|
||||
assert!(local_config_for_wsl_distro_with("wsl.exe".into(), " ").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_prefers_utf16_when_zero_bytes_dominate() {
|
||||
assert_eq!("abc".to_string(), decode_wsl_output(&utf16le("abc")));
|
||||
assert_eq!("abc".to_string(), decode_wsl_output(b"abc"));
|
||||
}
|
||||
@@ -231,6 +231,18 @@ Home:
|
||||
en: "Invalid local terminal configuration: %{error}"
|
||||
zh-CN: "本地终端配置无效:%{error}"
|
||||
zh-HK: "本機終端設定無效:%{error}"
|
||||
wsl_distributions_section:
|
||||
en: WSL Distributions
|
||||
zh-CN: WSL 发行版
|
||||
zh-HK: WSL 發行版
|
||||
wsl_distributions_default:
|
||||
en: default
|
||||
zh-CN: 默认
|
||||
zh-HK: 預設
|
||||
wsl_distributions_refresh:
|
||||
en: Re-detect WSL
|
||||
zh-CN: 重新识别 WSL
|
||||
zh-HK: 重新識別 WSL
|
||||
remote_desktop_parameters_invalid:
|
||||
en: "Invalid remote desktop connection parameters: %{error}"
|
||||
zh-CN: "远程桌面连接参数无效:%{error}"
|
||||
|
||||
@@ -1386,6 +1386,24 @@ impl HomePage {
|
||||
self.add_local_terminal_tab(config, window, cx);
|
||||
}
|
||||
|
||||
/// 以指定 WSL 发行版打开本地终端标签页(`wsl.exe -d <name>`,feigeCode/navop#182)。
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn add_terminal_tab_with_wsl_distro(
|
||||
&mut self,
|
||||
distro: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let config = match terminal::local_config_for_wsl_distro(&distro) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
push_local_terminal_config_error(window, &error, cx);
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.add_local_terminal_tab(config, window, cx);
|
||||
}
|
||||
|
||||
fn add_terminal_tab_from_profile(
|
||||
&mut self,
|
||||
profile_kind: Option<LocalTerminalProfileKind>,
|
||||
|
||||
@@ -185,6 +185,9 @@ pub struct HomePage {
|
||||
team_permissions: TeamPermissionSnapshot,
|
||||
port_forwarding_runtime: Arc<tokio::sync::Mutex<PortForwardingRuntime>>,
|
||||
pub(crate) external_driver_registry: IpcDriverRegistry,
|
||||
/// Windows 已识别的 WSL 发行版缓存(issue #182);None=识别中,空列表=无发行版。
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
pub(crate) wsl_distributions: Option<Arc<Vec<terminal::WslDistribution>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -27,6 +27,28 @@ impl HomePage {
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// 后台识别已安装的 WSL 发行版,供本地终端下拉菜单展示(feigeCode/navop#182)。
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(super) fn load_wsl_distributions(&mut self, cx: &mut Context<Self>) {
|
||||
let detect_task = cx.background_spawn(async move { terminal::list_wsl_distributions() });
|
||||
|
||||
cx.spawn(async move |this, cx: &mut AsyncApp| {
|
||||
let distributions = match detect_task.await {
|
||||
Ok(distributions) => distributions,
|
||||
// 未安装 WSL、未启用 WSL 功能或没有发行版时识别失败属预期,按空列表处理。
|
||||
Err(error) => {
|
||||
tracing::info!("WSL 发行版识别失败: {error}");
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
_ = this.update(cx, |this, cx| {
|
||||
this.wsl_distributions = Some(Arc::new(distributions));
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub(super) fn load_connections(&mut self, cx: &mut Context<Self>) {
|
||||
if self.saved_connections_locked() {
|
||||
tracing::warn!("主密钥未解锁,暂缓加载本地连接,避免将加密密码解密为空");
|
||||
|
||||
@@ -103,6 +103,7 @@ impl HomePage {
|
||||
tokio::sync::Mutex::new(PortForwardingRuntime::new()),
|
||||
),
|
||||
external_driver_registry: IpcDriverRegistry::empty(),
|
||||
wsl_distributions: None,
|
||||
};
|
||||
|
||||
// 使用持久化身份预载本地团队权限,不等待在线会话恢复。
|
||||
@@ -110,6 +111,10 @@ impl HomePage {
|
||||
// 异步加载工作区
|
||||
page.load_workspaces(cx);
|
||||
|
||||
// Windows 下后台识别 WSL 发行版,供本地终端下拉菜单展示(feigeCode/navop#182)。
|
||||
#[cfg(target_os = "windows")]
|
||||
page.load_wsl_distributions(cx);
|
||||
|
||||
let has_repo_password = crypto::has_repo_password_set();
|
||||
let settings = AppSettings::current(cx);
|
||||
let master_key_policy =
|
||||
|
||||
@@ -14,6 +14,9 @@ impl HomePage {
|
||||
let profile_settings = AppSettings::global(cx).local_terminal_profile.clone();
|
||||
let view = cx.entity();
|
||||
let menu_view = view.clone();
|
||||
// WSL 发行版在启动时后台识别(仅 Windows);克隆快照供菜单闭包使用。
|
||||
#[cfg(target_os = "windows")]
|
||||
let wsl_distributions = self.wsl_distributions.clone();
|
||||
DropdownButton::new("local-terminal-dropdown")
|
||||
.flex_shrink_0()
|
||||
.button(
|
||||
@@ -32,7 +35,7 @@ impl HomePage {
|
||||
})),
|
||||
)
|
||||
.dropdown_menu_with_anchor(Anchor::TopRight, move |menu, _, _| {
|
||||
launch_options(cfg!(target_os = "windows"), &profile_settings)
|
||||
let menu = launch_options(cfg!(target_os = "windows"), &profile_settings)
|
||||
.into_iter()
|
||||
.fold(menu, |menu, (target, label)| {
|
||||
let view = menu_view.clone();
|
||||
@@ -52,8 +55,63 @@ impl HomePage {
|
||||
});
|
||||
},
|
||||
))
|
||||
})
|
||||
});
|
||||
#[cfg(target_os = "windows")]
|
||||
let menu =
|
||||
append_wsl_distributions(menu, wsl_distributions.clone(), menu_view.clone());
|
||||
menu
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
/// 在本地终端菜单末尾追加 WSL 发行版区段(feigeCode/navop#182)。
|
||||
///
|
||||
/// 识别完成前与识别无结果时整段隐藏,避免占位闪烁;
|
||||
/// 区段末尾提供「重新识别」入口,点击后重新后台识别(下次打开菜单生效)。
|
||||
#[cfg(target_os = "windows")]
|
||||
fn append_wsl_distributions(
|
||||
menu: gpui_component::menu::PopupMenu,
|
||||
distributions: Option<Arc<Vec<terminal::WslDistribution>>>,
|
||||
home: Entity<HomePage>,
|
||||
) -> gpui_component::menu::PopupMenu {
|
||||
let Some(distributions) = distributions else {
|
||||
return menu;
|
||||
};
|
||||
if distributions.is_empty() {
|
||||
return menu;
|
||||
}
|
||||
let menu = menu
|
||||
.separator()
|
||||
.item(PopupMenuItem::label(t!("Home.wsl_distributions_section")));
|
||||
let menu = distributions.iter().fold(menu, |menu, distribution| {
|
||||
let home = home.clone();
|
||||
let distro = distribution.name.clone();
|
||||
let label = wsl_distro_item_label(distribution);
|
||||
menu.item(PopupMenuItem::new(label).on_click(move |_, window, cx| {
|
||||
home.update(cx, |home, cx| {
|
||||
home.add_terminal_tab_with_wsl_distro(distro.clone(), window, cx)
|
||||
});
|
||||
}))
|
||||
});
|
||||
let home = home.clone();
|
||||
menu.separator().item(
|
||||
PopupMenuItem::new(t!("Home.wsl_distributions_refresh")).on_click(move |_, _, cx| {
|
||||
home.update(cx, |home, cx| home.load_wsl_distributions(cx));
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// 发行版条目文案:默认发行版追加「默认」标记,便于对齐 MobaXterm 的辨识体验。
|
||||
#[cfg(target_os = "windows")]
|
||||
fn wsl_distro_item_label(distribution: &terminal::WslDistribution) -> String {
|
||||
if distribution.is_default {
|
||||
format!(
|
||||
"{} · {}",
|
||||
distribution.name,
|
||||
t!("Home.wsl_distributions_default")
|
||||
)
|
||||
} else {
|
||||
distribution.name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,35 @@ fn all_local_terminal_profiles_use_the_same_tab_opener() {
|
||||
openers
|
||||
.matches("self.add_local_terminal_tab(config, window, cx)")
|
||||
.count(),
|
||||
2,
|
||||
"both custom and built-in profiles must use the shared activation path",
|
||||
3,
|
||||
"custom, built-in and WSL distro profiles must use the shared activation path",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wsl_distro_menu_items_reuse_the_local_terminal_activation_path() {
|
||||
let source = include_str!("../../home/home_tabs.rs");
|
||||
let opener = source
|
||||
.split(" pub(crate) fn add_terminal_tab_with_wsl_distro(")
|
||||
.nth(1)
|
||||
.and_then(|source| source.split(" fn add_terminal_tab_from_profile(").next())
|
||||
.expect("WSL distro tab opener");
|
||||
|
||||
assert!(opener.contains("local_config_for_wsl_distro(&distro)"));
|
||||
assert!(opener.contains("self.add_local_terminal_tab(config, window, cx)"));
|
||||
|
||||
let menu_source = include_str!("../../home_tab/local_terminal.rs");
|
||||
// WSL 发行版区段仅在 Windows 上渲染,其余平台保持原菜单。
|
||||
assert!(menu_source.contains("#[cfg(target_os = \"windows\")]\nfn append_wsl_distributions"));
|
||||
assert!(menu_source.contains("wsl_distributions_section"));
|
||||
assert!(menu_source.contains("add_terminal_tab_with_wsl_distro"));
|
||||
// 区段入口仅在识别到发行版后出现,并提供重新识别操作。
|
||||
assert!(menu_source.contains("wsl_distributions_refresh"));
|
||||
assert!(menu_source.contains("load_wsl_distributions"));
|
||||
|
||||
let data_source = include_str!("../data.rs");
|
||||
assert!(data_source.contains(
|
||||
"#[cfg(target_os = \"windows\")]\n pub(super) fn load_wsl_distributions"
|
||||
));
|
||||
assert!(data_source.contains("terminal::list_wsl_distributions()"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user