Path: blob/main/src/vs/workbench/contrib/debug/node/terminals.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as cp from 'child_process';6import { getDriveLetter } from '../../../../base/common/extpath.js';7import * as platform from '../../../../base/common/platform.js';89function spawnAsPromised(command: string, args: string[]): Promise<string> {10return new Promise((resolve, reject) => {11let stdout = '';12const child = cp.spawn(command, args);13if (child.pid) {14child.stdout.on('data', (data: Buffer) => {15stdout += data.toString();16});17}18child.on('error', err => {19reject(err);20});21child.on('close', code => {22resolve(stdout);23});24});25}2627export async function hasChildProcesses(processId: number | undefined): Promise<boolean> {28if (processId) {2930// if shell has at least one child process, assume that shell is busy31if (platform.isWindows) {32const windowsProcessTree = await import('@vscode/windows-process-tree');33return new Promise<boolean>(resolve => {34windowsProcessTree.getProcessTree(processId, processTree => {35resolve(!!processTree && processTree.children.length > 0);36});37});38} else {39return spawnAsPromised('/usr/bin/pgrep', ['-lP', String(processId)]).then(stdout => {40const r = stdout.trim();41if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #4368342return false;43} else {44return true;45}46}, error => {47return true;48});49}50}51// fall back to safe side52return Promise.resolve(true);53}5455const enum ShellType { cmd, powershell, bash }565758export function prepareCommand(shell: string, args: string[], argsCanBeInterpretedByShell: boolean, cwd?: string, env?: { [key: string]: string | null }): string {5960shell = shell.trim().toLowerCase();6162// try to determine the shell type63let shellType;64if (shell.indexOf('powershell') >= 0 || shell.indexOf('pwsh') >= 0) {65shellType = ShellType.powershell;66} else if (shell.indexOf('cmd.exe') >= 0) {67shellType = ShellType.cmd;68} else if (shell.indexOf('bash') >= 0) {69shellType = ShellType.bash;70} else if (platform.isWindows) {71shellType = ShellType.cmd; // pick a good default for Windows72} else {73shellType = ShellType.bash; // pick a good default for anything else74}7576let quote: (s: string) => string;77// begin command with a space to avoid polluting shell history78let command = ' ';7980switch (shellType) {8182case ShellType.powershell:8384quote = (s: string) => {85s = s.replace(/\'/g, '\'\'');86if (s.length > 0 && s.charAt(s.length - 1) === '\\') {87return `'${s}\\'`;88}89return `'${s}'`;90};9192if (cwd) {93const driveLetter = getDriveLetter(cwd);94if (driveLetter) {95command += `${driveLetter}:; `;96}97command += `cd ${quote(cwd)}; `;98}99if (env) {100for (const key in env) {101const value = env[key];102if (value === null) {103command += `Remove-Item env:${key}; `;104} else {105command += `\${env:${key}}='${value}'; `;106}107}108}109if (args.length > 0) {110const arg = args.shift()!;111const cmd = argsCanBeInterpretedByShell ? arg : quote(arg);112command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `;113for (const a of args) {114command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a);115command += ' ';116}117}118break;119120case ShellType.cmd:121122quote = (s: string) => {123// Note: Wrapping in cmd /C "..." complicates the escaping.124// cmd /C "node -e "console.log(process.argv)" """A^>0"""" # prints "A>0"125// cmd /C "node -e "console.log(process.argv)" "foo^> bar"" # prints foo> bar126// Outside of the cmd /C, it could be a simple quoting, but here, the ^ is needed too127s = s.replace(/\"/g, '""');128s = s.replace(/([><!^&|])/g, '^$1');129return (' "'.split('').some(char => s.includes(char)) || s.length === 0) ? `"${s}"` : s;130};131132if (cwd) {133const driveLetter = getDriveLetter(cwd);134if (driveLetter) {135command += `${driveLetter}: && `;136}137command += `cd ${quote(cwd)} && `;138}139if (env) {140command += 'cmd /C "';141for (const key in env) {142let value = env[key];143if (value === null) {144command += `set "${key}=" && `;145} else {146value = value.replace(/[&^|<>]/g, s => `^${s}`);147command += `set "${key}=${value}" && `;148}149}150}151for (const a of args) {152command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a);153command += ' ';154}155if (env) {156command += '"';157}158break;159160case ShellType.bash: {161162quote = (s: string) => {163s = s.replace(/(["'\\\$!><#()\[\]*&^| ;{}?`])/g, '\\$1');164return s.length === 0 ? `""` : s;165};166167const hardQuote = (s: string) => {168return /[^\w@%\/+=,.:^-]/.test(s) ? `'${s.replace(/'/g, '\'\\\'\'')}'` : s;169};170171if (cwd) {172command += `cd ${quote(cwd)} ; `;173}174if (env) {175command += '/usr/bin/env';176for (const key in env) {177const value = env[key];178if (value === null) {179command += ` -u ${hardQuote(key)}`;180} else {181command += ` ${hardQuote(`${key}=${value}`)}`;182}183}184command += ' ';185}186for (const a of args) {187command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a);188command += ' ';189}190break;191}192}193194return command;195}196197198