feat(terminal): 引号感知的 shell 参数解析与路径去引号纯函数

This commit is contained in:
HuanLinOTO
2026-09-08 21:21:30 +08:00
parent de0eac54b7
commit 829ad49ddf
2 changed files with 88 additions and 0 deletions
+56
View File
@@ -467,3 +467,59 @@ export function shellSpawnArgs(configured: string[] = []): string[] {
if (configured.length > 0) return [...configured]
return process.platform === 'win32' ? [] : ['-l']
}
/**
* Strip ONE pair of surrounding quotes from a configured shell path. Users
* paste Windows paths with spaces pre-quoted (`"C:\Program Files\…"`); the
* quotes are shell-input syntax, not part of the path. Unpaired quotes and
* shorter values stay verbatim.
*/
export function unquotePath(value: string): string {
if (value.length >= 2) {
const first = value[0]
const last = value[value.length - 1]
if ((first === '"' && last === '"') || (first === "'" && last === "'")) return value.slice(1, -1)
}
return value
}
/**
* Split a settings-page shell-arguments string into argv with quote-aware
* grouping: `'…'` / `"…"` group whitespace, and characters inside quotes are
* LITERAL — a backslash is never an escape, so Windows paths survive intact
* (`-File "C:\my init\init.ps1"` → three tokens, the last containing spaces).
* The price is that an argument containing a literal quote character cannot
* be expressed; shell startup arguments never need one. An unclosed quote
* folds the remainder into the current token (settings input stays
* forgiving); an empty quote pair yields no argument.
*/
export function splitShellArgs(input: string): string[] {
const args: string[] = []
let current = ''
let quote: '"' | "'" | null = null
let started = false
for (const ch of input) {
if (quote !== null) {
if (ch === quote) quote = null
else current += ch
continue
}
if (ch === '"' || ch === "'") {
quote = ch
started = true
continue
}
if (/\s/.test(ch)) {
if (started) {
args.push(current)
current = ''
started = false
}
continue
}
current += ch
started = true
}
if (started) args.push(current)
return args.filter(arg => arg !== '')
}