diff --git a/remote/package.json b/remote/package.json index acf5412a080..7a48c4cb238 100644 --- a/remote/package.json +++ b/remote/package.json @@ -28,6 +28,7 @@ "yazl": "^2.4.3" }, "optionalDependencies": { - "vscode-windows-ca-certs": "0.1.0" + "vscode-windows-ca-certs": "0.1.0", + "vscode-windows-registry": "1.0.1" } } diff --git a/remote/yarn.lock b/remote/yarn.lock index e5f73a8d3e6..6bf34b0726a 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -1112,6 +1112,13 @@ vscode-windows-ca-certs@0.1.0: dependencies: node-addon-api "1.6.2" +vscode-windows-registry@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.1.tgz#bc9f765563eb6dc1c9ad9a41f9eaacc84dfadc7c" + integrity sha512-q0aKXi9Py1OBdmXIJJFeJBzpPJMMUxMJNBU9FysWIXEwJyMQGEVevKzM2J3Qz/cHSc5LVqibmoUWzZ7g+97qRg== + dependencies: + nan "^2.12.1" + which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index a68310bb3ee..2d9fba053ce 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IShellDefinition } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { UriComponents, URI } from 'vs/base/common/uri'; @@ -22,11 +22,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape constructor( extHostContext: IExtHostContext, - @ITerminalService private readonly terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); this._remoteAuthority = extHostContext.remoteAuthority; - this._toDispose.push(terminalService.onInstanceCreated((instance) => { + this._toDispose.push(_terminalService.onInstanceCreated((instance) => { // Delay this message so the TerminalInstance constructor has a chance to finish and // return the ID normally to the extension host. The ID that is passed here will be used // to register non-extension API terminals in the extension host. @@ -35,25 +35,26 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._onInstanceDimensionsChanged(instance); }, EXT_HOST_CREATION_DELAY); })); - this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance))); - this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance))); - this._toDispose.push(terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance))); - this._toDispose.push(terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request))); - this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null))); - this._toDispose.push(terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title))); - this._toDispose.push(terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed))); + this._toDispose.push(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance))); + this._toDispose.push(_terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance))); + this._toDispose.push(_terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance))); + this._toDispose.push(_terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request))); + this._toDispose.push(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null))); + this._toDispose.push(_terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title))); + this._toDispose.push(_terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed))); + this._toDispose.push(_terminalService.onRequestWindowsShells(r => this._onRequestDetectWindowsShell(r))); // Set initial ext host state - this.terminalService.terminalInstances.forEach(t => { + this._terminalService.terminalInstances.forEach(t => { this._onTerminalOpened(t); t.processReady.then(() => this._onTerminalProcessIdReady(t)); }); - const activeInstance = this.terminalService.getActiveInstance(); + const activeInstance = this._terminalService.getActiveInstance(); if (activeInstance) { this._proxy.$acceptActiveTerminalChanged(activeInstance.id); } - this.terminalService.extHostReady(extHostContext.remoteAuthority); + this._terminalService.extHostReady(extHostContext.remoteAuthority); } public dispose(): void { @@ -75,7 +76,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape strictEnv, runInBackground }; - const terminal = this.terminalService.createTerminal(shellLaunchConfig); + const terminal = this._terminalService.createTerminal(shellLaunchConfig); return Promise.resolve({ id: terminal.id, name: terminal.title @@ -83,55 +84,55 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $createTerminalRenderer(name: string): Promise { - const instance = this.terminalService.createTerminalRenderer(name); + const instance = this._terminalService.createTerminalRenderer(name); return Promise.resolve(instance.id); } public $show(terminalId: number, preserveFocus: boolean): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { - this.terminalService.setActiveInstance(terminalInstance); - this.terminalService.showPanel(!preserveFocus); + this._terminalService.setActiveInstance(terminalInstance); + this._terminalService.showPanel(!preserveFocus); } } public $hide(terminalId: number): void { - const instance = this.terminalService.getActiveInstance(); + const instance = this._terminalService.getActiveInstance(); if (instance && instance.id === terminalId) { - this.terminalService.hidePanel(); + this._terminalService.hidePanel(); } } public $dispose(terminalId: number): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { terminalInstance.dispose(); } } public $terminalRendererWrite(terminalId: number, text: string): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { terminalInstance.write(text); } } public $terminalRendererSetName(terminalId: number, name: string): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { terminalInstance.setTitle(name, false); } } public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { terminalInstance.setDimensions(dimensions); } } public $terminalRendererRegisterOnInputListener(terminalId: number): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (!terminalInstance) { return; } @@ -147,14 +148,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $sendText(terminalId: number, text: string, addNewLine: boolean): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { terminalInstance.sendText(text, addNewLine); } } public $registerOnDataListener(terminalId: number): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (!terminalInstance) { return; } @@ -275,4 +276,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } this._terminalProcesses[terminalId].emitLatency(sum / COUNT); } + + private _onRequestDetectWindowsShell(resolve: (shells: IShellDefinition[]) => void): void { + this._proxy.$requestWindowsShells().then(shells => resolve(shells)); + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index bb93e419568..d21659ce49c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1106,6 +1106,11 @@ export interface ShellLaunchConfigDto { env?: { [key: string]: string | null }; } +export interface IShellDefinitionDto { + label: string; + path: string; +} + export interface ExtHostTerminalServiceShape { $acceptTerminalClosed(id: number): void; $acceptTerminalOpened(id: number, name: string): void; @@ -1123,6 +1128,7 @@ export interface ExtHostTerminalServiceShape { $acceptProcessRequestCwd(id: number): void; $acceptProcessRequestLatency(id: number): number; $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; + $requestWindowsShells(): Promise; } export interface ExtHostSCMShape { diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index eadd6cbdf7d..d2693538cc6 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -10,7 +10,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto, IShellDefinitionDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { ILogService } from 'vs/platform/log/common/log'; import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -20,7 +20,7 @@ import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; +import { getDefaultShell, detectWindowsShells } from 'vs/workbench/contrib/terminal/node/terminal'; const RENDERER_NO_PROCESS_ID = -1; @@ -575,6 +575,15 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return id; } + public $requestWindowsShells(): Promise { + console.log('$requestWindowsShells'); + if (!platform.isWindows) { + throw new Error('Can only detect Windows shells on Windows'); + } + console.log('$requestWindowsShells2'); + return detectWindowsShells(); + } + private _onProcessExit(id: number, exitCode: number): void { // Remove listeners this._terminalProcesses[id].dispose(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index dfefd249327..db127081d5d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -370,10 +370,10 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAct primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category); -// TODO: Web should support this as well, the shell query needs to go to the ext host though -if (platform.isWindows && !platform.isWeb) { - actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); -} +// TODO: This should be the remote OS +// if (platform.isWindows) { +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); +// } actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowWorkspaceShellTerminalCommand, AllowWorkspaceShellTerminalCommand.ID, AllowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Allow Workspace Shell Configuration', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand.ID, DisallowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Disallow Workspace Shell Configuration', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 19d55b65081..199d6b2183b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Action, IAction } from 'vs/base/common/actions'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper, ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -624,13 +624,13 @@ export class SelectDefaultShellWindowsTerminalAction extends Action { constructor( id: string, label: string, - @ITerminalNativeService private readonly _terminalNativeService: ITerminalNativeService + @ITerminalService private readonly _terminalService: ITerminalService ) { super(id, label); } public run(event?: any): Promise { - return this._terminalNativeService.selectDefaultWindowsShell(); + return this._terminalService.selectDefaultWindowsShell(); } } @@ -712,8 +712,7 @@ export class SwitchTerminalAction extends Action { constructor( id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService, - @ITerminalNativeService private readonly terminalNativeService: ITerminalNativeService + @ITerminalService private readonly terminalService: ITerminalService ) { super(id, label, 'terminal-action switch-terminal'); } @@ -728,7 +727,7 @@ export class SwitchTerminalAction extends Action { } if (item === SelectDefaultShellWindowsTerminalAction.LABEL) { this.terminalService.refreshActiveTab(); - return this.terminalNativeService.selectDefaultWindowsShell(); + return this.terminalService.selectDefaultWindowsShell(); } const selectedTabIndex = parseInt(item.split(':')[0], 10) - 1; this.terminalService.setActiveTabByIndex(selectedTabIndex); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts index 0e2849731fc..4d15ce7da31 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; -import { ITerminalNativeService, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalNativeService, LinuxDistro, IShellDefinition } from 'vs/workbench/contrib/terminal/common/terminal'; import { Emitter, Event } from 'vs/base/common/event'; export class TerminalNativeService implements ITerminalNativeService { @@ -23,10 +23,6 @@ export class TerminalNativeService implements ITerminalNativeService { throw new Error('Not implemented'); } - public selectDefaultWindowsShell(): Promise { - throw new Error('Not implemented'); - } - public getWslPath(): Promise { throw new Error('Not implemented'); } @@ -34,4 +30,9 @@ export class TerminalNativeService implements ITerminalNativeService { public getWindowsBuildNumber(): number { throw new Error('Not implemented'); } + + // TODO: Remove local impl + public detectWindowsShells(): Promise { + throw new Error('Not implemented'); + } } \ No newline at end of file diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 00c2820793d..1e2017721bc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -21,6 +21,8 @@ import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class TerminalService extends CommonTerminalService implements ITerminalService { private _configHelper: IBrowserTerminalConfigHelper; @@ -39,9 +41,11 @@ export class TerminalService extends CommonTerminalService implements ITerminalS @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @ITerminalNativeService readonly terminalNativeService: ITerminalNativeService + @ITerminalNativeService readonly terminalNativeService: ITerminalNativeService, + @IQuickInputService readonly quickInputService: IQuickInputService, + @IConfigurationService readonly configurationService: IConfigurationService ) { - super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService, remoteAgentService, terminalNativeService); + super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService, remoteAgentService, terminalNativeService, quickInputService, configurationService); this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this.terminalNativeService.linuxDistro); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index c66a35797f0..7f7ba8436e1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -222,6 +222,7 @@ export interface ITerminalService { onInstancesChanged: Event; onInstanceTitleChanged: Event; onActiveInstanceChanged: Event; + onRequestWindowsShells: Event<(shells: IShellDefinition[]) => void>; /** * Creates a terminal. @@ -267,6 +268,8 @@ export interface ITerminalService { findNext(): void; findPrevious(): void; + selectDefaultWindowsShell(): Promise; + setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; setWorkspaceShellAllowed(isAllowed: boolean): void; @@ -299,7 +302,12 @@ export interface ITerminalNativeService { getWindowsBuildNumber(): number; whenFileDeleted(path: URI): Promise; getWslPath(path: string): Promise; - selectDefaultWindowsShell(): Promise; + detectWindowsShells(): Promise; +} + +export interface IShellDefinition { + label: string; + path: string; } export const enum Direction { diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index c7da0cb4dbc..be79d920ce8 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalNativeService, IShellDefinition } from 'vs/workbench/contrib/terminal/common/terminal'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; @@ -22,6 +22,8 @@ import { basename } from 'vs/base/common/path'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { timeout } from 'vs/base/common/async'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; +import { IPickOptions, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export abstract class TerminalService implements ITerminalService { public _serviceBrand: any; @@ -63,6 +65,8 @@ export abstract class TerminalService implements ITerminalService { public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } protected readonly _onTabDisposed = new Emitter(); public get onTabDisposed(): Event { return this._onTabDisposed.event; } + protected readonly _onRequestWindowsShells = new Emitter<(shells: IShellDefinition[]) => void>(); + public get onRequestWindowsShells(): Event<(shells: IShellDefinition[]) => void> { return this._onRequestWindowsShells.event; } public abstract get configHelper(): ITerminalConfigHelper; @@ -76,7 +80,9 @@ export abstract class TerminalService implements ITerminalService { @IExtensionService private readonly _extensionService: IExtensionService, @IFileService protected readonly _fileService: IFileService, @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService, - @ITerminalNativeService private readonly _terminalNativeService: ITerminalNativeService + @ITerminalNativeService private readonly _terminalNativeService: ITerminalNativeService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { this._activeTabIndex = 0; this._isShuttingDown = false; @@ -522,4 +528,27 @@ export abstract class TerminalService implements ITerminalService { c(escapeNonWindowsPath(originalPath)); }); } + + public selectDefaultWindowsShell(): Promise { + return this._detectWindowsShells().then(shells => { + const options: IPickOptions = { + placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings") + }; + const quickPickItems = shells.map(s => { + return { label: s.label, description: s.path }; + }); + return this._quickInputService.pick(quickPickItems, options).then(async value => { + if (!value) { + return undefined; + } + const shell = value.description; + await this._configurationService.updateValue('terminal.integrated.shell.windows', shell, ConfigurationTarget.USER).then(() => shell); + return Promise.resolve(); + }); + }); + } + + private _detectWindowsShells(): Promise { + return new Promise(r => this._onRequestWindowsShells.fire(r)); + } } diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts index 68b7462447a..4eba4ead961 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts @@ -3,19 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { ipcRenderer as ipc } from 'electron'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; -import { ITerminalNativeService, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalNativeService, LinuxDistro, IShellDefinition } from 'vs/workbench/contrib/terminal/common/terminal'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; -import { getWindowsBuildNumber, linuxDistro } from 'vs/workbench/contrib/terminal/node/terminal'; -import { IQuickPickItem, IPickOptions, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { getWindowsBuildNumber, linuxDistro, detectWindowsShells } from 'vs/workbench/contrib/terminal/node/terminal'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { execFile } from 'child_process'; -import { coalesce } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class TerminalNativeService implements ITerminalNativeService { @@ -30,8 +26,6 @@ export class TerminalNativeService implements ITerminalNativeService { constructor( @IFileService private readonly _fileService: IFileService, - @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService readonly instantiationService: IInstantiationService, ) { ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => this._onOpenFileRequest.fire(request)); @@ -58,97 +52,8 @@ export class TerminalNativeService implements ITerminalNativeService { }); } - public selectDefaultWindowsShell(): Promise { - return this._detectWindowsShells().then(shells => { - const options: IPickOptions = { - placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings") - }; - return this._quickInputService.pick(shells, options).then(value => { - if (!value) { - return undefined; - } - const shell = value.description; - return this._configurationService.updateValue('terminal.integrated.shell.windows', shell, ConfigurationTarget.USER).then(() => shell); - }); - }); - } - - /** - * Get the executable file path of shell from registry. - * @param shellName The shell name to get the executable file path - * @returns `[]` or `[ 'path' ]` - */ - private async _getShellPathFromRegistry(shellName: string): Promise { - const Registry = await import('vscode-windows-registry'); - - try { - const shellPath = Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${shellName}.exe`, ''); - - if (shellPath === undefined) { - return []; - } - - return [shellPath]; - } catch (error) { - return []; - } - } - - private async _detectWindowsShells(): Promise { - // Determine the correct System32 path. We want to point to Sysnative - // when the 32-bit version of VS Code is running on a 64-bit machine. - // The reason for this is because PowerShell's important PSReadline - // module doesn't work if this is not the case. See #27915. - const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`; - - let useWSLexe = false; - - if (getWindowsBuildNumber() >= 16299) { - useWSLexe = true; - } - - const expectedLocations = { - 'Command Prompt': [`${system32Path}\\cmd.exe`], - PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`], - 'PowerShell Core': await this._getShellPathFromRegistry('pwsh'), - 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], - 'Git Bash': [ - `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, - `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, - `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, - `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, - `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, - ] - }; - const promises: PromiseLike<[string, string]>[] = []; - Object.keys(expectedLocations).forEach(key => promises.push(this._validateShellPaths(key, expectedLocations[key]))); - return Promise.all(promises) - .then(coalesce) - .then(results => { - return results.map(result => { - return { - label: result[0], - description: result[1] - }; - }); - }); - } - - private _validateShellPaths(label: string, potentialPaths: string[]): Promise<[string, string] | null> { - if (potentialPaths.length === 0) { - return Promise.resolve(null); - } - const current = potentialPaths.shift(); - if (current! === '') { - return this._validateShellPaths(label, potentialPaths); - } - return this._fileService.exists(URI.file(current!)).then(exists => { - if (!exists) { - return this._validateShellPaths(label, potentialPaths); - } - return [label, current] as [string, string]; - }); + public detectWindowsShells(): Promise { + return detectWindowsShells(); } /** diff --git a/src/vs/workbench/contrib/terminal/node/terminal.ts b/src/vs/workbench/contrib/terminal/node/terminal.ts index 493fad0b833..b1610199f3d 100644 --- a/src/vs/workbench/contrib/terminal/node/terminal.ts +++ b/src/vs/workbench/contrib/terminal/node/terminal.ts @@ -6,8 +6,10 @@ import * as os from 'os'; import * as platform from 'vs/base/common/platform'; import * as processes from 'vs/base/node/processes'; -import { readFile, fileExists } from 'vs/base/node/pfs'; -import { LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; +import { readFile, fileExists, stat } from 'vs/base/node/pfs'; +import { LinuxDistro, IShellDefinition } from 'vs/workbench/contrib/terminal/common/terminal'; +import { coalesce } from 'vs/base/common/arrays'; +import { normalize } from 'vs/base/common/path'; export function getDefaultShell(p: platform.Platform): string { if (p === platform.Platform.Windows) { @@ -82,3 +84,68 @@ export function getWindowsBuildNumber(): number { } return buildNumber; } + +export async function detectWindowsShells(): Promise { + console.log('a'); + // Determine the correct System32 path. We want to point to Sysnative + // when the 32-bit version of VS Code is running on a 64-bit machine. + // The reason for this is because PowerShell's important PSReadline + // module doesn't work if this is not the case. See #27915. + const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); + const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`; + + let useWSLexe = false; + + if (getWindowsBuildNumber() >= 16299) { + useWSLexe = true; + } + + const expectedLocations = { + 'Command Prompt': [`${system32Path}\\cmd.exe`], + PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`], + 'PowerShell Core': [await getShellPathFromRegistry('pwsh')], + 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], + 'Git Bash': [ + `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, + `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, + `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, + `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, + `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, + ] + }; + const promises: PromiseLike[] = []; + Object.keys(expectedLocations).forEach(key => promises.push(validateShellPaths(key, expectedLocations[key]))); + + console.log('$detectWindowsShells'); + return Promise.all(promises).then(coalesce); +} + + +function validateShellPaths(label: string, potentialPaths: string[]): Promise { + if (potentialPaths.length === 0) { + return Promise.resolve(undefined); + } + const current = potentialPaths.shift()!; + if (current! === '') { + return validateShellPaths(label, potentialPaths); + } + return stat(normalize(current)).then(stat => { + if (!stat.isFile && !stat.isSymbolicLink) { + return validateShellPaths(label, potentialPaths); + } + return { + label, + path: current + }; + }); +} + +async function getShellPathFromRegistry(shellName: string): Promise { + const Registry = await import('vscode-windows-registry'); + try { + const shellPath = Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${shellName}.exe`, ''); + return shellPath ? shellPath : ''; + } catch (error) { + return ''; + } +} \ No newline at end of file