* feat(terminal): 支持为自定义本地 Shell 配置启动参数 (#1221) 自定义本地 Shell 此前只能填可执行文件路径,无法指定启动参数, 导致接入 msys2 bash 时缺少 `--login -i`,shell 不经 profile 初始化、 环境变量缺失而无法使用。 - TerminalSettings 新增 localShellArgs(string[],默认 []),随设置自动迁移 - 新增 domain/shellArgs:引号感知的命令行分词/回显格式化 - resolveShellSetting 透传自定义参数;参数为空时回退到 bridge 默认参数, 命中已发现 shell(WSL/Git Bash)时忽略自定义参数,避免串味 - 自定义 Shell 弹窗新增「启动参数」输入,设置行展示完整命令(en/zh-CN/ru) 参数复用已有管线 session.localShellArgs → startLocalSession → pty.spawn, 不涉及云同步(与机器相关的 localShell 一致,均不同步)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): 修复自定义 Shell 参数的两处 review 问题 - 切换到默认/已发现 shell 时清空 localShellArgs:自定义参数仅对自定义 路径有意义,清空可避免在发现列表尚未加载(启动竞态)或所选 shell ID 在本机不可用时,旧参数被当作该 shell 的启动参数而泄漏导致启动失败 - formatShellArgs 对含双引号的参数改用单引号包裹,修复 `-c "..."` 这类参数重新打开弹窗保存时被破坏的往返问题 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): shellArgs 量化处理含两种引号的参数往返 采用单引号优先的引号策略:单引号内全字面(Windows 路径、双引号原样保留, 无需转义);仅当 token 自身含单引号时改用双引号包裹并转义 \ 与 "。 解析端仅在双引号区内将 \" / \\ 视为转义,其余反斜杠保持字面, 确保未加引号的 Windows 路径不被破坏。修复 `echo "it's ok"` 这类同时含 单双引号的参数在弹窗重新保存时被破坏的问题。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): 自定义 Shell 已选中后可重新打开编辑参数 自定义 shell 选中时下拉框值已是 __custom__,再次点选「自定义…」不会触发 onValueChange,导致无法重新打开弹窗修改参数/路径。改为把自定义 shell 概要 做成可点击的编辑入口(铅笔图标),点击即重新填充草稿并打开弹窗; 打开逻辑抽到 openCustomShellModal 复用。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): shellArgs 改用 POSIX 单引号方案,修复尾部反斜杠与空参数 - 解析:两种引号区内均全字面(双引号不再把 \" 当转义),保留 Windows 路径尾部反斜杠等手输入;引号外仅 \' 转义为字面单引号(支撑 '\'' 习语), 其余反斜杠保持字面,未加引号的 Windows 路径不受影响 - 格式化:统一单引号包裹(内容全字面),内嵌单引号用 POSIX '\'' 习语; 显式空参数输出为 '' 以免重新保存时被丢弃 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): customArgs take precedence over discovered shell defaults when user explicitly sets them --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
84 lines
2.7 KiB
TypeScript
84 lines
2.7 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { parseShellArgs, formatShellArgs } from "./shellArgs";
|
|
|
|
test("parseShellArgs splits plain space-separated args", () => {
|
|
assert.deepEqual(parseShellArgs("--login -i"), ["--login", "-i"]);
|
|
});
|
|
|
|
test("parseShellArgs returns empty array for empty or whitespace input", () => {
|
|
assert.deepEqual(parseShellArgs(""), []);
|
|
assert.deepEqual(parseShellArgs(" "), []);
|
|
});
|
|
|
|
test("parseShellArgs collapses surrounding and repeated whitespace", () => {
|
|
assert.deepEqual(parseShellArgs(" --login -i "), ["--login", "-i"]);
|
|
assert.deepEqual(parseShellArgs("\t--login\t-i\n"), ["--login", "-i"]);
|
|
});
|
|
|
|
test("parseShellArgs keeps double-quoted args with spaces intact", () => {
|
|
assert.deepEqual(
|
|
parseShellArgs('--rcfile "C:\\Program Files\\rc"'),
|
|
["--rcfile", "C:\\Program Files\\rc"],
|
|
);
|
|
});
|
|
|
|
test("parseShellArgs keeps single-quoted args with spaces intact", () => {
|
|
assert.deepEqual(parseShellArgs("--msg 'hello world'"), ["--msg", "hello world"]);
|
|
});
|
|
|
|
test("parseShellArgs allows quotes in the middle of a token", () => {
|
|
assert.deepEqual(parseShellArgs('a"b c"d'), ["ab cd"]);
|
|
});
|
|
|
|
test("formatShellArgs joins plain args with single spaces", () => {
|
|
assert.equal(formatShellArgs(["--login", "-i"]), "--login -i");
|
|
});
|
|
|
|
test("formatShellArgs returns empty string for empty array", () => {
|
|
assert.equal(formatShellArgs([]), "");
|
|
});
|
|
|
|
test("formatShellArgs single-quotes tokens containing whitespace", () => {
|
|
assert.equal(formatShellArgs(["--msg", "hello world"]), "--msg 'hello world'");
|
|
});
|
|
|
|
test("formatShellArgs keeps Windows backslash paths intact (no escaping)", () => {
|
|
assert.equal(formatShellArgs(["--rcfile", "C:\\Program Files\\rc"]), "--rcfile 'C:\\Program Files\\rc'");
|
|
});
|
|
|
|
test("parse and format round-trip", () => {
|
|
const cases: string[][] = [
|
|
[],
|
|
["--login", "-i"],
|
|
["--msg", "hello world"],
|
|
["--rcfile", "C:\\Program Files\\rc"],
|
|
["-c", 'echo "hello world"'],
|
|
["--msg", "it's fine"],
|
|
["-c", `echo "it's ok"`],
|
|
["a'b", 'c"d'],
|
|
["C:\\dir\\"],
|
|
["-c", ""],
|
|
];
|
|
for (const args of cases) {
|
|
assert.deepEqual(parseShellArgs(formatShellArgs(args)), args);
|
|
}
|
|
});
|
|
|
|
test("parseShellArgs preserves a trailing backslash inside double quotes", () => {
|
|
assert.deepEqual(parseShellArgs('"C:\\dir\\"'), ["C:\\dir\\"]);
|
|
});
|
|
|
|
test("formatShellArgs uses single quotes for tokens containing double quotes", () => {
|
|
assert.equal(formatShellArgs(['echo "x"']), `'echo "x"'`);
|
|
});
|
|
|
|
test("formatShellArgs uses the POSIX '\\'' idiom for embedded single quotes", () => {
|
|
assert.equal(formatShellArgs(["it's"]), "'it'\\''s'");
|
|
});
|
|
|
|
test("formatShellArgs emits an explicit empty arg as ''", () => {
|
|
assert.equal(formatShellArgs(["-c", ""]), "-c ''");
|
|
});
|