diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 58354e223d0..3abd2e8efd1 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -27,21 +27,12 @@ import { getFigSuggestions } from './fig/figInterface'; import { executeCommand, executeCommandTimeout, IFigExecuteExternals } from './fig/execute'; import { createTimeoutPromise } from './helpers/promise'; -// TODO: remove once API is finalized export const 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 + Bash = 'bash', + Fish = 'fish', + Zsh = 'zsh', + PowerShell = 'pwsh', + Python = 'python' } const isWindows = osIsWindows(); @@ -73,11 +64,10 @@ async function getShellGlobals(shellType: TerminalShellType, existingCommands?: if (cachedCommands) { return cachedCommands; } - const shell = getShell(shellType); - if (!shell) { + if (!shellType) { return; } - const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell }; + const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell: shellType }; const mixedCommands: (string | ICompletionResource)[] | undefined = await getShellSpecificGlobals.get(shellType)?.(options, existingCommands); const normalizedCommands = mixedCommands?.map(command => typeof command === 'string' ? ({ label: command }) : command); cachedGlobals.set(shellType, normalizedCommands); @@ -101,14 +91,15 @@ export async function activate(context: vscode.ExtensionContext) { return; } - const shellType: TerminalShellType | undefined = 'shellType' in terminal.state ? terminal.state.shellType as TerminalShellType : undefined; - if (!shellType) { + const shellType: string | undefined = 'shell' in terminal.state ? terminal.state.shell as string : undefined; + const terminalShellType = getTerminalShellType(shellType); + if (!terminalShellType) { console.debug('#terminalCompletions No shell type found for terminal'); return; } const commandsInPath = await pathExecutableCache.getExecutablesInPath(terminal.shellIntegration?.env?.value); - const shellGlobals = await getShellGlobals(shellType, commandsInPath?.labels) ?? []; + const shellGlobals = await getShellGlobals(terminalShellType, commandsInPath?.labels) ?? []; if (!commandsInPath?.completionResources) { console.debug('#terminalCompletions No commands found in path'); return; @@ -117,7 +108,7 @@ export async function activate(context: vscode.ExtensionContext) { const commands = [...shellGlobals, ...commandsInPath.completionResources]; const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition); const pathSeparator = isWindows ? '\\' : '/'; - const tokenType = getTokenType(terminalContext, shellType); + const tokenType = getTokenType(terminalContext, terminalShellType); const result = await Promise.race([ getCompletionItemsFromSpecs( availableSpecs, @@ -320,22 +311,6 @@ function compareItems(existingItem: vscode.TerminalCompletionItem, command: ICom } } -function getShell(shellType: TerminalShellType): string | undefined { - switch (shellType) { - case TerminalShellType.Bash: - return 'bash'; - case TerminalShellType.Fish: - return 'fish'; - case TerminalShellType.Zsh: - return 'zsh'; - case TerminalShellType.PowerShell: - return 'pwsh'; - default: { - return undefined; - } - } -} - function getEnvAsRecord(shellIntegrationEnv: { [key: string]: string | undefined } | undefined): Record { const env: Record = {}; for (const [key, value] of Object.entries(shellIntegrationEnv ?? process.env)) { @@ -349,6 +324,23 @@ function getEnvAsRecord(shellIntegrationEnv: { [key: string]: string | undefined return env; } +function getTerminalShellType(shellType: string | undefined): TerminalShellType | undefined { + switch (shellType) { + case 'bash': + return TerminalShellType.Bash; + case 'zsh': + return TerminalShellType.Zsh; + case 'pwsh': + return TerminalShellType.PowerShell; + case 'fish': + return TerminalShellType.Fish; + case 'python': + return TerminalShellType.Python; + default: + return undefined; + } +} + export function sanitizeProcessEnvironment(env: Record, ...preserve: string[]): void { const set = preserve.reduce>((set, key) => { set[key] = true; diff --git a/extensions/terminal-suggest/src/tokens.ts b/extensions/terminal-suggest/src/tokens.ts index a967aeb2054..0520a2315a4 100644 --- a/extensions/terminal-suggest/src/tokens.ts +++ b/extensions/terminal-suggest/src/tokens.ts @@ -5,18 +5,19 @@ import { TerminalShellType } from './terminalSuggestMain'; + export const enum TokenType { Command, Argument, } -const shellTypeResetChars: { [key: number]: string[] | undefined } = { - [TerminalShellType.Bash]: ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<'], - [TerminalShellType.Zsh]: ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '<>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<', '<<<', '<('], - [TerminalShellType.PowerShell]: ['>', '>>', '<', '2>', '2>>', '*>', '*>>', '|', '-and', '-or', '-not', '!', '&', '-eq', '-ne', '-gt', '-lt', '-ge', '-le', '-like', '-notlike', '-match', '-notmatch', '-contains', '-notcontains', '-in', '-notin'] -}; +const shellTypeResetChars = new Map([ + [TerminalShellType.Bash, ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<']], + [TerminalShellType.Zsh, ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '<>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<', '<<<', '<(']], + [TerminalShellType.PowerShell, ['>', '>>', '<', '2>', '2>>', '*>', '*>>', '|', '-and', '-or', '-not', '!', '&', '-eq', '-ne', '-gt', '-lt', '-ge', '-le', '-like', '-notlike', '-match', '-notmatch', '-contains', '-notcontains', '-in', '-notin']] +]); -const defaultShellTypeResetChars = shellTypeResetChars[TerminalShellType.Bash]!; +const defaultShellTypeResetChars = shellTypeResetChars.get(TerminalShellType.Bash)!; export function getTokenType(ctx: { commandLine: string; cursorPosition: number }, shellType: TerminalShellType | undefined): TokenType { const spaceIndex = ctx.commandLine.substring(0, ctx.cursorPosition).lastIndexOf(' '); @@ -24,7 +25,7 @@ export function getTokenType(ctx: { commandLine: string; cursorPosition: number return TokenType.Command; } const previousTokens = ctx.commandLine.substring(0, spaceIndex + 1).trim(); - const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars[shellType] ?? defaultShellTypeResetChars; + const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars.get(shellType) ?? defaultShellTypeResetChars; if (commandResetChars.some(e => previousTokens.endsWith(e))) { return TokenType.Command; } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 9c1c097aab2..9e666879933 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -257,12 +257,12 @@ import { assertNoRpc, poll } from '../utils'; test('onDidChangeTerminalState should fire with shellType when created', async () => { const terminal = window.createTerminal(); - if (terminal.state.shellType) { + if (terminal.state.shell) { return; } await new Promise(r => { disposables.push(window.onDidChangeTerminalState(e => { - if (e === terminal && e.state.shellType) { + if (e === terminal && e.state.shell) { r(); } })); diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 8c447180ba4..069a1ad2fc7 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -364,9 +364,6 @@ 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', }, diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index c88dbbcaa35..693cb1728a4 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -10,7 +10,7 @@ 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, TerminalShellType as VSCodeTerminalShellType } from './extHostTypes.js'; +import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType, TerminalExitReason, TerminalCompletionItem } from './extHostTypes.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { localize } from '../../../nls.js'; import { NotSupportedError } from '../../../base/common/errors.js'; @@ -18,7 +18,7 @@ import { serializeEnvironmentDescriptionMap, serializeEnvironmentVariableCollect 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, TerminalShellType, PosixShellType, WindowsShellType, GeneralShellType } from '../../../platform/terminal/common/terminal.js'; +import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, ProcessPropertyType, IProcessPropertyMap, TerminalShellType } 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'; @@ -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, shellType: undefined }; + private _state: vscode.TerminalState = { isInteractedWith: false, shell: undefined }; private _selection: string | undefined; shellIntegration: vscode.TerminalShellIntegration | undefined; @@ -269,28 +269,10 @@ export class ExtHostTerminal extends Disposable { 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) { + if (this._state.shell !== shellType) { this._state = { ...this._state, - shellType: extHostType + shell: shellType }; return true; } diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 87a4960333f..a784194d43c 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -7733,6 +7733,18 @@ declare module 'vscode' { * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html */ readonly isInteractedWith: boolean; + + /** + * The detected shell type of the {@link Terminal}. This will be `undefined` when there is + * not a clear signal as to what the shell is, or the shell is not supported yet. This + * value should change to the shell type of a sub-shell when launched (for example, running + * `bash` inside `zsh`). + * + * Note that the possible values are currently defined as any of the following: + * 'bash', 'cmd', 'csh', 'fish', 'gitbash', 'julia', 'ksh', 'node', 'nu', 'pwsh', 'python', + * 'sh', 'wsl', 'zsh'. + */ + readonly shell: string | undefined; } /** diff --git a/src/vscode-dts/vscode.proposed.terminalShellType.d.ts b/src/vscode-dts/vscode.proposed.terminalShellType.d.ts deleted file mode 100644 index e76defc6568..00000000000 --- a/src/vscode-dts/vscode.proposed.terminalShellType.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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; - } - -}