Get default shell selector working

This commit is contained in:
Daniel Imms 2019-06-17 11:53:58 -07:00
parent 7dfbccb8e2
commit c287c3cfc7
13 changed files with 192 additions and 151 deletions

View file

@ -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"
}
}

View file

@ -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"

View file

@ -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<number> {
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));
}
}

View file

@ -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<IShellDefinitionDto[]>;
}
export interface ExtHostSCMShape {

View file

@ -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<IShellDefinitionDto[]> {
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();

View file

@ -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);

View file

@ -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<any> {
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);

View file

@ -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<string | undefined> {
throw new Error('Not implemented');
}
public getWslPath(): Promise<string> {
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<IShellDefinition[]> {
throw new Error('Not implemented');
}
}

View file

@ -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);
}

View file

@ -222,6 +222,7 @@ export interface ITerminalService {
onInstancesChanged: Event<void>;
onInstanceTitleChanged: Event<ITerminalInstance>;
onActiveInstanceChanged: Event<ITerminalInstance | undefined>;
onRequestWindowsShells: Event<(shells: IShellDefinition[]) => void>;
/**
* Creates a terminal.
@ -267,6 +268,8 @@ export interface ITerminalService {
findNext(): void;
findPrevious(): void;
selectDefaultWindowsShell(): Promise<void>;
setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
setWorkspaceShellAllowed(isAllowed: boolean): void;
@ -299,7 +302,12 @@ export interface ITerminalNativeService {
getWindowsBuildNumber(): number;
whenFileDeleted(path: URI): Promise<void>;
getWslPath(path: string): Promise<string>;
selectDefaultWindowsShell(): Promise<string | undefined>;
detectWindowsShells(): Promise<IShellDefinition[]>;
}
export interface IShellDefinition {
label: string;
path: string;
}
export const enum Direction {

View file

@ -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<ITerminalInstance | undefined> { return this._onActiveInstanceChanged.event; }
protected readonly _onTabDisposed = new Emitter<ITerminalTab>();
public get onTabDisposed(): Event<ITerminalTab> { 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<void> {
return this._detectWindowsShells().then(shells => {
const options: IPickOptions<IQuickPickItem> = {
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<IShellDefinition[]> {
return new Promise(r => this._onRequestWindowsShells.fire(r));
}
}

View file

@ -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<string | undefined> {
return this._detectWindowsShells().then(shells => {
const options: IPickOptions<IQuickPickItem> = {
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<string[]> {
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<IQuickPickItem[]> {
// 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 <IQuickPickItem>{
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<IShellDefinition[]> {
return detectWindowsShells();
}
/**

View file

@ -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<IShellDefinition[]> {
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<IShellDefinition | undefined>[] = [];
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<IShellDefinition | undefined> {
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<string> {
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 '';
}
}