提交 cbe3a171 编写于 作者: D Daniel Imms 提交者: GitHub

Merge pull request #22750 from Microsoft/tyriar/22260

Implement Windows terminal shell selector
......@@ -127,7 +127,7 @@ export interface ITerminalService {
onInstanceTitleChanged: Event<string>;
terminalInstances: ITerminalInstance[];
createInstance(shell?: IShellLaunchConfig): ITerminalInstance;
createInstance(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
getInstanceFromId(terminalId: number): ITerminalInstance;
getInstanceLabels(): string[];
getActiveInstance(): ITerminalInstance;
......@@ -135,11 +135,13 @@ export interface ITerminalService {
setActiveInstanceByIndex(terminalIndex: number): void;
setActiveInstanceToNext(): void;
setActiveInstanceToPrevious(): void;
getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance;
showPanel(focus?: boolean): TPromise<void>;
hidePanel(): void;
setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
updateConfig(): void;
selectDefaultWindowsShell(): TPromise<string>;
}
export interface ITerminalInstance {
......
......@@ -60,7 +60,9 @@ export abstract class TerminalService implements ITerminalService {
}
protected abstract _showTerminalCloseConfirmation(): boolean;
public abstract createInstance(shell?: IShellLaunchConfig): ITerminalInstance;
public abstract createInstance(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract selectDefaultWindowsShell(): TPromise<string>;
public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
private _onWillShutdown(): boolean {
......
......@@ -17,7 +17,7 @@ import { TERMINAL_DEFAULT_SHELL_LINUX, TERMINAL_DEFAULT_SHELL_OSX, TERMINAL_DEFA
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, FocusTerminalAtIndexAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, FocusTerminalAtIndexAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
import { Registry } from 'vs/platform/platform';
import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
......@@ -266,5 +266,8 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAct
primary: KeyMod.CtrlCmd | KeyCode.KEY_K,
linux: { primary: null }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingsRegistry.WEIGHT.workbenchContrib(1)), 'Terminal: Clear', category);
if (platform.isWindows) {
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category);
}
registerColors();
......@@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as os from 'os';
import * as cp from 'child_process';
import platform = require('vs/base/common/platform');
import processes = require('vs/base/node/processes');
import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import * as processes from 'vs/base/node/processes';
export const TERMINAL_DEFAULT_SHELL_LINUX = !platform.isWindows ? (process.env.SHELL || 'sh') : 'sh';
export const TERMINAL_DEFAULT_SHELL_OSX = !platform.isWindows ? (process.env.SHELL || 'sh') : 'sh';
......
......@@ -24,10 +24,20 @@ export class ToggleTerminalAction extends TogglePanelAction {
constructor(
id: string, label: string,
@IPanelService panelService: IPanelService,
@IPartService partService: IPartService
@IPartService partService: IPartService,
@ITerminalService private terminalService: ITerminalService
) {
super(id, label, TERMINAL_PANEL_ID, panelService, partService);
}
public run(event?: any): TPromise<any> {
if (this.terminalService.terminalInstances.length === 0) {
// If there is not yet an instance attempt to create it here so that we can suggest a
// new shell on Windows (and not do so when the panel is restored on reload).
this.terminalService.createInstance(undefined, true);
}
return super.run();
}
}
export class KillTerminalAction extends Action {
......@@ -96,7 +106,11 @@ export class CreateNewTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
this.terminalService.setActiveInstance(this.terminalService.createInstance());
const instance = this.terminalService.createInstance(undefined, true);
if (!instance) {
return TPromise.as(void 0);
}
this.terminalService.setActiveInstance(instance);
return this.terminalService.showPanel(true);
}
}
......@@ -114,11 +128,11 @@ export class FocusActiveTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
if (!terminalInstance) {
terminalInstance = this.terminalService.createInstance();
const instance = this.terminalService.getActiveOrCreateInstance(true);
if (!instance) {
return TPromise.as(void 0);
}
this.terminalService.setActiveInstance(terminalInstance);
this.terminalService.setActiveInstance(instance);
return this.terminalService.showPanel(true);
}
}
......@@ -199,15 +213,31 @@ export class TerminalPasteAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
if (!terminalInstance) {
terminalInstance = this.terminalService.createInstance();
const instance = this.terminalService.getActiveOrCreateInstance();
if (instance) {
instance.paste();
}
terminalInstance.paste();
return TPromise.as(void 0);
}
}
export class SelectDefaultShellWindowsTerminalAction extends Action {
public static ID = 'workbench.action.terminal.selectDefaultShell';
public static LABEL = nls.localize('workbench.action.terminal.DefaultShell', "Select Default Shell");
constructor(
id: string, label: string,
@ITerminalService private terminalService: ITerminalService
) {
super(id, label);
}
public run(event?: any): TPromise<any> {
return this.terminalService.selectDefaultWindowsShell();
}
}
export class RunSelectedTextInTerminalAction extends Action {
public static ID = 'workbench.action.terminal.runSelectedText';
......@@ -222,9 +252,9 @@ export class RunSelectedTextInTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
if (!terminalInstance) {
terminalInstance = this.terminalService.createInstance();
const instance = this.terminalService.getActiveOrCreateInstance();
if (!instance) {
return TPromise.as(void 0);
}
let editor = this.codeEditorService.getFocusedCodeEditor();
if (editor) {
......@@ -236,7 +266,7 @@ export class RunSelectedTextInTerminalAction extends Action {
let endOfLinePreference = os.EOL === '\n' ? EndOfLinePreference.LF : EndOfLinePreference.CRLF;
text = editor.getModel().getValueInRange(selection, endOfLinePreference);
}
terminalInstance.sendText(text, true);
instance.sendText(text, true);
}
return TPromise.as(void 0);
}
......@@ -257,15 +287,15 @@ export class RunActiveFileInTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
if (!terminalInstance) {
terminalInstance = this.terminalService.createInstance();
const instance = this.terminalService.getActiveOrCreateInstance();
if (!instance) {
return TPromise.as(void 0);
}
const editor = this.codeEditorService.getFocusedCodeEditor();
if (editor) {
const uri = editor.getModel().uri;
if (uri.scheme === 'file') {
terminalInstance.sendText(uri.fsPath, true);
instance.sendText(uri.fsPath, true);
} else {
this.messageService.show(Severity.Warning, nls.localize('workbench.action.terminal.runActiveFile.noFile', 'Only files on disk can be run in the terminal'));
}
......
......@@ -488,8 +488,12 @@ export class TerminalInstance implements ITerminalInstance {
private _sendPtyDataToXterm(message: { type: string, content: string }): void {
if (message.type === 'data') {
this._widgetManager.closeMessage();
this._linkHandler.disposeTooltipListeners();
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
if (this._linkHandler) {
this._linkHandler.disposeTooltipListeners();
}
if (this._xterm) {
this._xterm.write(message.content);
}
......
......@@ -89,9 +89,12 @@ export class TerminalPanel extends Panel {
this._updateTheme();
} else {
return super.setVisible(visible).then(() => {
this._terminalService.createInstance();
this._updateFont();
this._updateTheme();
const instance = this._terminalService.createInstance();
if (instance) {
this._updateFont();
this._updateTheme();
}
return TPromise.as(void 0);
});
}
}
......
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as pfs from 'vs/base/node/pfs';
import * as platform from 'vs/base/common/platform';
import product from 'vs/platform/node/product';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
......@@ -13,10 +14,17 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
import { IQuickOpenService, IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen';
import { ITerminalInstance, ITerminalService, IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/parts/terminal/common/terminal';
import { TerminalService as AbstractTerminalService } from 'vs/workbench/parts/terminal/common/terminalService';
import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChoiceService } from "vs/platform/message/common/message";
import { Severity } from "vs/editor/common/standalone/standaloneBase";
import { IStorageService, StorageScope } from "vs/platform/storage/common/storage";
import { TERMINAL_DEFAULT_SHELL_WINDOWS } from "vs/workbench/parts/terminal/electron-browser/terminal";
export class TerminalService extends AbstractTerminalService implements ITerminalService {
private _configHelper: TerminalConfigHelper;
......@@ -30,14 +38,18 @@ export class TerminalService extends AbstractTerminalService implements ITermina
@IPartService _partService: IPartService,
@ILifecycleService _lifecycleService: ILifecycleService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IWindowIPCService private _windowService: IWindowIPCService
@IWindowIPCService private _windowService: IWindowIPCService,
@IQuickOpenService private _quickOpenService: IQuickOpenService,
@IConfigurationEditingService private _configurationEditingService: IConfigurationEditingService,
@IChoiceService private _choiceService: IChoiceService,
@IStorageService private _storageService: IStorageService
) {
super(_contextKeyService, _configurationService, _panelService, _partService, _lifecycleService);
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, platform.platform);
}
public createInstance(shell: IShellLaunchConfig = {}): ITerminalInstance {
public createInstance(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance {
let terminalInstance = this._instantiationService.createInstance(TerminalInstance,
this._terminalFocusContextKey,
this._configHelper,
......@@ -52,9 +64,115 @@ export class TerminalService extends AbstractTerminalService implements ITermina
this.setActiveInstanceByIndex(0);
}
this._onInstancesChanged.fire();
this._suggestShellChange(wasNewTerminalAction);
return terminalInstance;
}
private _suggestShellChange(wasNewTerminalAction?: boolean): void {
// Only suggest on Windows since $SHELL works great for macOS/Linux
if (!platform.isWindows) {
return;
}
// Only suggest when the terminal instance is being created by an explicit user action to
// launch a terminal, as opposed to something like tasks, debug, panel restore, etc.
if (!wasNewTerminalAction) {
return;
}
// Don't suggest if the user has explicitly opted out
const neverSuggest = this._storageService.getBoolean('terminal.neverSuggestSelectWindowsShell', StorageScope.GLOBAL, false);
if (neverSuggest) {
return;
}
// Never suggest if the setting is non-default already (ie. they set the setting manually)
if (this._configHelper.config.shell.windows !== TERMINAL_DEFAULT_SHELL_WINDOWS) {
this._storageService.store('terminal.neverSuggestSelectWindowsShell', true);
return;
}
const message = nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button.");
const options = [nls.localize('customize', "Customize"), nls.localize('cancel', "Cancel"), nls.localize('never again', "OK, Never Show Again")];
this._choiceService.choose(Severity.Info, message, options).then(choice => {
switch (choice) {
case 0:
return this.selectDefaultWindowsShell();
case 1:
return TPromise.as(null);
case 2:
this._storageService.store('terminal.neverSuggestSelectWindowsShell', true);
default:
return TPromise.as(null);
}
});
}
public selectDefaultWindowsShell(): TPromise<string> {
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._quickOpenService.pick(shells, options).then(value => {
if (!value) {
return null;
}
const shell = value.description;
const configChange = { key: 'terminal.integrated.shell.windows', value: shell };
return this._configurationEditingService.writeConfiguration(ConfigurationTarget.USER, configChange).then(() => shell);
});
});
}
private _detectWindowsShells(): TPromise<IPickOpenEntry[]> {
const windir = process.env['windir'];
const expectedLocations = {
'Command Prompt': [
`${windir}\\Sysnative\\cmd.exe`,
`${windir}\\System32\\cmd.exe`
],
PowerShell: [
`${windir}\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe`,
`${windir}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
],
'WSL Bash': [`${windir}\\Sysnative\\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`,
]
};
const promises: TPromise<[string, string]>[] = [];
Object.keys(expectedLocations).forEach(key => promises.push(this._validateShellPaths(key, expectedLocations[key])));
return TPromise.join(promises).then(results => {
return results.filter(result => !!result).map(result => {
return <IPickOpenEntry>{
label: result[0],
description: result[1]
};
});
});
}
private _validateShellPaths(label: string, potentialPaths: string[]): TPromise<[string, string]> {
const current = potentialPaths.shift();
return pfs.fileExists(current).then(exists => {
if (!exists) {
if (potentialPaths.length === 0) {
return null;
}
return this._validateShellPaths(label, potentialPaths);
}
return [label, current];
});
}
public getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance {
const activeInstance = this.getActiveInstance();
return activeInstance ? activeInstance : this.createInstance(undefined, wasNewTerminalAction);
}
protected _showTerminalCloseConfirmation(): boolean {
const cancelId = 1;
let message;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册