Skip to content

Commit

Permalink
Expose shell type to extensions (#238071)
Browse files Browse the repository at this point in the history
* copy everything from #237624

* try to better word notes in proposed.d.ts

* why is test being so flaky

* try sending one more text

* strictEqual only on isInteractedWith always fails

* update the name as recommended

* embed to make sure we are selecting event we are interested in as recommended

* add node as part of TerminalShellType

* getting type ..extHostTypes.TerminalShellType.Bash is not comparable to type ..vscode.TerminalShellType.Bash

* just use one enum?

* figured out how to get from extHostTypes

* clean up
  • Loading branch information
anthonykim1 authored Jan 17, 2025
1 parent bc04273 commit 57e4810
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -231,19 +231,40 @@ import { assertNoRpc, poll } from '../utils';
});
});

test('onDidChangeTerminalState should fire after writing to a terminal', async () => {
test('onDidChangeTerminalState should fire with isInteractedWith after writing to a terminal', async () => {
const terminal = window.createTerminal();
deepStrictEqual(terminal.state, { isInteractedWith: false });
strictEqual(terminal.state.isInteractedWith, false);
const eventState = await new Promise<TerminalState>(r => {
disposables.push(window.onDidChangeTerminalState(e => {
if (e === terminal) {
if (e === terminal && e.state.isInteractedWith) {
r(e.state);
}
}));
terminal.sendText('test');
});
deepStrictEqual(eventState, { isInteractedWith: true });
deepStrictEqual(terminal.state, { isInteractedWith: true });
strictEqual(eventState.isInteractedWith, true);
await new Promise<void>(r => {
disposables.push(window.onDidCloseTerminal(t => {
if (t === terminal) {
r();
}
}));
terminal.dispose();
});
});

test('onDidChangeTerminalState should fire with shellType when created', async () => {
const terminal = window.createTerminal();
if (terminal.state.shellType) {
return;
}
await new Promise<void>(r => {
disposables.push(window.onDidChangeTerminalState(e => {
if (e === terminal && e.state.shellType) {
r();
}
}));
});
await new Promise<void>(r => {
disposables.push(window.onDidCloseTerminal(t => {
if (t === terminal) {
Expand Down
3 changes: 3 additions & 0 deletions src/vs/platform/extensions/common/extensionsApiProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ const _allApiProposals = {
terminalShellEnv: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalShellEnv.d.ts',
},
terminalShellType: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalShellType.d.ts',
},
testObserver: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts',
},
Expand Down
8 changes: 8 additions & 0 deletions src/vs/workbench/api/browser/mainThreadTerminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._store.add(_terminalService.onAnyInstanceTitleChange(instance => instance && this._onTitleChanged(instance.instanceId, instance.title)));
this._store.add(_terminalService.onAnyInstanceDataInput(instance => this._proxy.$acceptTerminalInteraction(instance.instanceId)));
this._store.add(_terminalService.onAnyInstanceSelectionChange(instance => this._proxy.$acceptTerminalSelection(instance.instanceId, instance.selection)));
this._store.add(_terminalService.onAnyInstanceShellTypeChanged(instance => this._onShellTypeChanged(instance.instanceId)));

// Set initial ext host state
for (const instance of this._terminalService.instances) {
Expand Down Expand Up @@ -358,6 +359,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._proxy.$acceptTerminalTitleChange(terminalId, name);
}

private _onShellTypeChanged(terminalId: number): void {
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
this._proxy.$acceptTerminalShellType(terminalId, terminalInstance.shellType);
}
}

private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
this._proxy.$acceptTerminalClosed(terminalInstance.instanceId, terminalInstance.exitCode, terminalInstance.exitReason ?? TerminalExitReason.Unknown);
}
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
TerminalCompletionItem: extHostTypes.TerminalCompletionItem,
TerminalCompletionItemKind: extHostTypes.TerminalCompletionItemKind,
TerminalCompletionList: extHostTypes.TerminalCompletionList,
TerminalShellType: extHostTypes.TerminalShellType,
TextDocumentSaveReason: extHostTypes.TextDocumentSaveReason,
TextEdit: extHostTypes.TextEdit,
SnippetTextEdit: extHostTypes.SnippetTextEdit,
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { AuthInfo, Credentials } from '../../../platform/request/common/request.
import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from '../../../platform/telemetry/common/gdprTypings.js';
import { TelemetryLevel } from '../../../platform/telemetry/common/telemetry.js';
import { ISerializableEnvironmentDescriptionMap, ISerializableEnvironmentVariableCollection } from '../../../platform/terminal/common/environmentVariable.js';
import { ICreateContributedTerminalProfileOptions, IProcessProperty, IProcessReadyWindowsPty, IShellLaunchConfigDto, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalExitReason, TerminalLocation } from '../../../platform/terminal/common/terminal.js';
import { ICreateContributedTerminalProfileOptions, IProcessProperty, IProcessReadyWindowsPty, IShellLaunchConfigDto, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalExitReason, TerminalLocation, TerminalShellType } from '../../../platform/terminal/common/terminal.js';
import { ProvidedPortAttributes, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId, TunnelProviderFeatures } from '../../../platform/tunnel/common/tunnel.js';
import { EditSessionIdentityMatch } from '../../../platform/workspace/common/editSessions.js';
import { WorkspaceTrustRequestOptions } from '../../../platform/workspace/common/workspaceTrust.js';
Expand Down Expand Up @@ -2416,6 +2416,7 @@ export interface ExtHostTerminalServiceShape {
$acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void;
$acceptTerminalInteraction(id: number): void;
$acceptTerminalSelection(id: number, selection: string | undefined): void;
$acceptTerminalShellType(id: number, shellType: TerminalShellType | undefined): void;
$startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined>;
$acceptProcessAckDataEvent(id: number, charCount: number): void;
$acceptProcessInput(id: number, data: string): void;
Expand Down
48 changes: 44 additions & 4 deletions src/vs/workbench/api/common/extHostTerminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import { createDecorator } from '../../../platform/instantiation/common/instanti
import { URI } from '../../../base/common/uri.js';
import { IExtHostRpcService } from './extHostRpcService.js';
import { IDisposable, DisposableStore, Disposable, MutableDisposable } from '../../../base/common/lifecycle.js';
import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType, TerminalExitReason, TerminalCompletionItem } from './extHostTypes.js';
import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType, TerminalExitReason, TerminalCompletionItem, TerminalShellType as VSCodeTerminalShellType } from './extHostTypes.js';
import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
import { localize } from '../../../nls.js';
import { NotSupportedError } from '../../../base/common/errors.js';
import { serializeEnvironmentDescriptionMap, serializeEnvironmentVariableCollection } from '../../../platform/terminal/common/environmentVariableShared.js';
import { CancellationTokenSource } from '../../../base/common/cancellation.js';
import { generateUuid } from '../../../base/common/uuid.js';
import { IEnvironmentVariableCollectionDescription, IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from '../../../platform/terminal/common/environmentVariable.js';
import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, ProcessPropertyType, IProcessPropertyMap } from '../../../platform/terminal/common/terminal.js';
import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, ProcessPropertyType, IProcessPropertyMap, TerminalShellType, PosixShellType, WindowsShellType, GeneralShellType } from '../../../platform/terminal/common/terminal.js';
import { TerminalDataBufferer } from '../../../platform/terminal/common/terminalDataBuffering.js';
import { ThemeColor } from '../../../base/common/themables.js';
import { Promises } from '../../../base/common/async.js';
Expand Down Expand Up @@ -85,7 +85,7 @@ export class ExtHostTerminal extends Disposable {
private _pidPromiseComplete: ((value: number | undefined) => any) | undefined;
private _rows: number | undefined;
private _exitStatus: vscode.TerminalExitStatus | undefined;
private _state: vscode.TerminalState = { isInteractedWith: false };
private _state: vscode.TerminalState = { isInteractedWith: false, shellType: undefined };
private _selection: string | undefined;

shellIntegration: vscode.TerminalShellIntegration | undefined;
Expand Down Expand Up @@ -258,7 +258,40 @@ export class ExtHostTerminal extends Disposable {

public setInteractedWith(): boolean {
if (!this._state.isInteractedWith) {
this._state = { isInteractedWith: true };
this._state = {
...this._state,
isInteractedWith: true
};
return true;
}
return false;
}

public setShellType(shellType: TerminalShellType | undefined): boolean {

let extHostType: VSCodeTerminalShellType | undefined;

switch (shellType) {
case PosixShellType.Sh: extHostType = VSCodeTerminalShellType.Sh; break;
case PosixShellType.Bash: extHostType = VSCodeTerminalShellType.Bash; break;
case PosixShellType.Fish: extHostType = VSCodeTerminalShellType.Fish; break;
case PosixShellType.Csh: extHostType = VSCodeTerminalShellType.Csh; break;
case PosixShellType.Ksh: extHostType = VSCodeTerminalShellType.Ksh; break;
case PosixShellType.Zsh: extHostType = VSCodeTerminalShellType.Zsh; break;
case WindowsShellType.CommandPrompt: extHostType = VSCodeTerminalShellType.CommandPrompt; break;
case WindowsShellType.GitBash: extHostType = VSCodeTerminalShellType.GitBash; break;
case GeneralShellType.PowerShell: extHostType = VSCodeTerminalShellType.PowerShell; break;
case GeneralShellType.Python: extHostType = VSCodeTerminalShellType.Python; break;
case GeneralShellType.Julia: extHostType = VSCodeTerminalShellType.Julia; break;
case GeneralShellType.NuShell: extHostType = VSCodeTerminalShellType.NuShell; break;
default: extHostType = undefined; break;
}

if (this._state.shellType !== shellType) {
this._state = {
...this._state,
shellType: extHostType
};
return true;
}
return false;
Expand Down Expand Up @@ -765,6 +798,13 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
return completions;
}

public $acceptTerminalShellType(id: number, shellType: TerminalShellType | undefined): void {
const terminal = this.getTerminalById(id);
if (terminal?.setShellType(shellType)) {
this._onDidChangeTerminalState.fire(terminal.value);
}
}

public registerTerminalQuickFixProvider(id: string, extensionId: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable {
if (this._quickFixProviders.has(id)) {
throw new Error(`Terminal quick fix provider "${id}" is already registered`);
Expand Down
16 changes: 16 additions & 0 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2072,6 +2072,22 @@ export enum TerminalShellExecutionCommandLineConfidence {
High = 2
}

export enum TerminalShellType {
Sh = 1,
Bash = 2,
Fish = 3,
Csh = 4,
Ksh = 5,
Zsh = 6,
CommandPrompt = 7,
GitBash = 8,
PowerShell = 9,
Python = 10,
Julia = 11,
NuShell = 12,
Node = 13
}

export class TerminalLink implements vscode.TerminalLink {
constructor(
public startIndex: number,
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ export interface ITerminalService extends ITerminalInstanceHost {
readonly onAnyInstanceProcessIdReady: Event<ITerminalInstance>;
readonly onAnyInstanceSelectionChange: Event<ITerminalInstance>;
readonly onAnyInstanceTitleChange: Event<ITerminalInstance>;
readonly onAnyInstanceShellTypeChanged: Event<ITerminalInstance>;

/**
* Creates a terminal.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export class TerminalService extends Disposable implements ITerminalService {
@memoize get onAnyInstanceProcessIdReady() { return this._register(this.createOnInstanceEvent(e => e.onProcessIdReady)).event; }
@memoize get onAnyInstanceSelectionChange() { return this._register(this.createOnInstanceEvent(e => e.onDidChangeSelection)).event; }
@memoize get onAnyInstanceTitleChange() { return this._register(this.createOnInstanceEvent(e => e.onTitleChanged)).event; }

@memoize get onAnyInstanceShellTypeChanged() { return this._register(this.createOnInstanceEvent(e => Event.map(e.onDidChangeShellType, () => e))).event; }
constructor(
@IContextKeyService private _contextKeyService: IContextKeyService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
Expand Down
40 changes: 40 additions & 0 deletions src/vscode-dts/vscode.proposed.terminalShellType.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

declare module 'vscode' {

// https://github.com/microsoft/vscode/issues/230165

/**
* Known terminal shell types.
*/
export enum TerminalShellType {
Sh = 1,
Bash = 2,
Fish = 3,
Csh = 4,
Ksh = 5,
Zsh = 6,
CommandPrompt = 7,
GitBash = 8,
PowerShell = 9,
Python = 10,
Julia = 11,
NuShell = 12,
Node = 13
}

// Part of TerminalState since the shellType can change multiple times and this comes with an event.
export interface TerminalState {
/**
* The current detected shell type of the terminal. New shell types may be added in the
* future in which case they will be returned as a number that is not part of
* {@link TerminalShellType}.
* Includes number type to prevent the breaking change when new enum members are added?
*/
readonly shellType?: TerminalShellType | number | undefined;
}

}

0 comments on commit 57e4810

Please sign in to comment.