未验证 提交 76b54755 编写于 作者: D Daniel Imms 提交者: GitHub

Merge pull request #75644 from microsoft/tyriar/75456_windows_term

Support Windows on web
......@@ -12,7 +12,7 @@
"keytar": "4.2.1",
"minimist": "1.2.0",
"native-watchdog": "1.0.0",
"node-pty": "0.8.1",
"node-pty": "0.9.0-beta17",
"onigasm-umd": "^2.2.2",
"semver": "^5.5.0",
"spdlog": "^0.9.0",
......@@ -21,13 +21,14 @@
"vscode-proxy-agent": "0.4.0",
"vscode-ripgrep": "^1.2.5",
"vscode-textmate": "^4.1.1",
"yauzl": "^2.9.2",
"yazl": "^2.4.3",
"xterm": "3.15.0-beta34",
"xterm-addon-search": "0.1.0-beta6",
"xterm-addon-web-links": "0.1.0-beta10"
"xterm-addon-web-links": "0.1.0-beta10",
"yauzl": "^2.9.2",
"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"
}
}
......@@ -599,11 +599,6 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
nan@2.12.1:
version "2.12.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
nan@2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
......@@ -614,7 +609,7 @@ nan@^2.10.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099"
integrity sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==
nan@^2.12.1, nan@^2.14.0:
nan@^2.12.1, nan@^2.13.2, nan@^2.14.0:
version "2.14.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
......@@ -636,12 +631,12 @@ node-addon-api@1.6.2:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.2.tgz#d8aad9781a5cfc4132cc2fecdbdd982534265217"
integrity sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA==
node-pty@0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.8.1.tgz#94b457bec013e7a09b8d9141f63b0787fa25c23f"
integrity sha512-j+/g0Q5dR+vkELclpJpz32HcS3O/3EdPSGPvDXJZVJQLCvgG0toEbfmymxAEyQyZEpaoKHAcoL+PvKM+4N9nlw==
node-pty@0.9.0-beta17:
version "0.9.0-beta17"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0-beta17.tgz#9b490df86a8124dea595e9fbedeaaf4b2eedbbcb"
integrity sha512-E94XwIs3JxLKAboquHY9Kytbbj/T/tJtRpQoAUdfPE7UXRta/NV+xdmRNhZkeU9jCji+plm656GbYFievgNPkQ==
dependencies:
nan "2.12.1"
nan "^2.13.2"
noop-logger@^0.1.1:
version "0.1.1"
......@@ -1117,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"
......
......@@ -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.onRequestAvailableShells(r => this._onRequestAvailableShells(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 _onRequestAvailableShells(resolve: (shells: IShellDefinition[]) => void): void {
this._proxy.$requestAvailableShells().then(shells => resolve(shells));
}
}
......@@ -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;
$requestAvailableShells(): Promise<IShellDefinitionDto[]>;
}
export interface ExtHostSCMShape {
......
......@@ -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, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal';
const RENDERER_NO_PROCESS_ID = -1;
......@@ -534,7 +534,9 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
// Fork the process and listen for messages
this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env);
const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, terminalConfig.get('windowsEnableConpty') as boolean, this._logService);
// TODO: Support conpty on remote, it doesn't seem to work for some reason?
const enableConpty = false; //terminalConfig.get('windowsEnableConpty') as boolean;
const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, enableConpty, this._logService);
p.onProcessReady((e: { pid: number, cwd: string }) => this._proxy.$sendProcessReady(id, e.pid, e.cwd));
p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title));
p.onProcessData(data => this._proxy.$sendProcessData(id, data));
......@@ -573,6 +575,10 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
return id;
}
public $requestAvailableShells(): Promise<IShellDefinitionDto[]> {
return detectAvailableShells();
}
private _onProcessExit(id: number, exitCode: number): void {
// Remove listeners
this._terminalProcesses[id].dispose();
......
......@@ -370,9 +370,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAct
primary: 0,
mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category);
if (platform.isWindows) {
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category);
}
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);
......
......@@ -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';
......@@ -35,7 +35,6 @@ import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export const TERMINAL_PICKER_PREFIX = 'term ';
......@@ -624,13 +623,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 +711,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 +726,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);
......@@ -744,8 +742,7 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
action: IAction,
@ITerminalService private readonly terminalService: ITerminalService,
@IThemeService themeService: IThemeService,
@IContextViewService contextViewService: IContextViewService,
@IWorkbenchEnvironmentService private workbenchEnvironmentService: IWorkbenchEnvironmentService
@IContextViewService contextViewService: IContextViewService
) {
super(null, action, terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') });
......@@ -757,11 +754,8 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
private _updateItems(): void {
const items = this.terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label });
let enableSelectDefaultShell = this.workbenchEnvironmentService.configuration.remoteAuthority ? false : isWindows;
if (enableSelectDefaultShell) {
items.push({ text: SwitchTerminalActionViewItem.SEPARATOR });
items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL });
}
items.push({ text: SwitchTerminalActionViewItem.SEPARATOR });
items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL });
this.setOptions(items, this.terminalService.activeTabIndex);
}
}
......
......@@ -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');
}
......
......@@ -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);
}
......
......@@ -222,6 +222,7 @@ export interface ITerminalService {
onInstancesChanged: Event<void>;
onInstanceTitleChanged: Event<ITerminalInstance>;
onActiveInstanceChanged: Event<ITerminalInstance | undefined>;
onRequestAvailableShells: 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,11 @@ export interface ITerminalNativeService {
getWindowsBuildNumber(): number;
whenFileDeleted(path: URI): Promise<void>;
getWslPath(path: string): Promise<string>;
selectDefaultWindowsShell(): Promise<string | undefined>;
}
export interface IShellDefinition {
label: string;
path: string;
}
export const enum Direction {
......
......@@ -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 _onRequestAvailableShells = new Emitter<(shells: IShellDefinition[]) => void>();
public get onRequestAvailableShells(): Event<(shells: IShellDefinition[]) => void> { return this._onRequestAvailableShells.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;
......@@ -526,4 +532,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._onRequestAvailableShells.fire(r));
}
}
......@@ -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 { 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 { 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,99 +52,6 @@ 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];
});
}
/**
* Converts a path to a path on WSL using the wslpath utility.
* @param path The original path.
......
......@@ -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, basename } from 'vs/base/common/path';
export function getDefaultShell(p: platform.Platform): string {
if (p === platform.Platform.Windows) {
......@@ -82,3 +84,80 @@ export function getWindowsBuildNumber(): number {
}
return buildNumber;
}
export function detectAvailableShells(): Promise<IShellDefinition[]> {
return platform.isWindows ? detectAvailableWindowsShells() : detectAvailableUnixShells();
}
async function detectAvailableWindowsShells(): Promise<IShellDefinition[]> {
// 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])));
return Promise.all(promises).then(coalesce);
}
async function detectAvailableUnixShells(): Promise<IShellDefinition[]> {
const contents = await readFile('/etc/shells', 'utf8');
const shells = contents.split('\n').filter(e => e.trim().indexOf('#') !== 0 && e.trim().length > 0);
return shells.map(e => {
return {
label: basename(e),
path: e
};
});
}
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 '';
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册