Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose shell type to extensions #238071

Merged
merged 14 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
anthonykim1 marked this conversation as resolved.
Show resolved Hide resolved
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;
}

}
Loading