src/vs/workbench/api/common/extHostTerminalShellIntegration.ts

500 LOC · 340 covered · 160 uncovered · 54 ranges · 7 concepts · 7 introducers · 8 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/base/common/async.ts · 2749 LOCcommon/async.tsextHostTerminalShellIntegration.ts ×3 · 13 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×6 · 28 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×13 · 51 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×1 · 4 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×1 · 3 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×1 · 2 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.ts ×29 · 253 introduced LOCextHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration different execution unexpectedly ended|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration different execution unexpectedly ended|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand 4 multi-line commands with output|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand 4 multi-line commands with output|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand ^C to clear previous command|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand ^C to clear previous command|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command comment followed by long second command|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command comment followed by long second command|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command line|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command line|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command with long second command|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration executeCommand multi-line command with long second command|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration no end event|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration no end event|occurrence=1extHostTerminalShellInte…extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration simple execution|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostTerminalShellIntegration.test|title=InternalTerminalShellIntegration simple execution|occurrence=1extHostTerminalShellInte…Focused file · src/vs/workbench/api/common/extHostTerminalShellIntegration.ts · 500 LOCcommon/extHostTerminalSh…

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 > /*--------------------------------------------------------------------------------------------- extHostTerminalShellIntegration.ts ×29
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 type * as vscode from 'vscode';
7 > import { TerminalShellExecutionCommandLineConfidence } from './extHostTypes.js';
8 > import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
9 > import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
10 > import { MainContext, type ExtHostTerminalShellIntegrationShape, type MainThreadTerminalShellIntegrationShape } from './extHost.protocol.js';
11 > import { IExtHostRpcService } from './extHostRpcService.js';
12 > import { IExtHostTerminalService } from './extHostTerminalService.js';
13 > import { Emitter, type Event } from '../../../base/common/event.js';
14 > import { URI } from '../../../base/common/uri.js';
15 > import { AsyncIterableObject, Barrier, type AsyncIterableEmitter } from '../../../base/common/async.js';
16 >
17 > export interface IExtHostTerminalShellIntegration extends ExtHostTerminalShellIntegrationShape {
18 > readonly _serviceBrand: undefined;
19 >
20 > readonly onDidChangeTerminalShellIntegration: Event<vscode.TerminalShellIntegrationChangeEvent>;
21 > readonly onDidStartTerminalShellExecution: Event<vscode.TerminalShellExecutionStartEvent>;
22 > readonly onDidEndTerminalShellExecution: Event<vscode.TerminalShellExecutionEndEvent>;
23 > }
24 > export const IExtHostTerminalShellIntegration = createDecorator<IExtHostTerminalShellIntegration>('IExtHostTerminalShellIntegration');
25 >
26 > export class ExtHostTerminalShellIntegration extends Disposable implements IExtHostTerminalShellIntegration {
27 >
28 > readonly _serviceBrand: undefined;
29 >
30 > protected _proxy: MainThreadTerminalShellIntegrationShape;
31 >
32 > private _activeShellIntegrations: Map</*instanceId*/number, InternalTerminalShellIntegration> = new Map();
33 >
34 > protected readonly _onDidChangeTerminalShellIntegration = this._register(new Emitter<vscode.TerminalShellIntegrationChangeEvent>());
35 > readonly onDidChangeTerminalShellIntegration = this._onDidChangeTerminalShellIntegration.event;
36 > protected readonly _onDidStartTerminalShellExecution = this._register(new Emitter<vscode.TerminalShellExecutionStartEvent>());
37 > readonly onDidStartTerminalShellExecution = this._onDidStartTerminalShellExecution.event;
38 > protected readonly _onDidEndTerminalShellExecution = this._register(new Emitter<vscode.TerminalShellExecutionEndEvent>());
39 > readonly onDidEndTerminalShellExecution = this._onDidEndTerminalShellExecution.event;
40 >
41 > constructor(
42 @IExtHostRpcService extHostRpc: IExtHostRpcService,
43 @IExtHostTerminalService private readonly _extHostTerminalService: IExtHostTerminalService,
44 ) {
45 super();
46
47 this._proxy = extHostRpc.getProxy(MainContext.MainThreadTerminalShellIntegration);
48
49 // Clean up listeners
50 this._register(toDisposable(() => {
51 for (const [_, integration] of this._activeShellIntegrations) {
52 integration.dispose();
53 }
54 this._activeShellIntegrations.clear();
55 }));
56
57 // Convenient test code:
58 // this.onDidChangeTerminalShellIntegration(e => {
59 // console.log('*** onDidChangeTerminalShellIntegration', e);
60 // });
61 // this.onDidStartTerminalShellExecution(async e => {
62 // console.log('*** onDidStartTerminalShellExecution', e);
63 // // new Promise<void>(r => {
64 // // (async () => {
65 // // for await (const d of e.execution.read()) {
66 // // console.log('data2', d);
67 // // }
68 // // })();
69 // // });
70 // for await (const d of e.execution.read()) {
71 // console.log('data', d);
72 // }
73 // });
74 // this.onDidEndTerminalShellExecution(e => {
75 // console.log('*** onDidEndTerminalShellExecution', e);
76 // });
77 // setTimeout(() => {
78 // console.log('before executeCommand(\"echo hello\")');
79 // Array.from(this._activeShellIntegrations.values())[0].value.executeCommand('echo hello');
80 // console.log('after executeCommand(\"echo hello\")');
81 // }, 4000);
82 }
84 > public $shellIntegrationChange(instanceId: number, supportsExecuteCommandApi: boolean): void {
85 const terminal = this._extHostTerminalService.getTerminalById(instanceId);
86 if (!terminal) {
87 return;
88 }
89
90 const apiTerminal = terminal.value;
91 let shellIntegration = this._activeShellIntegrations.get(instanceId);
92 if (!shellIntegration) {
93 shellIntegration = new InternalTerminalShellIntegration(terminal.value, supportsExecuteCommandApi, this._onDidStartTerminalShellExecution);
94 this._activeShellIntegrations.set(instanceId, shellIntegration);
95 shellIntegration.store.add(terminal.onWillDispose(() => this._activeShellIntegrations.get(instanceId)?.dispose()));
96 shellIntegration.store.add(shellIntegration.onDidRequestShellExecution(commandLine => this._proxy.$executeCommand(instanceId, commandLine)));
97 shellIntegration.store.add(shellIntegration.onDidRequestEndExecution(e => this._onDidEndTerminalShellExecution.fire(e)));
98 shellIntegration.store.add(shellIntegration.onDidRequestChangeShellIntegration(e => this._onDidChangeTerminalShellIntegration.fire(e)));
99 terminal.shellIntegration = shellIntegration.value;
100 }
101 this._onDidChangeTerminalShellIntegration.fire({
102 terminal: apiTerminal,
103 shellIntegration: shellIntegration.value
104 });
105 }
107 > public $shellExecutionStart(instanceId: number, supportsExecuteCommandApi: boolean, commandLineValue: string, commandLineConfidence: TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, cwd: string | undefined): void {
108 // Force shellIntegration creation if it hasn't been created yet, this could when events
109 // don't come through on startup
110 if (!this._activeShellIntegrations.has(instanceId)) {
111 this.$shellIntegrationChange(instanceId, supportsExecuteCommandApi);
112 }
113 const commandLine: vscode.TerminalShellExecutionCommandLine = {
114 value: commandLineValue,
115 confidence: commandLineConfidence,
116 isTrusted
117 };
118 this._activeShellIntegrations.get(instanceId)?.startShellExecution(commandLine, this._convertCwdToUri(cwd));
119 }
121 > public $shellExecutionEnd(instanceId: number, commandLineValue: string, commandLineConfidence: TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, exitCode: number | undefined): void {
122 const commandLine: vscode.TerminalShellExecutionCommandLine = {
123 value: commandLineValue,
124 confidence: commandLineConfidence,
125 isTrusted
126 };
127 this._activeShellIntegrations.get(instanceId)?.endShellExecution(commandLine, exitCode);
128 }
130 > public $shellExecutionData(instanceId: number, data: string): void {
131 this._activeShellIntegrations.get(instanceId)?.emitData(data);
132 }
134 > public $shellEnvChange(instanceId: number, shellEnvKeys: string[], shellEnvValues: string[], isTrusted: boolean): void {
135 this._activeShellIntegrations.get(instanceId)?.setEnv(shellEnvKeys, shellEnvValues, isTrusted);
136 }
138 > public $cwdChange(instanceId: number, cwd: string | undefined): void {
139 this._activeShellIntegrations.get(instanceId)?.setCwd(this._convertCwdToUri(cwd));
140 }
142 > public $closeTerminal(instanceId: number): void {
143 this._activeShellIntegrations.get(instanceId)?.dispose();
144 this._activeShellIntegrations.delete(instanceId);
145 }
147 > private _convertCwdToUri(cwd: string | undefined): URI | undefined {
148 // IMPORTANT: cwd is provided to the exthost as a string from the renderer and only
149 // converted to a URI on the machine in which the pty is hosted on. The string version of
150 // the cwd is used from the renderer such that it's access is synchronous and its event
151 // comes through in order relative to other shell integration events.
152 return cwd ? URI.file(cwd) : undefined;
153 }
155 >
156 > interface IExecutionProperties {
157 > isMultiLine: boolean;
158 > unresolvedCommandLines: string[] | undefined;
159 > }
160 >
161 > export class InternalTerminalShellIntegration extends Disposable {
162 > private _pendingExecutions: InternalTerminalShellExecution[] = [];
163 > private _pendingEndingExecution: InternalTerminalShellExecution | undefined;
164 >
165 > private _currentExecutionProperties: IExecutionProperties | undefined;
166 > private _currentExecution: InternalTerminalShellExecution | undefined;
167 > get currentExecution(): InternalTerminalShellExecution | undefined { return this._currentExecution; }
168 >
169 >
170 > private _env: vscode.TerminalShellIntegrationEnvironment | undefined;
171 > private _cwd: URI | undefined;
172 >
173 > readonly store: DisposableStore = this._register(new DisposableStore());
174 >
175 > readonly value: vscode.TerminalShellIntegration;
176 >
177 > protected readonly _onDidRequestChangeShellIntegration = this._register(new Emitter<vscode.TerminalShellIntegrationChangeEvent>());
178 > readonly onDidRequestChangeShellIntegration = this._onDidRequestChangeShellIntegration.event;
179 > protected readonly _onDidRequestShellExecution = this._register(new Emitter<string>());
180 > readonly onDidRequestShellExecution = this._onDidRequestShellExecution.event;
181 > protected readonly _onDidRequestEndExecution = this._register(new Emitter<vscode.TerminalShellExecutionEndEvent>());
182 > readonly onDidRequestEndExecution = this._onDidRequestEndExecution.event;
183 > protected readonly _onDidRequestNewExecution = this._register(new Emitter<string>());
184 > readonly onDidRequestNewExecution = this._onDidRequestNewExecution.event;
185 >
186 > constructor(
187 > private readonly _terminal: vscode.Terminal,
188 > supportsExecuteCommandApi: boolean,
189 > private readonly _onDidStartTerminalShellExecution: Emitter<vscode.TerminalShellExecutionStartEvent>
190 > ) {
191 > super();
192 >
193 > const that = this;
194 > this.value = {
195 > get cwd(): URI | undefined {
196 return that._cwd;
197 },
198 > get env(): vscode.TerminalShellIntegrationEnvironment | undefined { extHostTerminalShellIntegration.ts ×29
199 if (!that._env) {
200 return undefined;
201 }
202 return Object.freeze({
203 isTrusted: that._env.isTrusted,
204 value: Object.freeze({ ...that._env.value })
205 });
206 },
207 > // executeCommand(commandLine: string): vscode.TerminalShellExecution; extHostTerminalShellIntegration.ts ×29
208 > // executeCommand(executable: string, args: string[]): vscode.TerminalShellExecution;
209 > executeCommand(commandLineOrExecutable: string, args?: string[]): vscode.TerminalShellExecution {
210 if (!supportsExecuteCommandApi) {
211 throw new Error('This terminal does not support the executeCommand API.');
212 }
213 let commandLineValue = commandLineOrExecutable;
214 if (args) {
215 for (const arg of args) {
216 const wrapInQuotes = !arg.match(/["'`]/) && arg.match(/\s/);
217 if (wrapInQuotes) {
218 commandLineValue += ` "${arg}"`;
219 } else {
220 commandLineValue += ` ${arg}`;
221 }
222 }
223 }
224
225 that._onDidRequestShellExecution.fire(commandLineValue);
226 // Fire the event in a microtask to allow the extension to use the execution before
227 // the start event fires
228 const commandLine: vscode.TerminalShellExecutionCommandLine = {
229 value: commandLineValue,
230 confidence: TerminalShellExecutionCommandLineConfidence.High,
231 isTrusted: true
232 };
233 const execution = that.requestNewShellExecution(commandLine, that._cwd).value;
234 return execution;
235 }
237 > }
238 >
239 > requestNewShellExecution(commandLine: vscode.TerminalShellExecutionCommandLine, cwd: URI | undefined) {
240 > const execution = new InternalTerminalShellExecution(commandLine, cwd ?? this._cwd); extHostTerminalShellIntegration.ts ×13
241 > const unresolvedCommandLines = splitAndSanitizeCommandLine(commandLine.value);
242 > if (unresolvedCommandLines.length > 1) {
243 > this._currentExecutionProperties = { extHostTerminalShellIntegration.ts ×6
244 > isMultiLine: true,
245 > unresolvedCommandLines: splitAndSanitizeCommandLine(commandLine.value),
246 > };
247 > }
248 > this._pendingExecutions.push(execution); extHostTerminalShellIntegration.ts ×13
249 > this._onDidRequestNewExecution.fire(commandLine.value);
250 > return execution;
251 > }
253 > startShellExecution(commandLine: vscode.TerminalShellExecutionCommandLine, cwd: URI | undefined): undefined {
254 > // Since an execution is starting, fire the end event for any execution that is awaiting to
255 > // end. When this happens it means that the data stream may not be flushed and therefore may
256 > // fire events after the end event.
257 > if (this._pendingEndingExecution) {
258 > this._onDidRequestEndExecution.fire({ terminal: this._terminal, shellIntegration: this.value, execution: this._pendingEndingExecution.value, exitCode: undefined }); extHostTerminalShellIntegration.ts ×3
259 > this._pendingEndingExecution = undefined;
260 > }
262 > if (this._currentExecution) {
263 > // If the current execution is multi-line, check if this command line is part of it. extHostTerminalShellIntegration.ts ×1
264 > if (this._currentExecutionProperties?.isMultiLine && this._currentExecutionProperties.unresolvedCommandLines) {
265 > const subExecutionResult = isSubExecution(this._currentExecutionProperties.unresolvedCommandLines, commandLine); extHostTerminalShellIntegration.ts ×6
266 > if (subExecutionResult) {
267 > this._currentExecutionProperties.unresolvedCommandLines = subExecutionResult.unresolvedCommandLines;
268 > return;
269 > }
270 > }
271 > this._currentExecution.endExecution(undefined); extHostTerminalShellIntegration.ts ×1
272 > this._currentExecution.flush();
273 > this._onDidRequestEndExecution.fire({ terminal: this._terminal, shellIntegration: this.value, execution: this._currentExecution.value, exitCode: undefined });
274 > }
276 > // Get the matching pending execution, how strict this is depends on the confidence of the
277 > // command line
278 > let currentExecution: InternalTerminalShellExecution | undefined;
279 > if (commandLine.confidence === TerminalShellExecutionCommandLineConfidence.High) {
280 > for (const [i, execution] of this._pendingExecutions.entries()) {
281 > if (execution.value.commandLine.value === commandLine.value) { extHostTerminalShellIntegration.ts ×13
282 > currentExecution = execution; extHostTerminalShellIntegration.ts ×3
283 > this._currentExecutionProperties = {
284 > isMultiLine: false,
285 > unresolvedCommandLines: undefined,
286 > };
287 > currentExecution = execution;
288 > this._pendingExecutions.splice(i, 1);
289 > break;
291 > const subExecutionResult = isSubExecution(splitAndSanitizeCommandLine(execution.value.commandLine.value), commandLine);
292 > if (subExecutionResult) {
293 > this._currentExecutionProperties = { extHostTerminalShellIntegration.ts ×6
294 > isMultiLine: true,
295 > unresolvedCommandLines: subExecutionResult.unresolvedCommandLines,
296 > };
297 > currentExecution = execution;
298 > this._pendingExecutions.splice(i, 1);
299 > break;
300 > }
302 > }
304 currentExecution = this._pendingExecutions.shift();
305 }
307 > // If there is no execution, create a new one
308 > if (!currentExecution) {
309 > // Fallback to the shell integration's cwd as the cwd may not have been restored after a reload extHostTerminalShellIntegration.ts ×1
310 > currentExecution = new InternalTerminalShellExecution(commandLine, cwd ?? this._cwd);
311 > }
313 > this._currentExecution = currentExecution;
314 > this._onDidStartTerminalShellExecution.fire({ terminal: this._terminal, shellIntegration: this.value, execution: this._currentExecution.value });
315 > }
316 >
317 > emitData(data: string): void {
318 > this.currentExecution?.emitData(data); extHostTerminalShellIntegration.ts ×13
319 > }
321 > endShellExecution(commandLine: vscode.TerminalShellExecutionCommandLine | undefined, exitCode: number | undefined): void {
322 > // If the current execution is multi-line, don't end it until the next command line is
323 > // confirmed to not be a part of it.
324 > if (this._currentExecutionProperties?.isMultiLine) {
325 > if (this._currentExecutionProperties.unresolvedCommandLines && this._currentExecutionProperties.unresolvedCommandLines.length > 0) { extHostTerminalShellIntegration.ts ×6
326 > return;
327 > }
328 > }
330 > if (this._currentExecution) {
331 > const commandLineForEvent = this._currentExecutionProperties?.isMultiLine ? this._currentExecution.value.commandLine : commandLine;
332 > this._currentExecution.endExecution(commandLineForEvent);
333 > const currentExecution = this._currentExecution;
334 > this._pendingEndingExecution = currentExecution;
335 > this._currentExecution = undefined;
336 > // IMPORTANT: Ensure the current execution's data events are flushed in order to
337 > // prevent data events firing after the end event fires.
338 > currentExecution.flush().then(() => {
339 > // Only fire if it's still the same execution, if it's changed it would have already
340 > // been fired.
341 > if (this._pendingEndingExecution === currentExecution) {
342 > this._onDidRequestEndExecution.fire({ terminal: this._terminal, shellIntegration: this.value, execution: currentExecution.value, exitCode });
343 > this._pendingEndingExecution = undefined;
344 > }
345 > });
346 > }
347 > }
348 >
349 > setEnv(keys: string[], values: string[], isTrusted: boolean): void {
350 const env: { [key: string]: string | undefined } = {};
351 for (let i = 0; i < keys.length; i++) {
352 env[keys[i]] = values[i];
353 }
354 this._env = { value: env, isTrusted };
355 this._fireChangeEvent();
356 }
358 > setCwd(cwd: URI | undefined): void {
359 let wasChanged = false;
360 if (URI.isUri(this._cwd)) {
361 wasChanged = !URI.isUri(cwd) || this._cwd.toString() !== cwd.toString();
362 } else if (this._cwd !== cwd) {
363 wasChanged = true;
364 }
365 if (wasChanged) {
366 this._cwd = cwd;
367 this._fireChangeEvent();
368 }
369 }
371 > private _fireChangeEvent() {
372 this._onDidRequestChangeShellIntegration.fire({ terminal: this._terminal, shellIntegration: this.value });
373 }
375 >
376 > class InternalTerminalShellExecution {
377 > readonly value: vscode.TerminalShellExecution;
378 >
379 > private _dataStream: ShellExecutionDataStream | undefined;
380 > private _isEnded: boolean = false;
381 >
382 > constructor(
383 > private _commandLine: vscode.TerminalShellExecutionCommandLine,
384 > readonly cwd: URI | undefined,
385 > ) {
386 > const that = this;
387 > this.value = {
388 > get commandLine(): vscode.TerminalShellExecutionCommandLine {
389 > return that._commandLine;
390 > },
391 > get cwd(): URI | undefined {
392 return that.cwd;
393 },
394 > read(): AsyncIterable<string> { extHostTerminalShellIntegration.ts ×29
395 > return that._createDataStream();
396 > }
397 > };
398 > }
399 >
400 > private _createDataStream(): AsyncIterable<string> {
401 > if (!this._dataStream) {
402 > if (this._isEnded) {
403 return AsyncIterableObject.EMPTY;
404 }
405 > this._dataStream = new ShellExecutionDataStream(); extHostTerminalShellIntegration.ts ×29
406 > }
407 > return this._dataStream.createIterable();
408 > }
409 >
410 > emitData(data: string): void {
411 > if (!this._isEnded) { extHostTerminalShellIntegration.ts ×13
412 > this._dataStream?.emitData(data);
413 > }
414 > }
416 > endExecution(commandLine: vscode.TerminalShellExecutionCommandLine | undefined): void {
417 > if (commandLine) {
418 > this._commandLine = commandLine;
419 > }
420 > this._dataStream?.endExecution();
421 > this._isEnded = true;
422 > }
423 >
424 > async flush(): Promise<void> {
425 > if (this._dataStream) {
426 > await this._dataStream.flush();
427 > this._dataStream.dispose();
428 > this._dataStream = undefined;
429 > }
430 > }
431 > }
432 >
433 > class ShellExecutionDataStream extends Disposable {
434 > private _barrier: Barrier | undefined;
435 > private _iterables: AsyncIterableObject<string>[] = [];
436 > private _emitters: AsyncIterableEmitter<string>[] = [];
437 >
438 > createIterable(): AsyncIterable<string> {
439 > if (!this._barrier) {
440 > this._barrier = new Barrier();
441 > }
442 > const barrier = this._barrier;
443 > const iterable = new AsyncIterableObject<string>(async emitter => {
444 > this._emitters.push(emitter);
445 > await barrier.wait();
446 > });
447 > this._iterables.push(iterable);
448 > return iterable;
449 > }
450 >
451 > emitData(data: string): void {
452 > for (const emitter of this._emitters) { extHostTerminalShellIntegration.ts ×13
453 > emitter.emitOne(data);
454 > }
455 > }
457 > endExecution(): void {
458 > this._barrier?.open();
459 > }
460 >
461 > async flush(): Promise<void> {
462 > await Promise.all(this._iterables.map(e => e.toPromise()));
463 > }
464 > }
465 >
466 > function splitAndSanitizeCommandLine(commandLine: string): string[] { extHostTerminalShellIntegration.ts ×13
467 > return commandLine
468 > .split('\n')
469 > .map(line => line.trim())
470 > .filter(line => line.length > 0);
471 > }
473 > /**
474 > * When executing something that the shell considers multiple commands, such as
475 > * a comment followed by a command, this needs to all be tracked under a single
476 > * execution.
477 > */
478 > function isSubExecution(unresolvedCommandLines: string[], commandLine: vscode.TerminalShellExecutionCommandLine): { unresolvedCommandLines: string[] } | false { extHostTerminalShellIntegration.ts ×13
479 > if (unresolvedCommandLines.length === 0) {
480 return false;
481 }
482 > const newUnresolvedCommandLines = [...unresolvedCommandLines]; extHostTerminalShellIntegration.ts ×13
483 > const subExecutionLines = splitAndSanitizeCommandLine(commandLine.value);
484 > if (newUnresolvedCommandLines && newUnresolvedCommandLines.length > 0) {
485 > // If all sub-execution lines are in the command line, this is part of the
486 > // multi-line execution.
487 > while (newUnresolvedCommandLines.length > 0) {
488 > if (newUnresolvedCommandLines[0] !== subExecutionLines[0]) {
489 > break;
490 > }
491 > newUnresolvedCommandLines.shift(); extHostTerminalShellIntegration.ts ×6
492 > subExecutionLines.shift();
493 > }
495 > if (subExecutionLines.length === 0) {
496 > return { unresolvedCommandLines: newUnresolvedCommandLines }; extHostTerminalShellIntegration.ts ×6
497 > }
500 > }