extHostTerminalShellIntegration.ts ×29

Frontier kind: Code frontier

unlabeled · c_ddcd89c5cede

8 tests · 75772 LOC · 256 files · introduces 0 tests · 253 LOC · 2 files

Introduces — evidence that enters the hierarchy at this concept

Code
32 ranges253 lines · 2 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
4954 ranges75772 lines · 256 files · Browse complete extent
All tests (intent)
8 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

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 native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

2 files ranked by introduced lines: 253 introduced LOC across 32 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/api/common/extHostTerminalShellIntegration.ts 247 introduced LOC · 29 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extHostTerminalShellIntegration.ts
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,
81 // }, 4000);
82 }
84 > public $shellIntegrationChange(instanceId: number, supportsExecuteCommandApi: boolean): void {
85 const terminal = this._extHostTerminalService.getTerminalById(instanceId);
86 if (!terminal) {
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
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,
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
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
199 if (!that._env) {
200 return undefined;
205 });
206 },
207 > // executeCommand(commandLine: string): vscode.TerminalShellExecution; extHostTerminalShellIntegration.ts
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.');
234 return execution;
235 }
237 > }
238 >
239 > requestNewShellExecution(commandLine: vscode.TerminalShellExecutionCommandLine, cwd: URI | undefined) {
240 const execution = new InternalTerminalShellExecution(commandLine, cwd ?? this._cwd);
241 const unresolvedCommandLines = splitAndSanitizeCommandLine(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 });
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.
264 if (this._currentExecutionProperties?.isMultiLine && this._currentExecutionProperties.unresolvedCommandLines) {
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) {
282 currentExecution = execution;
301 }
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
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);
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) {
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++) {
355 this._fireChangeEvent();
356 }
358 > setCwd(cwd: URI | undefined): void {
359 let wasChanged = false;
360 if (URI.isUri(this._cwd)) {
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
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
406 > }
407 > return this._dataStream.createIterable();
408 > }
409 >
410 > emitData(data: string): void {
411 if (!this._isEnded) {
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) {
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[] {
467 return commandLine
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 {
479 if (unresolvedCommandLines.length === 0) {
src/vs/base/common/async.ts 6 introduced LOC · 3 ranges

Open complete file

2200
2201 public static async toPromise<T>(iterable: AsyncIterable<T>): Promise<T[]> {
2202 > const result: T[] = []; async.ts
2203 > for await (const item of iterable) {
2204 result.push(item);
2205 }
2206 > return result; async.ts
2207 > }
2208
2209 public toPromise(): Promise<T[]> {
2210 > return AsyncIterableObject.toPromise(this); async.ts
2211 > }
2212
2213 /**