From 1a59decdf1af0c1de515e1c56dc6b883589db235 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Feb 2018 08:52:50 -0800 Subject: [PATCH 1/8] Simplify terminal split model, only allow one dimension --- .../parts/terminal/common/terminal.ts | 10 +- .../electron-browser/terminal.contribution.ts | 22 +- .../electron-browser/terminalActions.ts | 57 ++- .../electron-browser/terminalService.ts | 3 - .../terminal/electron-browser/terminalTab.ts | 365 ++++++------------ 5 files changed, 149 insertions(+), 308 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index ca49c3eb32e..6f340678f1f 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -182,13 +182,6 @@ export interface ITerminalService { setWorkspaceShellAllowed(isAllowed: boolean): void; } -export const enum Direction { - Left = 0, - Right = 1, - Up = 2, - Down = 3 -} - export interface ITerminalTab { activeInstance: ITerminalInstance; terminalInstances: ITerminalInstance[]; @@ -196,7 +189,8 @@ export interface ITerminalTab { onDisposed: Event; onInstancesChanged: Event; - focusDirection(direction: Direction): void; + focusPreviousPane(): void; + focusNextPane(): void; setActiveInstanceByIndex(index: number): void; attachToElement(element: HTMLElement): void; setVisible(visible: boolean): void; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index b94b3f695b2..b53bbd50087 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -18,7 +18,7 @@ import { getTerminalDefaultShellUnixLike, getTerminalDefaultShellWindows } from import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitVerticalTerminalAction, FocusTerminalLeftAction, FocusTerminalRightAction, FocusTerminalUpAction, FocusTerminalDownAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitVerticalTerminalAction, FocusPreviousPaneTerminalAction, FocusNextPaneTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -274,8 +274,8 @@ configurationRegistry.registerConfiguration({ TogglePanelAction.ID, 'workbench.action.quickOpenView', SplitVerticalTerminalAction.ID, - FocusTerminalLeftAction.ID, - FocusTerminalRightAction.ID + FocusPreviousPaneTerminalAction.ID, + FocusNextPaneTerminalAction.ID ].sort() }, 'terminal.integrated.env.osx': { @@ -416,22 +416,14 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitVerticalTer primary: null, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_D } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split Vertically', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalLeftAction, FocusTerminalLeftAction.ID, FocusTerminalLeftAction.LABEL, { +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousPaneTerminalAction, FocusPreviousPaneTerminalAction.ID, FocusPreviousPaneTerminalAction.LABEL, { primary: KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.LeftArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Terminal To Left', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalRightAction, FocusTerminalRightAction.ID, FocusTerminalRightAction.LABEL, { +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Previous Pane', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextPaneTerminalAction, FocusNextPaneTerminalAction.ID, FocusNextPaneTerminalAction.LABEL, { primary: KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.RightArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Terminal To Right', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalDownAction, FocusTerminalDownAction.ID, FocusTerminalDownAction.LABEL, { - primary: KeyMod.Alt | KeyCode.DownArrow, - mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.DownArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Terminal Below', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalUpAction, FocusTerminalUpAction.ID, FocusTerminalUpAction.LABEL, { - primary: KeyMod.Alt | KeyCode.UpArrow, - mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.UpArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Terminal Above', category); +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Next Pane', category); terminalCommands.setup(); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index 25ffd59fe6e..24faf6a9f8b 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -8,7 +8,7 @@ import * as os from 'os'; 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 } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TPromise } from 'vs/base/common/winjs.base'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; @@ -298,7 +298,7 @@ export class CreateNewInActiveWorkspaceTerminalAction extends Action { export class SplitVerticalTerminalAction extends Action { public static readonly ID = 'workbench.action.terminal.splitVertical'; - public static readonly LABEL = nls.localize('workbench.action.terminal.splitVertical', "Split the terminal vertically"); + public static readonly LABEL = nls.localize('workbench.action.terminal.splitVertical', "Split Terminal Vertically"); constructor( id: string, label: string, @@ -317,10 +317,12 @@ export class SplitVerticalTerminalAction extends Action { } } -export abstract class BaseFocusDirectionTerminalAction extends Action { +export class FocusPreviousPaneTerminalAction extends Action { + public static readonly ID = 'workbench.action.terminal.focusPreviousPane'; + public static readonly LABEL = nls.localize('workbench.action.terminal.focusPreviousPane', "Focus Previous Pane"); + constructor( id: string, label: string, - private _direction: Direction, @ITerminalService private readonly _terminalService: ITerminalService ) { super(id, label); @@ -331,44 +333,29 @@ export abstract class BaseFocusDirectionTerminalAction extends Action { if (!tab) { return TPromise.as(void 0); } - tab.focusDirection(this._direction); + tab.focusPreviousPane(); return this._terminalService.showPanel(true); } } -export class FocusTerminalLeftAction extends BaseFocusDirectionTerminalAction { - public static readonly ID = 'workbench.action.terminal.focusTerminalLeft'; - public static readonly LABEL = nls.localize('workbench.action.terminal.focusTerminalLeft', "Focus terminal to the left"); +export class FocusNextPaneTerminalAction extends Action { + public static readonly ID = 'workbench.action.terminal.focusNextPane'; + public static readonly LABEL = nls.localize('workbench.action.terminal.focusNextPane', "Focus Next Pane"); - constructor(id: string, label: string, @ITerminalService terminalService: ITerminalService) { - super(id, label, Direction.Left, terminalService); + constructor( + id: string, label: string, + @ITerminalService private readonly _terminalService: ITerminalService + ) { + super(id, label); } -} -export class FocusTerminalRightAction extends BaseFocusDirectionTerminalAction { - public static readonly ID = 'workbench.action.terminal.focusTerminalRight'; - public static readonly LABEL = nls.localize('workbench.action.terminal.focusTerminalRight', "Focus terminal to the right"); - - constructor(id: string, label: string, @ITerminalService terminalService: ITerminalService) { - super(id, label, Direction.Right, terminalService); - } -} - -export class FocusTerminalUpAction extends BaseFocusDirectionTerminalAction { - public static readonly ID = 'workbench.action.terminal.focusTerminalUp'; - public static readonly LABEL = nls.localize('workbench.action.terminal.focusTerminalUp', "Focus terminal above"); - - constructor(id: string, label: string, @ITerminalService terminalService: ITerminalService) { - super(id, label, Direction.Up, terminalService); - } -} - -export class FocusTerminalDownAction extends BaseFocusDirectionTerminalAction { - public static readonly ID = 'workbench.action.terminal.focusTerminalDown'; - public static readonly LABEL = nls.localize('workbench.action.terminal.focusTerminalDown', "Focus terminal below"); - - constructor(id: string, label: string, @ITerminalService terminalService: ITerminalService) { - super(id, label, Direction.Down, terminalService); + public run(event?: any): TPromise { + const tab = this._terminalService.getActiveTab(); + if (!tab) { + return TPromise.as(void 0); + } + tab.focusNextPane(); + return this._terminalService.showPanel(true); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index eb6eafdbb3e..f0abc458631 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -237,8 +237,5 @@ export class TerminalService extends AbstractTerminalService implements ITermina this._configHelper.panelContainer = panelContainer; this._terminalContainer = terminalContainer; this._terminalTabs.forEach(tab => tab.attachToElement(this._terminalContainer)); - this._terminalInstances.forEach(terminalInstance => { - terminalInstance.attachToElement(this._terminalContainer); - }); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index 04acf742dbc..877287adcb3 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITerminalInstance, IShellLaunchConfig, ITerminalTab, Direction } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalInstance, IShellLaunchConfig, ITerminalTab } from 'vs/workbench/parts/terminal/common/terminal'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -12,71 +12,33 @@ import Event, { Emitter, anyEvent } from 'vs/base/common/event'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView } from 'vs/base/browser/ui/splitview/splitview'; -class SplitPane implements IView { - public minimumSize: number = 100; +class SplitPaneContainer implements IView { + public minimumSize: number = 40; public maximumSize: number = Number.MAX_VALUE; - public instance: ITerminalInstance; - public orientation: Orientation | undefined; - - protected _size: number; - - private _splitView: SplitView | undefined; + // TODO: Swap height and width when rotation is implemented + private _height: number; + private _width: number; + private _splitView: SplitView; private _children: SplitPane[] = []; - private _container: HTMLElement; - private _isContainerSet: boolean = false; - private _onDidChange: Event = Event.None; - public get children(): SplitPane[] { return this._children; } + private _onDidChange: Event = Event.None; public get onDidChange(): Event { return this._onDidChange; } constructor( - private _parent?: SplitPane, - public orthogonalSize?: number, - private _needsReattach?: boolean + private _container: HTMLElement, + public orientation: Orientation ) { - } - - protected branch(container: HTMLElement, orientation: Orientation, instance: ITerminalInstance): void { - this.orientation = orientation; - container.removeChild((this.instance)._wrapperElement); - - this._splitView = new SplitView(container, { orientation }); + this._width = this._container.offsetWidth; + this._height = this._container.offsetHeight; + this._splitView = new SplitView(this._container, { orientation: this.orientation }); this._splitView.onDidSashReset(() => this._resetSize()); - this.layout(this._size); - this.orthogonalLayout(this.orthogonalSize); - - this.addChild(this.orthogonalSize / 2, this._size, this.instance, 0, this._isContainerSet); - this.addChild(this.orthogonalSize / 2, this._size, instance); - - // Instance is now owned by the first child - this.instance = null; + this.render(this._container); + this._splitView.layout(this._width); } - public split(instance: ITerminalInstance): void { - if (this._parent && this._parent.orientation === this.orientation) { - // TODO: Splitting sizes can be a bit weird when not splitting the right-most pane - // If we kept proportions when adding the view to the splitview it would be alright - const index = this._parent._children.indexOf(this); - this._parent.addChild(this._size / 2, this.orthogonalSize, instance, index + 1); - } else { - this.branch(this._container, this.orientation, instance); - } - } - - private addChild(size: number, orthogonalSize: number, instance: ITerminalInstance, index?: number, needsReattach?: boolean): void { - const child = new SplitPane(this, orthogonalSize, needsReattach); - child.orientation = this.orientation; - child.instance = instance; - this._splitView.addView(child, size, index); - - if (typeof index === 'number') { - this._children.splice(index, 0, child); - } else { - this._children.push(child); - } - - this._onDidChange = anyEvent(...this._children.map(c => c.onDidChange)); + public split(instance: ITerminalInstance, index: number = this._children.length): void { + this._addChild(this._width / (this._children.length + 1), instance, index); } private _resetSize(): void { @@ -90,31 +52,93 @@ class SplitPane implements IView { } } - public remove(): void { - if (!this._parent) { + private _addChild(size: number, instance: ITerminalInstance, index: number): void { + const child = new SplitPane(this._height); + child.orientation = this.orientation; + child.instance = instance; + this._splitView.addView(child, size, index); + + if (typeof index === 'number') { + this._children.splice(index, 0, child); + } else { + this._children.push(child); + } + + this._resetSize(); + + this._onDidChange = anyEvent(...this._children.map(c => c.onDidChange)); + } + + public render(container: HTMLElement): void { + this._container = container; + } + + public remove(instance: ITerminalInstance): void { + let index = null; + for (let i = 0; i < this._children.length; i++) { + if (this._children[i].instance === instance) { + index = i; + } + } + if (index !== null) { + this._children.splice(index, 1); + this._splitView.removeView(index); + this._resetSize(); + } + } + + public layoutBox(width: number, height: number): void { + if (this.orientation === Orientation.HORIZONTAL) { + this.layout(height); + this.orthogonalLayout(width); + } else { + this.layout(width); + this.orthogonalLayout(height); + } + } + + public layout(size: number): void { + // Only layout when both sizes are known + this._height = size; + if (!this._height) { return; } - this._parent.removeChild(this); + this._children.forEach(c => c.orthogonalLayout(this._height)); } - public removeChild(child: SplitPane): void { - const index = this._children.indexOf(child); - this._children.splice(index, 1); - this._splitView.removeView(index); + public orthogonalLayout(size: number): void { + this._width = size; + + if (this._splitView) { + this._splitView.layout(this._width); + } + } +} + +class SplitPane implements IView { + public minimumSize: number = 40; + public maximumSize: number = Number.MAX_VALUE; + + public instance: ITerminalInstance; + public orientation: Orientation | undefined; + protected _size: number; + private _isContainerSet: boolean = false; + + private _onDidChange: Event = Event.None; + public get onDidChange(): Event { return this._onDidChange; } + + constructor( + public orthogonalSize: number + ) { } public render(container: HTMLElement): void { if (!container) { return; } - this._container = container; if (!this._isContainerSet && this.instance) { - if (this._needsReattach) { - (this.instance).reattachToElement(container); - } else { - this.instance.attachToElement(container); - } + this.instance.attachToElement(container); this._isContainerSet = true; } } @@ -126,75 +150,21 @@ class SplitPane implements IView { return; } - if (this.instance) { - if (this.orientation === Orientation.VERTICAL) { - this.instance.layout({ width: this.orthogonalSize, height: this._size }); - } else { - this.instance.layout({ width: this._size, height: this.orthogonalSize }); - } - return; - } - - for (const child of this.children) { - child.orthogonalLayout(this._size); + if (this.orientation === Orientation.VERTICAL) { + this.instance.layout({ width: this.orthogonalSize, height: this._size }); + } else { + this.instance.layout({ width: this._size, height: this.orthogonalSize }); } } public orthogonalLayout(size: number): void { this.orthogonalSize = size; - - if (this._splitView) { - this._splitView.layout(this.orthogonalSize); - } } } -class RootSplitPane extends SplitPane { - private static _lastKnownWidth: number; - private static _lastKnownHeight: number; - - private _width: number; - private _height: number; - - protected branch(container: HTMLElement, orientation: Orientation, instance: ITerminalInstance): void { - if (orientation === Orientation.VERTICAL) { - this._size = this._width || RootSplitPane._lastKnownWidth; - this.orthogonalSize = this._height || RootSplitPane._lastKnownHeight; - } else { - this._size = this._height || RootSplitPane._lastKnownHeight; - this.orthogonalSize = this._width || RootSplitPane._lastKnownWidth; - } - - super.branch(container, orientation, instance); - } - - public layoutBox(width: number, height: number): void { - RootSplitPane._lastKnownWidth = width; - RootSplitPane._lastKnownHeight = height; - if (this.orientation === Orientation.VERTICAL) { - this.layout(width); - this.orthogonalLayout(height); - } else if (this.orientation === Orientation.HORIZONTAL) { - this.layout(height); - this.orthogonalLayout(width); - } else { - this._width = width; - this._height = height; - this.instance.layout({ width, height }); - } - } -} - -const directionOrientation: { [direction: number]: Orientation } = { - [Direction.Left]: Orientation.HORIZONTAL, - [Direction.Right]: Orientation.HORIZONTAL, - [Direction.Up]: Orientation.VERTICAL, - [Direction.Down]: Orientation.VERTICAL -}; - export class TerminalTab extends Disposable implements ITerminalTab { private _terminalInstances: ITerminalInstance[] = []; - private _rootSplitPane: RootSplitPane; + private _splitPaneContainer: SplitPaneContainer | undefined; private _tabElement: HTMLElement; private _activeInstanceIndex: number; @@ -226,9 +196,6 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._initInstanceListeners(instance); this._activeInstanceIndex = 0; - this._rootSplitPane = new RootSplitPane(); - this._rootSplitPane.instance = instance; - if (this._container) { this.attachToElement(this._container); } @@ -272,10 +239,9 @@ export class TerminalTab extends Disposable implements ITerminalTab { this.activeInstance.focus(true); } - // Find the instance's SplitPane and unsplit it - const pane = this._findSplitPane(instance); - if (pane) { - pane.remove(); + // Remove the instance from the split pane if it has been created + if (this._splitPaneContainer) { + this._splitPaneContainer.remove(instance); } // Fire events and dispose tab if it was the last instance @@ -286,44 +252,6 @@ export class TerminalTab extends Disposable implements ITerminalTab { } } - private _findSplitPane(instance: ITerminalInstance): SplitPane { - const openList: SplitPane[] = [this._rootSplitPane]; - while (openList.length > 0) { - const current = openList.shift(); - if (current.instance === instance) { - return current; - } - openList.push.apply(openList, current.children); - } - return null; - } - - // TODO: Should this live inside SplitPane? - private _findSplitPanePath(instance: ITerminalInstance, path: SplitPane[] = [this._rootSplitPane]): SplitPane[] { - // Gets all split panes from the root to the pane containing the instance. - const pane = path[path.length - 1]; - - // Base case: path found - if (pane.instance === instance) { - return path; - } - - // Rescurse child panes - for (let i = 0; i < pane.children.length; i++) { - const child = pane.children[i]; - - const subPath = path.slice(); - subPath.push(child); - const result = this._findSplitPanePath(instance, subPath); - if (result) { - return result; - } - } - - // No children contain instance - return null; - } - private _setActiveInstance(instance: ITerminalInstance): void { this.setActiveInstanceByIndex(this._getIndexFromId(instance.id)); } @@ -343,7 +271,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { public setActiveInstanceByIndex(index: number): void { // Check for invalid value - if (index >= this._terminalInstances.length) { + if (index < 0 || index >= this._terminalInstances.length) { return; } @@ -360,7 +288,10 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._tabElement = document.createElement('div'); this._tabElement.classList.add('terminal-tab'); this._container.appendChild(this._tabElement); - this._rootSplitPane.render(this._tabElement); + if (!this._splitPaneContainer) { + this._splitPaneContainer = new SplitPaneContainer(this._tabElement, Orientation.HORIZONTAL); + this.terminalInstances.forEach(instance => this._splitPaneContainer.split(instance)); + } } public get title(): string { @@ -390,23 +321,14 @@ export class TerminalTab extends Disposable implements ITerminalTab { shellLaunchConfig); // TODO: Should this be pulled from the splitpanes instead? Currently there are 2 sources of truth. // _terminalInstances is also the order they were created, not the order in which they appear - this._terminalInstances.push(instance); + this._terminalInstances.splice(this._activeInstanceIndex + 1, 0, instance); this._initInstanceListeners(instance); - - if (this._rootSplitPane.instance) { - this._rootSplitPane.orientation = Orientation.HORIZONTAL; - this._rootSplitPane.split(instance); - } else { - // The original branch has already occured, find the inner SplitPane and split it - const activePane = this._findSplitPane(this.activeInstance); - activePane.orientation = Orientation.HORIZONTAL; - activePane.split(instance); - } - if (this._tabElement) { - this._rootSplitPane.render(this._tabElement); - } this._setActiveInstance(instance); + if (this._splitPaneContainer) { + this._splitPaneContainer.split(instance, this._activeInstanceIndex); + } + return instance; } @@ -415,67 +337,16 @@ export class TerminalTab extends Disposable implements ITerminalTab { } public layout(width: number, height: number): void { - this._rootSplitPane.layoutBox(width, height); + if (this._splitPaneContainer) { + this._splitPaneContainer.layoutBox(width, height); + } } - public focusDirection(direction: Direction): void { - const activeInstance = this.activeInstance; - if (!activeInstance) { - return null; - } + public focusPreviousPane(): void { + this.setActiveInstanceByIndex(this._activeInstanceIndex - 1); + } - const desiredOrientation = directionOrientation[direction]; - const isUpOrLeft = direction === Direction.Left || direction === Direction.Up; - - // Find the closest horizontal SplitPane ancestor with a child to the left - let closestHorizontalPane: SplitPane = null; - const panePath = this._findSplitPanePath(activeInstance); - let index = panePath.length - 1; - let ancestorIndex: number; - while (--index >= 0) { - const pane = panePath[index]; - // Continue up the path if not the desired orientation - if (pane.orientation !== desiredOrientation) { - continue; - } - - // Find index of the panePath pane and break out of loop if it's not the left-most child - ancestorIndex = pane.children.indexOf(panePath[index + 1]); - // Make sure that the pane is not on the boundary - if (isUpOrLeft) { - if (ancestorIndex > 0) { - closestHorizontalPane = pane; - break; - } - } else { - if (ancestorIndex < pane.children.length - 1) { - closestHorizontalPane = pane; - break; - } - } - } - - // There are no panes to the left - if (!closestHorizontalPane) { - return; - } - - let current: SplitPane; - if (isUpOrLeft) { - // Find the bottom/right-most instance - current = closestHorizontalPane.children[ancestorIndex - 1]; - while (current.children && current.children.length > 0) { - current = current.children[current.children.length - 1]; - } - } else { - // Find the top/left-most instance - current = closestHorizontalPane.children[ancestorIndex + 1]; - while (current.children && current.children.length > 0) { - current = current.children[0]; - } - } - - // Focus the instance to the left - current.instance.focus(); + public focusNextPane(): void { + this.setActiveInstanceByIndex(this._activeInstanceIndex + 1); } } From 4f7ef0be8cc08100a6cb9bba11ed901f8307595d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Feb 2018 09:44:14 -0800 Subject: [PATCH 2/8] Add commands to resize pane --- .../parts/terminal/common/terminal.ts | 8 +++ .../electron-browser/terminal.contribution.ts | 24 ++++++- .../electron-browser/terminalActions.ts | 68 ++++++++++++++++++- .../terminal/electron-browser/terminalTab.ts | 56 ++++++++++++++- 4 files changed, 151 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 6f340678f1f..74b01e5e015 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -182,6 +182,13 @@ export interface ITerminalService { setWorkspaceShellAllowed(isAllowed: boolean): void; } +export const enum Direction { + Left = 0, + Right = 1, + Up = 2, + Down = 3 +} + export interface ITerminalTab { activeInstance: ITerminalInstance; terminalInstances: ITerminalInstance[]; @@ -191,6 +198,7 @@ export interface ITerminalTab { focusPreviousPane(): void; focusNextPane(): void; + resizePane(direction: Direction): void; setActiveInstanceByIndex(index: number): void; attachToElement(element: HTMLElement): void; setVisible(visible: boolean): void; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index b53bbd50087..769af803b78 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -18,7 +18,7 @@ import { getTerminalDefaultShellUnixLike, getTerminalDefaultShellWindows } from import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitVerticalTerminalAction, FocusPreviousPaneTerminalAction, FocusNextPaneTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitVerticalTerminalAction, FocusPreviousPaneTerminalAction, FocusNextPaneTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, ResizePaneDownTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -275,7 +275,11 @@ configurationRegistry.registerConfiguration({ 'workbench.action.quickOpenView', SplitVerticalTerminalAction.ID, FocusPreviousPaneTerminalAction.ID, - FocusNextPaneTerminalAction.ID + FocusNextPaneTerminalAction.ID, + ResizePaneLeftTerminalAction.ID, + ResizePaneRightTerminalAction.ID, + ResizePaneUpTerminalAction.ID, + ResizePaneDownTerminalAction.ID ].sort() }, 'terminal.integrated.env.osx': { @@ -424,6 +428,22 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextPaneTer primary: KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.RightArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Next Pane', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneLeftTerminalAction, ResizePaneLeftTerminalAction.ID, ResizePaneLeftTerminalAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow, + mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Left', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneRightTerminalAction, ResizePaneRightTerminalAction.ID, ResizePaneRightTerminalAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow, + mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Right', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow, + mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow } +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Up', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow, + mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow } +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Down', category); terminalCommands.setup(); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index 24faf6a9f8b..9edbbfdba1c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -8,7 +8,7 @@ import * as os from 'os'; 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 } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction } from 'vs/workbench/parts/terminal/common/terminal'; import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TPromise } from 'vs/base/common/winjs.base'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; @@ -359,6 +359,72 @@ export class FocusNextPaneTerminalAction extends Action { } } +export abstract class BaseFocusDirectionTerminalAction extends Action { + constructor( + id: string, label: string, + private _direction: Direction, + @ITerminalService private readonly _terminalService: ITerminalService + ) { + super(id, label); + } + + public run(event?: any): TPromise { + const tab = this._terminalService.getActiveTab(); + if (tab) { + tab.resizePane(this._direction); + } + return TPromise.as(void 0); + } +} + +export class ResizePaneLeftTerminalAction extends BaseFocusDirectionTerminalAction { + public static readonly ID = 'workbench.action.terminal.resizePaneLeft'; + public static readonly LABEL = nls.localize('workbench.action.terminal.resizePaneLeft', "Resize Pane Left"); + + constructor( + id: string, label: string, + @ITerminalService readonly terminalService: ITerminalService + ) { + super(id, label, Direction.Left, terminalService); + } +} + +export class ResizePaneRightTerminalAction extends BaseFocusDirectionTerminalAction { + public static readonly ID = 'workbench.action.terminal.resizePaneRight'; + public static readonly LABEL = nls.localize('workbench.action.terminal.resizePaneRight', "Resize Pane Right"); + + constructor( + id: string, label: string, + @ITerminalService readonly terminalService: ITerminalService + ) { + super(id, label, Direction.Right, terminalService); + } +} + +export class ResizePaneUpTerminalAction extends BaseFocusDirectionTerminalAction { + public static readonly ID = 'workbench.action.terminal.resizePaneUp'; + public static readonly LABEL = nls.localize('workbench.action.terminal.resizePaneUp', "Resize Pane Up"); + + constructor( + id: string, label: string, + @ITerminalService readonly terminalService: ITerminalService + ) { + super(id, label, Direction.Up, terminalService); + } +} + +export class ResizePaneDownTerminalAction extends BaseFocusDirectionTerminalAction { + public static readonly ID = 'workbench.action.terminal.resizePaneDown'; + public static readonly LABEL = nls.localize('workbench.action.terminal.resizePaneDown', "Resize Pane Down"); + + constructor( + id: string, label: string, + @ITerminalService readonly terminalService: ITerminalService + ) { + super(id, label, Direction.Down, terminalService); + } +} + export class FocusActiveTerminalAction extends Action { public static readonly ID = 'workbench.action.terminal.focus'; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index 877287adcb3..cd1494e2e5f 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITerminalInstance, IShellLaunchConfig, ITerminalTab } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalInstance, IShellLaunchConfig, ITerminalTab, Direction, ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -52,6 +52,44 @@ class SplitPaneContainer implements IView { } } + public resizePane(index: number, direction: Direction, amount: number): void { + // TODO: Should resize pane up/down resize the panel? + + // Only resize the correct dimension + const isHorizontal = direction === Direction.Left || direction === Direction.Right; + if (isHorizontal && this.orientation !== Orientation.HORIZONTAL || + !isHorizontal && this.orientation !== Orientation.VERTICAL) { + return; + } + + // Only resize when there is mor ethan one pane + if (this._children.length <= 1) { + return; + } + + // Get sizes + const sizes = []; + for (let i = 0; i < this._splitView.length; i++) { + sizes.push(this._splitView.getViewSize(i)); + } + + // Remove size from right pane, unless index is the last pane in which case use left pane + const isSizingRightPane = index !== this._children.length - 1; + const indexToChange = isSizingRightPane ? index + 1 : index - 1; + if (isSizingRightPane && direction === Direction.Left) { + amount *= -1; + } else if (!isSizingRightPane && direction === Direction.Right) { + amount *= -1; + } + sizes[index] += amount; + sizes[indexToChange] -= amount; + + // Apply + for (let i = 0; i < this._splitView.length - 1; i++) { + this._splitView.resizeView(i, sizes[i]); + } + } + private _addChild(size: number, instance: ITerminalInstance, index: number): void { const child = new SplitPane(this._height); child.orientation = this.orientation; @@ -181,7 +219,8 @@ export class TerminalTab extends Disposable implements ITerminalTab { configHelper: TerminalConfigHelper, private _container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, - @IInstantiationService private readonly _instantiationService: IInstantiationService + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ITerminalService private readonly _terminalService: ITerminalService ) { super(); this._onDisposed = new Emitter(); @@ -349,4 +388,17 @@ export class TerminalTab extends Disposable implements ITerminalTab { public focusNextPane(): void { this.setActiveInstanceByIndex(this._activeInstanceIndex + 1); } + + public resizePane(direction: Direction): void { + if (!this._splitPaneContainer) { + return; + } + + const isHorizontal = (direction === Direction.Left || direction === Direction.Right); + const font = this._terminalService.configHelper.getFont(); + const amount = isHorizontal ? font.charWidth : font.charHeight; + if (amount) { + this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, amount); + } + } } From dfe6ae826162f0d5c8a7b99564d0429d57ba17c3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Feb 2018 12:21:42 -0800 Subject: [PATCH 3/8] Rotate terminal pane when panel position changes --- .../electron-browser/terminalInstance.ts | 21 ++++- .../terminal/electron-browser/terminalTab.ts | 88 ++++++++++--------- 2 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 50fcaca4306..6811123769b 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -168,7 +168,7 @@ export class TerminalInstance implements ITerminalInstance { // Only attach xterm.js to the DOM if the terminal panel has been opened before. if (_container) { - this.attachToElement(_container); + this._attachToElement(_container); } }); @@ -337,6 +337,25 @@ export class TerminalInstance implements ITerminalInstance { } public attachToElement(container: HTMLElement): void { + // The container did not change, do nothing + if (this._container === container) { + return; + } + + // Attach has not occured yet + if (!this._wrapperElement) { + this._attachToElement(container); + return; + } + + // TODO: Verify listeners still work + // The container changed, reattach + this._container.removeChild(this._wrapperElement); + this._container = container; + this._container.appendChild(this._wrapperElement); + } + + public _attachToElement(container: HTMLElement): void { this._xtermReadyPromise.then(() => { if (this._wrapperElement) { throw new Error('The terminal instance has already been attached to a container'); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index cd1494e2e5f..cf697159f04 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -11,12 +11,9 @@ import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/t import Event, { Emitter, anyEvent } from 'vs/base/common/event'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView } from 'vs/base/browser/ui/splitview/splitview'; +import { IPartService, Position } from 'vs/workbench/services/part/common/partService'; -class SplitPaneContainer implements IView { - public minimumSize: number = 40; - public maximumSize: number = Number.MAX_VALUE; - - // TODO: Swap height and width when rotation is implemented +class SplitPaneContainer { private _height: number; private _width: number; private _splitView: SplitView; @@ -33,12 +30,12 @@ class SplitPaneContainer implements IView { this._height = this._container.offsetHeight; this._splitView = new SplitView(this._container, { orientation: this.orientation }); this._splitView.onDidSashReset(() => this._resetSize()); - this.render(this._container); - this._splitView.layout(this._width); + this._splitView.layout(this.orientation === Orientation.HORIZONTAL ? this._width : this._height); } public split(instance: ITerminalInstance, index: number = this._children.length): void { - this._addChild(this._width / (this._children.length + 1), instance, index); + const size = this.orientation === Orientation.HORIZONTAL ? this._width : this._height; + this._addChild(size / (this._children.length + 1), instance, index); } private _resetSize(): void { @@ -91,7 +88,7 @@ class SplitPaneContainer implements IView { } private _addChild(size: number, instance: ITerminalInstance, index: number): void { - const child = new SplitPane(this._height); + const child = new SplitPane(this.orientation === Orientation.HORIZONTAL ? this._height : this._width); child.orientation = this.orientation; child.instance = instance; this._splitView.addView(child, size, index); @@ -107,10 +104,6 @@ class SplitPaneContainer implements IView { this._onDidChange = anyEvent(...this._children.map(c => c.onDidChange)); } - public render(container: HTMLElement): void { - this._container = container; - } - public remove(instance: ITerminalInstance): void { let index = null; for (let i = 0; i < this._children.length; i++) { @@ -125,32 +118,41 @@ class SplitPaneContainer implements IView { } } - public layoutBox(width: number, height: number): void { + public layout(width: number, height: number): void { + this._width = width; + this._height = height; if (this.orientation === Orientation.HORIZONTAL) { - this.layout(height); - this.orthogonalLayout(width); + this._splitView.layout(width); + this._children.forEach(c => c.orthogonalLayout(height)); } else { - this.layout(width); - this.orthogonalLayout(height); + this._splitView.layout(height); + this._children.forEach(c => c.orthogonalLayout(width)); } } - public layout(size: number): void { - // Only layout when both sizes are known - this._height = size; - if (!this._height) { + public setOrientation(orientation: Orientation): void { + if (this.orientation === orientation) { return; } + this.orientation = orientation; - this._children.forEach(c => c.orthogonalLayout(this._height)); - } - - public orthogonalLayout(size: number): void { - this._width = size; - - if (this._splitView) { - this._splitView.layout(this._width); + // Remove old split view + while (this._container.children.length > 0) { + this._container.removeChild(this._container.children[0]); } + this._splitView.dispose(); + + // Create new split view with updated orientation + this._splitView = new SplitView(this._container, { orientation }); + this._splitView.onDidSashReset(() => this._resetSize()); + this._children.forEach(child => { + child.orientation = orientation; + this._splitView.addView(child, 1); + }); + + // Allow time for a layout to occur + this.layout(this._container.offsetWidth, this._container.offsetHeight); + this._resetSize(); } } @@ -161,7 +163,6 @@ class SplitPane implements IView { public instance: ITerminalInstance; public orientation: Orientation | undefined; protected _size: number; - private _isContainerSet: boolean = false; private _onDidChange: Event = Event.None; public get onDidChange(): Event { return this._onDidChange; } @@ -175,10 +176,7 @@ class SplitPane implements IView { if (!container) { return; } - if (!this._isContainerSet && this.instance) { - this.instance.attachToElement(container); - this._isContainerSet = true; - } + this.instance.attachToElement(container); } public layout(size: number): void { @@ -204,6 +202,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { private _terminalInstances: ITerminalInstance[] = []; private _splitPaneContainer: SplitPaneContainer | undefined; private _tabElement: HTMLElement; + private _panelPosition: Position = Position.BOTTOM; private _activeInstanceIndex: number; @@ -220,7 +219,8 @@ export class TerminalTab extends Disposable implements ITerminalTab { private _container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ITerminalService private readonly _terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService, + @IPartService private readonly _partService: IPartService ) { super(); this._onDisposed = new Emitter(); @@ -328,7 +328,9 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._tabElement.classList.add('terminal-tab'); this._container.appendChild(this._tabElement); if (!this._splitPaneContainer) { - this._splitPaneContainer = new SplitPaneContainer(this._tabElement, Orientation.HORIZONTAL); + this._panelPosition = this._partService.getPanelPosition(); + const orientation = this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; + this._splitPaneContainer = new SplitPaneContainer(this._tabElement, orientation); this.terminalInstances.forEach(instance => this._splitPaneContainer.split(instance)); } } @@ -358,8 +360,6 @@ export class TerminalTab extends Disposable implements ITerminalTab { configHelper, undefined, shellLaunchConfig); - // TODO: Should this be pulled from the splitpanes instead? Currently there are 2 sources of truth. - // _terminalInstances is also the order they were created, not the order in which they appear this._terminalInstances.splice(this._activeInstanceIndex + 1, 0, instance); this._initInstanceListeners(instance); this._setActiveInstance(instance); @@ -377,7 +377,15 @@ export class TerminalTab extends Disposable implements ITerminalTab { public layout(width: number, height: number): void { if (this._splitPaneContainer) { - this._splitPaneContainer.layoutBox(width, height); + // Check if the panel position changed and rotate panes if so + const newPanelPosition = this._partService.getPanelPosition(); + if (newPanelPosition !== this._panelPosition) { + const newOrientation = newPanelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; + this._splitPaneContainer.setOrientation(newOrientation); + } + this._panelPosition = newPanelPosition; + + this._splitPaneContainer.layout(width, height); } } From fd4977f028ddf400d01cbfbd6be95aab0bdfa1b1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Feb 2018 12:30:07 -0800 Subject: [PATCH 4/8] Support up/down focus pane keybindings --- .../electron-browser/terminal.contribution.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index 769af803b78..5f250fc0a84 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -422,11 +422,19 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitVerticalTer }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split Vertically', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousPaneTerminalAction, FocusPreviousPaneTerminalAction.ID, FocusPreviousPaneTerminalAction.LABEL, { primary: KeyMod.Alt | KeyCode.LeftArrow, - mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.LeftArrow } + secondary: [KeyMod.Alt | KeyCode.UpArrow], + mac: { + primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.LeftArrow, + secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.UpArrow] + } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Previous Pane', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextPaneTerminalAction, FocusNextPaneTerminalAction.ID, FocusNextPaneTerminalAction.LABEL, { primary: KeyMod.Alt | KeyCode.RightArrow, - mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.RightArrow } + secondary: [KeyMod.Alt | KeyCode.DownArrow], + mac: { + primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.RightArrow, + secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.DownArrow] + } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Next Pane', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneLeftTerminalAction, ResizePaneLeftTerminalAction.ID, ResizePaneLeftTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow, From 7d99f14ac0bf0ed5da22b6a292800a056676ecab Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Feb 2018 12:30:37 -0800 Subject: [PATCH 5/8] Support vertical resize commands --- .../parts/terminal/electron-browser/terminalTab.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index cf697159f04..2bffb957e9a 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -71,11 +71,15 @@ class SplitPaneContainer { } // Remove size from right pane, unless index is the last pane in which case use left pane - const isSizingRightPane = index !== this._children.length - 1; - const indexToChange = isSizingRightPane ? index + 1 : index - 1; - if (isSizingRightPane && direction === Direction.Left) { + const isSizingEndPane = index !== this._children.length - 1; + const indexToChange = isSizingEndPane ? index + 1 : index - 1; + if (isSizingEndPane && direction === Direction.Left) { amount *= -1; - } else if (!isSizingRightPane && direction === Direction.Right) { + } else if (!isSizingEndPane && direction === Direction.Right) { + amount *= -1; + } else if (isSizingEndPane && direction === Direction.Up) { + amount *= -1; + } else if (!isSizingEndPane && direction === Direction.Down) { amount *= -1; } sizes[index] += amount; From 202cfa2ec6a1490bacfe540c1fba7f8406b0e721 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Feb 2018 12:35:16 -0800 Subject: [PATCH 6/8] Draw split border in vertical orientation --- .../parts/terminal/electron-browser/media/terminal.css | 8 ++++++-- .../parts/terminal/electron-browser/terminalPanel.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css index 280fb9c685e..e6b426e6e09 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css @@ -76,11 +76,15 @@ box-sizing: border-box; } -.monaco-workbench .panel.integrated-terminal .split-view-view:not(:first-child) { - /* border-color is set by theme key terminal.border */ +/* border-color is set by theme key terminal.border */ +.monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:first-child) { border-left-width: 1px; border-left-style: solid; } +.monaco-workbench .panel.integrated-terminal .monaco-split-view2.vertical .split-view-view:not(:first-child) { + border-top-width: 1px; + border-top-style: solid; +} .monaco-workbench .panel.integrated-terminal.enable-ligatures { font-variant-ligatures: normal; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index a66423a6618..42ec35b4024 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -298,7 +298,7 @@ export class TerminalPanel extends Panel { const borderColor = theme.getColor(TERMINAL_BORDER_COLOR) || theme.getColor(PANEL_BORDER); if (borderColor) { - css += `.monaco-workbench .panel.integrated-terminal .split-view-view:not(:first-child) { border-left-color: ${borderColor.toString()}; }`; + css += `.monaco-workbench .panel.integrated-terminal .split-view-view:not(:first-child) { border-color: ${borderColor.toString()}; }`; } // Borrow the editor's hover background for now From 5239149395e3a2264d1eb9873be4e7e5a60eb178 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Feb 2018 12:42:47 -0800 Subject: [PATCH 7/8] splitVertical -> split --- src/vs/workbench/parts/terminal/common/terminal.ts | 2 +- src/vs/workbench/parts/terminal/common/terminalService.ts | 2 +- .../terminal/electron-browser/terminal.contribution.ts | 8 ++++---- .../parts/terminal/electron-browser/terminalActions.ts | 8 ++++---- .../parts/terminal/electron-browser/terminalPanel.ts | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 74b01e5e015..6f330309063 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -163,7 +163,7 @@ export interface ITerminalService { setActiveInstance(terminalInstance: ITerminalInstance): void; setActiveInstanceByIndex(terminalIndex: number): void; getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance; - splitInstanceVertically(instance: ITerminalInstance): void; + splitInstance(instance: ITerminalInstance): void; getActiveTab(): ITerminalTab; setActiveTabToNext(): void; diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 82ef1eaf3d9..6f97e2f97fa 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -230,7 +230,7 @@ export abstract class TerminalService implements ITerminalService { this.setActiveTabByIndex(newIndex); } - public splitInstanceVertically(instanceToSplit: ITerminalInstance): void { + public splitInstance(instanceToSplit: ITerminalInstance): void { const tab = this._getTabForInstance(instanceToSplit); if (!tab) { return; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index 5f250fc0a84..5de4a9fac04 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -18,7 +18,7 @@ import { getTerminalDefaultShellUnixLike, getTerminalDefaultShellWindows } from import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitVerticalTerminalAction, FocusPreviousPaneTerminalAction, FocusNextPaneTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, ResizePaneDownTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitTerminalAction, FocusPreviousPaneTerminalAction, FocusNextPaneTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, ResizePaneDownTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -273,7 +273,7 @@ configurationRegistry.registerConfiguration({ MoveToLineEndTerminalAction.ID, TogglePanelAction.ID, 'workbench.action.quickOpenView', - SplitVerticalTerminalAction.ID, + SplitTerminalAction.ID, FocusPreviousPaneTerminalAction.ID, FocusNextPaneTerminalAction.ID, ResizePaneLeftTerminalAction.ID, @@ -416,10 +416,10 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineEndTer primary: null, mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line End', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitVerticalTerminalAction, SplitVerticalTerminalAction.ID, SplitVerticalTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL, { primary: null, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_D } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split Vertically', category); +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousPaneTerminalAction, FocusPreviousPaneTerminalAction.ID, FocusPreviousPaneTerminalAction.LABEL, { primary: KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.Alt | KeyCode.UpArrow], diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index 9edbbfdba1c..f00e4eb09af 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -296,9 +296,9 @@ export class CreateNewInActiveWorkspaceTerminalAction extends Action { } } -export class SplitVerticalTerminalAction extends Action { - public static readonly ID = 'workbench.action.terminal.splitVertical'; - public static readonly LABEL = nls.localize('workbench.action.terminal.splitVertical', "Split Terminal Vertically"); +export class SplitTerminalAction extends Action { + public static readonly ID = 'workbench.action.terminal.split'; + public static readonly LABEL = nls.localize('workbench.action.terminal.split', "Split Terminal"); constructor( id: string, label: string, @@ -312,7 +312,7 @@ export class SplitVerticalTerminalAction extends Action { if (!instance) { return TPromise.as(void 0); } - this._terminalService.splitInstanceVertically(instance); + this._terminalService.splitInstance(instance); return this._terminalService.showPanel(true); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index 42ec35b4024..7632917fb28 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -19,7 +19,7 @@ import { ITerminalService, TERMINAL_PANEL_ID } from 'vs/workbench/parts/terminal import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { TerminalFindWidget } from './terminalFindWidget'; import { editorHoverBackground, editorHoverBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitVerticalTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; import { Panel } from 'vs/workbench/browser/panel'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -149,7 +149,7 @@ export class TerminalPanel extends Panel { new Separator(), this._instantiationService.createInstance(ClearTerminalAction, ClearTerminalAction.ID, nls.localize('clear', "Clear")), new Separator(), - this._instantiationService.createInstance(SplitVerticalTerminalAction, SplitVerticalTerminalAction.ID, nls.localize('splitVertically', "Split Vertically")) + this._instantiationService.createInstance(SplitTerminalAction, SplitTerminalAction.ID, nls.localize('splitVertically', "Split Vertically")) ]; this._contextMenuActions.forEach(a => { this._register(a); From 6891b4a0134775c20fd03f48b39d86cabba87c07 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Feb 2018 13:50:55 -0800 Subject: [PATCH 8/8] Allow manual resizing of panes --- .../parts/terminal/electron-browser/terminalTab.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index 2bffb957e9a..99852a131ce 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -19,6 +19,11 @@ class SplitPaneContainer { private _splitView: SplitView; private _children: SplitPane[] = []; + // If the user sizes the panes manually, the proportional resizing will not be applied. + // Proportional resizing will come back when: a sash is reset, an instance is added/removed or + // the panel position moves. + private _isManuallySized: boolean = false; + private _onDidChange: Event = Event.None; public get onDidChange(): Event { return this._onDidChange; } @@ -29,7 +34,9 @@ class SplitPaneContainer { this._width = this._container.offsetWidth; this._height = this._container.offsetHeight; this._splitView = new SplitView(this._container, { orientation: this.orientation }); + // TODO: Dispose listeners this._splitView.onDidSashReset(() => this._resetSize()); + this._splitView.onDidSashChange(() => this._isManuallySized = true); this._splitView.layout(this.orientation === Orientation.HORIZONTAL ? this._width : this._height); } @@ -39,6 +46,7 @@ class SplitPaneContainer { } private _resetSize(): void { + // TODO: Optimize temrinal instance layout let totalSize = 0; for (let i = 0; i < this._splitView.length; i++) { totalSize += this._splitView.getViewSize(i); @@ -47,6 +55,7 @@ class SplitPaneContainer { for (let i = 0; i < this._splitView.length - 1; i++) { this._splitView.resizeView(i, newSize); } + this._isManuallySized = false; } public resizePane(index: number, direction: Direction, amount: number): void { @@ -123,6 +132,9 @@ class SplitPaneContainer { } public layout(width: number, height: number): void { + if (!this._isManuallySized) { + this._resetSize(); + } this._width = width; this._height = height; if (this.orientation === Orientation.HORIZONTAL) {