src/vs/platform/externalTerminal/node/externalTerminalService.ts

399 LOC · 162 covered · 237 uncovered · 37 ranges · 12 concepts · 10 introducers · 11 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/platform/externalTerminal/common/externalTerminal.ts · 37 LOCcommon/externalTerminal.…externalTerminalService.ts ×7 · 17 introduced LOCexternalTerminalService.…externalTerminalService.test|title=ExternalTerminalService WinTerminalService - cwd is correct regardless of case|occurrence=1, externalTerminalService.test|title=ExternalTerminalService WinTerminalService - windows terminal should open workspace directory|occurrence=1 · 0 introduced LOCexternalTerminalService.…externalTerminalService.ts ×1 · 6 introduced LOCexternalTerminalService.…externalTerminalService.test|title=ExternalTerminalService WinTerminalService - uses terminal from configuration|occurrence=1 · 0 introduced LOCexternalTerminalService.…externalTerminalService.ts ×3 · 13 introduced LOCexternalTerminalService.…externalTerminalService.ts ×1 · 3 introduced LOCexternalTerminalService.…externalTerminalService.ts ×1 · 2 introduced LOCexternalTerminalService.…externalTerminalService.ts ×3 · 10 introduced LOCexternalTerminalService.…externalTerminalService.ts ×1 · 13 introduced LOCexternalTerminalService.…externalTerminalService.ts ×1 · 13 introduced LOCexternalTerminalService.…externalTerminalService.ts ×1 · 5 introduced LOCexternalTerminalService.…externalTerminalService.ts ×18 · 117 introduced LOCexternalTerminalService.…externalTerminalService.test|title=ExternalTerminalService LinuxTerminalService - Ghostty should be spawned with working directory|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService LinuxTerminalService - Ghostty should be spawned with working directory|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService LinuxTerminalService - uses default terminal when configuration.terminal.external.linuxExec is undefined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService LinuxTerminalService - uses default terminal when configuration.terminal.external.linuxExec is undefined|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService LinuxTerminalService - uses terminal from configuration|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService LinuxTerminalService - uses terminal from configuration|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService MacTerminalService - Ghostty.app should be spawned correctly|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService MacTerminalService - Ghostty.app should be spawned correctly|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService MacTerminalService - uses default terminal when configuration.terminal.external.osxExec is undefined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService MacTerminalService - uses default terminal when configuration.terminal.external.osxExec is undefined|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService MacTerminalService - uses terminal from configuration|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService MacTerminalService - uses terminal from configuration|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService WinTerminalService - cmder should be spawned differently|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService WinTerminalService - cmder should be spawned differently|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService WinTerminalService - cwd is correct regardless of case|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService WinTerminalService - cwd is correct regardless of case|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService WinTerminalService - uses default terminal when configuration.terminal.external.windowsExec is undefined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService WinTerminalService - uses default terminal when configuration.terminal.external.windowsExec is undefined|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService WinTerminalService - uses terminal from configuration|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService WinTerminalService - uses terminal from configuration|occurrence=1externalTerminalService.…externalTerminalService.test|title=ExternalTerminalService WinTerminalService - windows terminal should open workspace directory|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/externalTerminal/test/node/externalTerminalService.test|title=ExternalTerminalService WinTerminalService - windows terminal should open workspace directory|occurrence=1externalTerminalService.…Focused file · src/vs/platform/externalTerminal/node/externalTerminalService.ts · 399 LOCnode/externalTerminalSer…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- externalTerminalService.ts ×18
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 { memoize } from '../../../base/common/decorators.js';
8 > import { FileAccess } from '../../../base/common/network.js';
9 > import * as path from '../../../base/common/path.js';
10 > import * as env from '../../../base/common/platform.js';
11 > import { sanitizeProcessEnvironment } from '../../../base/common/processes.js';
12 > import * as pfs from '../../../base/node/pfs.js';
13 > import * as processes from '../../../base/node/processes.js';
14 > import * as nls from '../../../nls.js';
15 > import { DEFAULT_TERMINAL_OSX, IExternalTerminalService, IExternalTerminalSettings, ITerminalForPlatform } from '../common/externalTerminal.js';
16 > import { ITerminalEnvironment } from '../../terminal/common/terminal.js';
17 >
18 > const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console");
19 >
20 > abstract class ExternalTerminalService {
21 > public _serviceBrand: undefined;
22 >
23 > async getDefaultTerminalForPlatforms(): Promise<ITerminalForPlatform> {
24 return {
25 windows: WindowsExternalTerminalService.getDefaultTerminalWindows(),
26 linux: await LinuxExternalTerminalService.getDefaultTerminalLinuxReady(),
27 osx: DEFAULT_TERMINAL_OSX
28 };
29 }
31 >
32 > export class WindowsExternalTerminalService extends ExternalTerminalService implements IExternalTerminalService {
33 > private static readonly CMD = 'cmd.exe';
34 > private static _DEFAULT_TERMINAL_WINDOWS: string;
35 >
36 > public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
37 return this.spawnTerminal(cp, configuration, processes.getWindowsShell(), cwd);
38 }
40 > public spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, command: string, cwd?: string): Promise<void> {
41 > const exec = configuration.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows(); externalTerminalService.ts ×3
42 >
43 > // Make the drive letter uppercase on Windows (see #9448)
44 > if (cwd && cwd[1] === ':') {
45 > cwd = cwd[0].toUpperCase() + cwd.substr(1); externalTerminalService.ts ×1
46 > }
48 > // cmder ignores the environment cwd and instead opts to always open in %USERPROFILE%
49 > // unless otherwise specified
50 > const basename = path.basename(exec, '.exe').toLowerCase();
51 > if (basename === 'cmder') {
52 > spawner.spawn(exec, cwd ? [cwd] : undefined); externalTerminalService.ts ×1
53 > return Promise.resolve(undefined);
54 > }
56 > const cmdArgs = ['/c', 'start', '/wait'];
57 > if (exec.indexOf(' ') >= 0) {
58 // The "" argument is the window title. Without this, exec doesn't work when the path
59 // contains spaces. #6590
60 // Title is Execution Path. #220129
61 cmdArgs.push(exec);
62 }
63 > cmdArgs.push(exec); externalTerminalService.ts ×3
64 > // Add starting directory parameter for Windows Terminal (see #90734)
65 > if (basename === 'wt') {
66 cmdArgs.push('-d .');
67 }
69 > return new Promise<void>((c, e) => {
70 > const env = getSanitizedEnvironment(process);
71 > const child = spawner.spawn(command, cmdArgs, { cwd, env, detached: true });
72 > child.on('error', e);
73 > child.on('exit', () => c());
74 > });
77 > public async runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise<number | undefined> {
78 const exec = settings.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows();
79 const wt = await WindowsExternalTerminalService.getWtExePath();
80
81 return new Promise<number | undefined>((resolve, reject) => {
82
83 const title = `"${dir} - ${TERMINAL_TITLE}"`;
84 const command = `"${args.join('" "')}" & pause`; // use '|' to only pause on non-zero exit code
85
86 // merge environment variables into a copy of the process.env
87 const env = Object.assign({}, getSanitizedEnvironment(process), envVars);
88
89 // delete environment variables that have a null value
90 Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]);
91
92 const options = {
93 cwd: dir,
94 env: env,
95 windowsVerbatimArguments: true
96 };
97
98 let spawnExec: string;
99 let cmdArgs: string[];
100
101 if (path.basename(exec, '.exe') === 'wt') {
102 // Handle Windows Terminal specially; -d to set the cwd and run a cmd.exe instance
103 // inside it
104 spawnExec = exec;
105 cmdArgs = ['-d', '.', WindowsExternalTerminalService.CMD, '/c', command];
106 } else if (wt) {
107 // prefer to use the window terminal to spawn if it's available instead
108 // of start, since that allows ctrl+c handling (#81322)
109 spawnExec = wt;
110 cmdArgs = ['-d', '.', exec, '/c', command];
111 } else {
112 spawnExec = WindowsExternalTerminalService.CMD;
113 cmdArgs = ['/c', 'start', title, '/wait', exec, '/c', `"${command}"`];
114 }
115
116 const cmd = cp.spawn(spawnExec, cmdArgs, options);
117
118 cmd.on('error', err => {
119 reject(improveError(err));
120 });
121
122 resolve(undefined);
123 });
124 }
126 > public static getDefaultTerminalWindows(): string {
127 > if (!WindowsExternalTerminalService._DEFAULT_TERMINAL_WINDOWS) { externalTerminalService.ts ×1
128 > const isWoW64 = !!process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
129 > WindowsExternalTerminalService._DEFAULT_TERMINAL_WINDOWS = `${process.env.windir ? process.env.windir : 'C:\\Windows'}\\${isWoW64 ? 'Sysnative' : 'System32'}\\cmd.exe`;
130 > }
131 > return WindowsExternalTerminalService._DEFAULT_TERMINAL_WINDOWS;
132 > }
134 > @memoize
135 > private static async getWtExePath() {
136 try {
137 return await processes.findExecutable('wt');
138 } catch {
139 return undefined;
140 }
141 }
143 >
144 > export class MacExternalTerminalService extends ExternalTerminalService implements IExternalTerminalService {
145 > private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X
146 >
147 > public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
148 return this.spawnTerminal(cp, configuration, cwd);
149 }
151 > public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise<number | undefined> {
152
153 const terminalApp = settings.osxExec || DEFAULT_TERMINAL_OSX;
154
155 return new Promise<number | undefined>((resolve, reject) => {
156
157 if (terminalApp === DEFAULT_TERMINAL_OSX || terminalApp === 'iTerm.app') {
158
159 // On OS X we launch an AppleScript that creates (or reuses) a Terminal window
160 // and then launches the program inside that window.
161
162 const script = terminalApp === DEFAULT_TERMINAL_OSX ? 'TerminalHelper' : 'iTermHelper';
163 const scriptpath = FileAccess.asFileUri(`vs/workbench/contrib/externalTerminal/node/${script}.scpt`).fsPath;
164
165 const osaArgs = [
166 scriptpath,
167 '-t', title || TERMINAL_TITLE,
168 '-w', dir,
169 ];
170
171 for (const a of args) {
172 osaArgs.push('-a');
173 osaArgs.push(a);
174 }
175
176 if (envVars) {
177 // merge environment variables into a copy of the process.env
178 const env = Object.assign({}, getSanitizedEnvironment(process), envVars);
179
180 for (const key in env) {
181 const value = env[key];
182 if (value === null) {
183 osaArgs.push('-u');
184 osaArgs.push(key);
185 } else {
186 osaArgs.push('-e');
187 osaArgs.push(`${key}=${value}`);
188 }
189 }
190 }
191
192 const osa = cp.spawn(MacExternalTerminalService.OSASCRIPT, osaArgs);
193 setupSpawnErrorHandling(osa, resolve, reject, terminalApp);
194 } else if (terminalApp === 'Ghostty.app') {
195 // Ghostty uses CLI flags directly instead of AppleScript like Mac Terminal and iTerm
196 // Note: -na is required (not just -a) because we need to spawn a new instance that
197 // receives our --args. With just -a, if Ghostty is already running, open will
198 // activate the existing instance and ignore --args entirely.
199 const env = Object.assign({}, getSanitizedEnvironment(process), envVars);
200 const openArgs = ['-na', 'Ghostty.app', '--args'];
201 openArgs.push('--working-directory=' + dir);
202 openArgs.push('--wait-after-command=true');
203 openArgs.push('-e', ...args);
204
205 const cmd = cp.spawn('/usr/bin/open', openArgs, { env });
206 setupSpawnErrorHandling(cmd, resolve, reject, terminalApp);
207 } else {
208 reject(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp)));
209 }
210 });
211 }
213 > spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
214 > const terminalApp = configuration.osxExec || DEFAULT_TERMINAL_OSX; externalTerminalService.ts ×1
215 >
216 > return new Promise<void>((c, e) => {
217 > const args = ['-a', terminalApp];
218 > if (cwd) {
219 > args.push(cwd);
220 > }
221 > const env = getSanitizedEnvironment(process);
222 > const child = spawner.spawn('/usr/bin/open', args, { cwd, env });
223 > child.on('error', e);
224 > child.on('exit', () => c());
225 > });
226 > }
228 >
229 > export class LinuxExternalTerminalService extends ExternalTerminalService implements IExternalTerminalService {
230 >
231 > private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue...");
232 >
233 > public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
234 return this.spawnTerminal(cp, configuration, cwd);
235 }
237 > public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise<number | undefined> {
238
239 const execPromise = settings.linuxExec ? Promise.resolve(settings.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady();
240
241 return new Promise<number | undefined>((resolve, reject) => {
242 execPromise.then(exec => {
243 const basename = path.basename(exec).toLowerCase();
244 if (basename === 'ghostty') {
245 const ghosttyArgs: string[] = [];
246 if (dir) {
247 ghosttyArgs.push(`--working-directory=${dir}`);
248 }
249 ghosttyArgs.push('--wait-after-command=true');
250 if (args.length) {
251 ghosttyArgs.push('-e', ...args);
252 }
253 LinuxExternalTerminalService.spawnTerminalWithEnv(exec, ghosttyArgs, dir, envVars, resolve, reject);
254 return;
255 }
256
257 const termArgs: string[] = [];
258 //termArgs.push('--title');
259 //termArgs.push(`"${TERMINAL_TITLE}"`);
260 if (exec.indexOf('gnome-terminal') >= 0) {
261 termArgs.push('-x');
262 } else {
263 termArgs.push('-e');
264 }
265 termArgs.push('bash');
266 termArgs.push('-c');
267
268 const bashCommand = `${quote(args)}; echo; read -p "${LinuxExternalTerminalService.WAIT_MESSAGE}" -n1;`;
269 termArgs.push(`''${bashCommand}''`); // wrapping argument in two sets of ' because node is so "friendly" that it removes one set...
270
271
272 LinuxExternalTerminalService.spawnTerminalWithEnv(exec, termArgs, dir, envVars, resolve, reject);
273 });
274 });
275 }
277 > private static spawnTerminalWithEnv(
278 exec: string,
279 args: string[],
280 dir: string,
281 envVars: ITerminalEnvironment,
282 resolve: (value: number | PromiseLike<number | undefined> | undefined) => void,
283 reject: (reason?: unknown) => void
284 ): void {
285 const env = Object.assign({}, getSanitizedEnvironment(process), envVars);
286
287 // delete environment variables that have a null value
288 Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]);
289
290 const cmd = cp.spawn(exec, args, { cwd: dir, env });
291 setupSpawnErrorHandling(cmd, resolve, reject, exec);
292 }
294 > private static _DEFAULT_TERMINAL_LINUX_READY: Promise<string>;
295 >
296 > public static async getDefaultTerminalLinuxReady(): Promise<string> {
297 > if (!LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY) { externalTerminalService.ts ×7
298 > if (!env.isLinux) {
299 LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY = Promise.resolve('xterm');
301 > const isDebian = await pfs.Promises.exists('/etc/debian_version');
302 > LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY = new Promise<string>(r => {
303 > if (isDebian) {
304 r('x-terminal-emulator');
305 > } else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') { externalTerminalService.ts ×7
306 r('gnome-terminal');
307 > } else if (process.env.DESKTOP_SESSION === 'kde-plasma') { externalTerminalService.ts ×7
308 r('konsole');
309 > } else if (process.env.COLORTERM) { externalTerminalService.ts ×7
310 r(process.env.COLORTERM);
311 > } else if (process.env.TERM) { externalTerminalService.ts ×7
312 > r(process.env.TERM);
313 > } else {
314 r('xterm');
315 }
317 > }
318 > }
319 > return LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY;
320 > }
322 > spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
323 > const execPromise = configuration.linuxExec ? Promise.resolve(configuration.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady(); externalTerminalService.ts ×1
324 >
325 > return new Promise<void>((c, e) => {
326 > execPromise.then(exec => {
327 > const env = getSanitizedEnvironment(process);
328 > const basename = path.basename(exec).toLowerCase();
329 > const args = basename === 'ghostty' && cwd ? [`--working-directory=${cwd}`] : [];
330 > const child = spawner.spawn(exec, args, { cwd, env });
331 > child.on('error', e);
332 > child.on('exit', () => c());
333 > });
334 > });
335 > }
337 >
338 > function getSanitizedEnvironment(process: NodeJS.Process) { externalTerminalService.ts ×1
339 > const env = { ...process.env };
340 > sanitizeProcessEnvironment(env);
341 > return env;
342 > }
344 > /**
345 > * tries to turn OS errors into more meaningful error messages
346 > */
347 function improveError(err: Error & { errno?: string; path?: string }): Error {
348 if (err.errno === 'ENOENT' && err.path) {
349 return new Error(nls.localize('ext.term.app.not.found', "can't find terminal application '{0}'", err.path));
350 }
351 return err;
352 }
354 > /**
355 > * Attaches error handling to a spawned child process for terminal launching.
356 > */
357 function setupSpawnErrorHandling(
358 cmd: cp.ChildProcess,
359 resolve: (value: number | PromiseLike<number | undefined> | undefined) => void,
360 // eslint-disable-next-line @typescript-eslint/no-explicit-any
361 reject: (reason?: any) => void,
362 terminalApp: string
363 ): void {
364 let stderr = '';
365 cmd.on('error', err => {
366 reject(improveError(err));
367 });
368 cmd.stderr?.on('data', (data) => {
369 stderr += data.toString();
370 });
371 cmd.on('exit', (code: number) => {
372 if (code === 0) {
373 resolve(undefined);
374 } else {
375 if (stderr) {
376 const lines = stderr.split('\n', 1);
377 reject(new Error(lines[0]));
378 } else {
379 reject(new Error(nls.localize('terminal.launch.failed', "Launching '{0}' failed with exit code {1}", terminalApp, code)));
380 }
381 }
382 });
383 }
385 > /**
386 > * Quote args if necessary and combine into a space separated string.
387 > */
388 function quote(args: string[]): string {
389 let r = '';
390 for (const a of args) {
391 if (a.indexOf(' ') >= 0) {
392 r += '"' + a + '"';
393 } else {
394 r += a;
395 }
396 r += ' ';
397 }
398 return r;
399 }