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

Merge pull request #7503 from Microsoft/6458_multiple_terminals

Add multiple terminals
......@@ -20,6 +20,14 @@
background-color: transparent!important;
}
.monaco-workbench .integrated-terminal .terminal-wrapper {
display: none;
}
.monaco-workbench .integrated-terminal .terminal-wrapper.active {
display: block;
}
.monaco-workbench .integrated-terminal .terminal-cursor {
background-color: #333;
}
......
......@@ -10,7 +10,7 @@ import {SyncActionDescriptor} from 'vs/platform/actions/common/actions';
import {registerSingleton} from 'vs/platform/instantiation/common/extensions';
import {IWorkbenchActionRegistry, Extensions as ActionExtensions} from 'vs/workbench/common/actionRegistry';
import {TerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminalService';
import {FocusTerminalAction, ToggleTerminalAction} from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
import {CloseTerminalAction, CreateNewTerminalAction, FocusTerminalAction, ToggleTerminalAction} from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
import {ITerminalService, TERMINAL_PANEL_ID, TERMINAL_DEFAULT_SHELL_LINUX, TERMINAL_DEFAULT_SHELL_OSX, TERMINAL_DEFAULT_SHELL_WINDOWS} from 'vs/workbench/parts/terminal/electron-browser/terminal';
import * as panel from 'vs/workbench/browser/panel';
import {Registry} from 'vs/platform/platform';
......@@ -89,10 +89,18 @@ registerSingleton(ITerminalService, TerminalService);
'terminal'
));
// On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl
let actionRegistry = <IWorkbenchActionRegistry>Registry.as(ActionExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTerminalAction, ToggleTerminalAction.ID, ToggleTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.US_BACKTICK,
// on mac cmd+` is reserved to cycle between windows
mac: { primary: KeyMod.WinCtrl | KeyCode.US_BACKTICK }
}), 'View: ' + ToggleTerminalAction.LABEL, nls.localize('viewCategory', "View"));
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalAction, FocusTerminalAction.ID, FocusTerminalAction.LABEL), FocusTerminalAction.LABEL);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKTICK,
mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_BACKTICK }
}), CreateNewTerminalAction.LABEL);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseTerminalAction, CloseTerminalAction.ID, CloseTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X,
mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_X }
}), CloseTerminalAction.LABEL);
......@@ -42,6 +42,8 @@ export interface ITerminalConfiguration {
export interface ITerminalService {
serviceId: ServiceIdentifier<any>;
close(): TPromise<any>;
createNew(): TPromise<any>;
focus(): TPromise<any>;
toggle(): TPromise<any>;
}
......@@ -10,8 +10,8 @@ import {ITerminalService} from 'vs/workbench/parts/terminal/electron-browser/ter
export class ToggleTerminalAction extends Action {
public static ID = 'workbench.action.terminal.toggleTerminal';
public static LABEL = nls.localize('toggleTerminal', "Toggle Integrated Terminal");
public static ID = 'workbench.action.terminal.toggle';
public static LABEL = nls.localize('workbench.action.terminal.toggle', "Toggle Integrated Terminal");
constructor(
id: string, label: string,
......@@ -25,10 +25,44 @@ export class ToggleTerminalAction extends Action {
}
}
export class CloseTerminalAction extends Action {
public static ID = 'workbench.action.terminal.close';
public static LABEL = nls.localize('workbench.action.terminal.close', "Terminal: Close the current terminal");
constructor(
id: string, label: string,
@ITerminalService private terminalService: ITerminalService
) {
super(id, label);
}
public run(event?: any): TPromise<any> {
return this.terminalService.close();
}
}
export class CreateNewTerminalAction extends Action {
public static ID = 'workbench.action.terminal.new';
public static LABEL = nls.localize('workbench.action.terminal.new', "Terminal: Create New Integrated Terminal");
constructor(
id: string, label: string,
@ITerminalService private terminalService: ITerminalService
) {
super(id, label);
}
public run(event?: any): TPromise<any> {
return this.terminalService.createNew();
}
}
export class FocusTerminalAction extends Action {
public static ID = 'workbench.action.terminal.focus';
public static LABEL = nls.localize('focusTerminal', "Focus Integrated Terminal");
public static LABEL = nls.localize('workbench.action.terminal.focus', "Terminal: Focus Terminal");
constructor(
id: string, label: string,
......
......@@ -37,6 +37,7 @@ export class TerminalInstance {
) {
this.toDispose = [];
this.wrapperElement = document.createElement('div');
this.wrapperElement.classList.add('terminal-wrapper');
this.ptyProcess = this.createTerminalProcess();
this.terminalDomElement = document.createElement('div');
this.parentDomElement.classList.add('integrated-terminal');
......@@ -62,10 +63,7 @@ export class TerminalInstance {
});
this.ptyProcess.on('exit', (exitCode) => {
this.dispose();
// TODO: When multiple terminals are supported this should do something smarter. There is
// also a weird bug here at least on Ubuntu 15.10 where the new terminal text does not
// repaint correctly.
if (exitCode !== 0) {
if (exitCode) {
console.error('Integrated terminal exited with code ' + exitCode);
}
this.onExitCallback(this);
......@@ -137,6 +135,10 @@ export class TerminalInstance {
});
}
public toggleVisibility(visible: boolean) {
this.wrapperElement.classList.toggle('active', visible);
}
public setFont(font: ITerminalFont): void {
this.font = font;
this.terminalDomElement.style.fontFamily = this.font.fontFamily;
......@@ -162,8 +164,10 @@ export class TerminalInstance {
}
public dispose(): void {
this.parentDomElement.removeChild(this.wrapperElement);
this.wrapperElement = null;
if (this.wrapperElement) {
this.parentDomElement.removeChild(this.wrapperElement);
this.wrapperElement = null;
}
this.toDispose = lifecycle.dispose(this.toDispose);
this.terminal.destroy();
this.ptyProcess.kill();
......
......@@ -50,12 +50,25 @@ export class TerminalPanel extends Panel {
this.parentDomElement.appendChild(this.themeStyleElement);
this.configurationHelper = new TerminalConfigHelper(platform.platform, this.configurationService, this.parentDomElement);
this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'wheel', (event: WheelEvent) => {
this.terminalInstances[0].dispatchEvent(new WheelEvent(event.type, event));
this.terminalInstances[this.activeTerminalIndex].dispatchEvent(new WheelEvent(event.type, event));
}));
return this.createTerminal();
}
public createNewTerminalInstance(): TPromise<void> {
return this.createTerminal().then(() => {
this.updateFont();
this.focus();
});
}
public closeActiveTerminal(): TPromise<void> {
return new TPromise<void>(resolve => {
this.onTerminalInstanceExit(this.terminalInstances[this.activeTerminalIndex]);
});
}
public setVisible(visible: boolean): TPromise<void> {
if (visible) {
if (this.terminalInstances.length > 0) {
......@@ -76,31 +89,39 @@ export class TerminalPanel extends Panel {
private createTerminal(): TPromise<void> {
return new TPromise<void>(resolve => {
this.terminalInstances.push(new TerminalInstance(this.configurationHelper.getShell(), this.parentDomElement, this.contextService, this.terminalService, this.onTerminalInstanceExit.bind(this)));
this.activeTerminalIndex = this.terminalInstances.length - 1;
this.setActiveTerminal(this.terminalInstances.length - 1);
this.toDispose.push(this.themeService.onDidThemeChange(this.updateTheme.bind(this)));
this.toDispose.push(this.configurationService.onDidUpdateConfiguration(this.updateFont.bind(this)));
resolve(void 0);
});
}
private setActiveTerminal(index: number) {
this.activeTerminalIndex = index;
this.terminalInstances.forEach((terminalInstance, i) => {
terminalInstance.toggleVisibility(i === this.activeTerminalIndex);
});
}
private onTerminalInstanceExit(terminalInstance: TerminalInstance): void {
for (var i = 0; i < this.terminalInstances.length; i++) {
if (this.terminalInstances[i] === terminalInstance) {
if (this.activeTerminalIndex === i) {
this.activeTerminalIndex = -1;
} else if (this.activeTerminalIndex > i) {
if (this.activeTerminalIndex > i) {
this.activeTerminalIndex--;
}
this.terminalInstances.splice(i, 1);
let killedTerminal = this.terminalInstances.splice(i, 1)[0];
killedTerminal.dispose();
}
}
this.terminalService.toggle();
if (this.terminalInstances.length === 0) {
this.activeTerminalIndex = -1;
this.terminalService.toggle();
} else {
this.setActiveTerminal(Math.min(this.activeTerminalIndex, this.terminalInstances.length - 1));
}
}
private updateTheme(themeId?: string): void {
if (this.terminalInstances.length === 0) {
return;
}
if (!themeId) {
themeId = this.themeService.getTheme();
}
......
......@@ -7,6 +7,7 @@ import {TPromise} from 'vs/base/common/winjs.base';
import {IPanelService} from 'vs/workbench/services/panel/common/panelService';
import {IPartService} from 'vs/workbench/services/part/common/partService';
import {ITerminalService, TERMINAL_PANEL_ID} from 'vs/workbench/parts/terminal/electron-browser/terminal';
import {TerminalPanel} from 'vs/workbench/parts/terminal/electron-browser/terminalPanel';
export class TerminalService implements ITerminalService {
public serviceId = ITerminalService;
......@@ -31,4 +32,27 @@ export class TerminalService implements ITerminalService {
return this.panelService.openPanel(TERMINAL_PANEL_ID, true);
}
public createNew(): TPromise<any> {
let panel = this.panelService.getActivePanel();
if (!panel || panel.getId() !== TERMINAL_PANEL_ID) {
return this.toggle().then(() => {
panel = this.panelService.getActivePanel();
return (<TerminalPanel>panel).createNewTerminalInstance();
});
}
return (<TerminalPanel>panel).createNewTerminalInstance();
}
public close(): TPromise<any> {
// TODO: Refactor to share code with createNew
let panel = this.panelService.getActivePanel();
if (!panel || panel.getId() !== TERMINAL_PANEL_ID) {
return this.toggle().then(() => {
panel = this.panelService.getActivePanel();
return (<TerminalPanel>panel).closeActiveTerminal();
});
}
return (<TerminalPanel>panel).closeActiveTerminal();
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册