Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/node/terminals.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as cp from 'child_process';
7
import { getDriveLetter } from '../../../../base/common/extpath.js';
8
import * as platform from '../../../../base/common/platform.js';
9
10
function spawnAsPromised(command: string, args: string[]): Promise<string> {
11
return new Promise((resolve, reject) => {
12
let stdout = '';
13
const child = cp.spawn(command, args);
14
if (child.pid) {
15
child.stdout.on('data', (data: Buffer) => {
16
stdout += data.toString();
17
});
18
}
19
child.on('error', err => {
20
reject(err);
21
});
22
child.on('close', code => {
23
resolve(stdout);
24
});
25
});
26
}
27
28
export async function hasChildProcesses(processId: number | undefined): Promise<boolean> {
29
if (processId) {
30
31
// if shell has at least one child process, assume that shell is busy
32
if (platform.isWindows) {
33
const windowsProcessTree = await import('@vscode/windows-process-tree');
34
return new Promise<boolean>(resolve => {
35
windowsProcessTree.getProcessTree(processId, processTree => {
36
resolve(!!processTree && processTree.children.length > 0);
37
});
38
});
39
} else {
40
return spawnAsPromised('/usr/bin/pgrep', ['-lP', String(processId)]).then(stdout => {
41
const r = stdout.trim();
42
if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683
43
return false;
44
} else {
45
return true;
46
}
47
}, error => {
48
return true;
49
});
50
}
51
}
52
// fall back to safe side
53
return Promise.resolve(true);
54
}
55
56
const enum ShellType { cmd, powershell, bash }
57
58
59
export function prepareCommand(shell: string, args: string[], argsCanBeInterpretedByShell: boolean, cwd?: string, env?: { [key: string]: string | null }): string {
60
61
shell = shell.trim().toLowerCase();
62
63
// try to determine the shell type
64
let shellType;
65
if (shell.indexOf('powershell') >= 0 || shell.indexOf('pwsh') >= 0) {
66
shellType = ShellType.powershell;
67
} else if (shell.indexOf('cmd.exe') >= 0) {
68
shellType = ShellType.cmd;
69
} else if (shell.indexOf('bash') >= 0) {
70
shellType = ShellType.bash;
71
} else if (platform.isWindows) {
72
shellType = ShellType.cmd; // pick a good default for Windows
73
} else {
74
shellType = ShellType.bash; // pick a good default for anything else
75
}
76
77
let quote: (s: string) => string;
78
// begin command with a space to avoid polluting shell history
79
let command = ' ';
80
81
switch (shellType) {
82
83
case ShellType.powershell:
84
85
quote = (s: string) => {
86
s = s.replace(/\'/g, '\'\'');
87
if (s.length > 0 && s.charAt(s.length - 1) === '\\') {
88
return `'${s}\\'`;
89
}
90
return `'${s}'`;
91
};
92
93
if (cwd) {
94
const driveLetter = getDriveLetter(cwd);
95
if (driveLetter) {
96
command += `${driveLetter}:; `;
97
}
98
command += `cd ${quote(cwd)}; `;
99
}
100
if (env) {
101
for (const key in env) {
102
const value = env[key];
103
if (value === null) {
104
command += `Remove-Item env:${key}; `;
105
} else {
106
command += `\${env:${key}}='${value}'; `;
107
}
108
}
109
}
110
if (args.length > 0) {
111
const arg = args.shift()!;
112
const cmd = argsCanBeInterpretedByShell ? arg : quote(arg);
113
command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `;
114
for (const a of args) {
115
command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a);
116
command += ' ';
117
}
118
}
119
break;
120
121
case ShellType.cmd:
122
123
quote = (s: string) => {
124
// Note: Wrapping in cmd /C "..." complicates the escaping.
125
// cmd /C "node -e "console.log(process.argv)" """A^>0"""" # prints "A>0"
126
// cmd /C "node -e "console.log(process.argv)" "foo^> bar"" # prints foo> bar
127
// Outside of the cmd /C, it could be a simple quoting, but here, the ^ is needed too
128
s = s.replace(/\"/g, '""');
129
s = s.replace(/([><!^&|])/g, '^$1');
130
return (' "'.split('').some(char => s.includes(char)) || s.length === 0) ? `"${s}"` : s;
131
};
132
133
if (cwd) {
134
const driveLetter = getDriveLetter(cwd);
135
if (driveLetter) {
136
command += `${driveLetter}: && `;
137
}
138
command += `cd ${quote(cwd)} && `;
139
}
140
if (env) {
141
command += 'cmd /C "';
142
for (const key in env) {
143
let value = env[key];
144
if (value === null) {
145
command += `set "${key}=" && `;
146
} else {
147
value = value.replace(/[&^|<>]/g, s => `^${s}`);
148
command += `set "${key}=${value}" && `;
149
}
150
}
151
}
152
for (const a of args) {
153
command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a);
154
command += ' ';
155
}
156
if (env) {
157
command += '"';
158
}
159
break;
160
161
case ShellType.bash: {
162
163
quote = (s: string) => {
164
s = s.replace(/(["'\\\$!><#()\[\]*&^| ;{}?`])/g, '\\$1');
165
return s.length === 0 ? `""` : s;
166
};
167
168
const hardQuote = (s: string) => {
169
return /[^\w@%\/+=,.:^-]/.test(s) ? `'${s.replace(/'/g, '\'\\\'\'')}'` : s;
170
};
171
172
if (cwd) {
173
command += `cd ${quote(cwd)} ; `;
174
}
175
if (env) {
176
command += '/usr/bin/env';
177
for (const key in env) {
178
const value = env[key];
179
if (value === null) {
180
command += ` -u ${hardQuote(key)}`;
181
} else {
182
command += ` ${hardQuote(`${key}=${value}`)}`;
183
}
184
}
185
command += ' ';
186
}
187
for (const a of args) {
188
command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a);
189
command += ' ';
190
}
191
break;
192
}
193
}
194
195
return command;
196
}
197
198