diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/panelview.css index 6dfe2379b16..e4853977a88 100644 --- a/src/vs/base/browser/ui/splitview/panelview.css +++ b/src/vs/base/browser/ui/splitview/panelview.css @@ -83,4 +83,27 @@ .monaco-panel-view .panel > .panel-body { overflow: hidden; flex: 1; -} \ No newline at end of file +} + +/* Animation */ + +.monaco-panel-view.animated .split-view-view { + transition-duration: 0.15s; + -webkit-transition-duration: 0.15s; + -moz-transition-duration: 0.15s; + transition-timing-function: ease-out; + -webkit-transition-timing-function: ease-out; + -moz-transition-timing-function: ease-out; +} + +.monaco-panel-view.animated.vertical .split-view-view { + transition-property: height; + -webkit-transition-property: height; + -moz-transition-property: height; +} + +.monaco-panel-view.animated.horizontal .split-view-view { + transition-property: width; + -webkit-transition-property: width; + -moz-transition-property: width; +} diff --git a/src/vs/base/browser/ui/splitview/panelview.ts b/src/vs/base/browser/ui/splitview/panelview.ts index 8b02b510168..7d7caf121cd 100644 --- a/src/vs/base/browser/ui/splitview/panelview.ts +++ b/src/vs/base/browser/ui/splitview/panelview.ts @@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { $, append, addClass, removeClass, toggleClass, trackFocus } from 'vs/base/browser/dom'; import { firstIndex } from 'vs/base/common/arrays'; import { Color, RGBA } from 'vs/base/common/color'; -import { SplitView, IView } from './splitview2'; +import { SplitView, IView } from './splitview'; export interface IPanelOptions { ariaHeaderLabel?: string; @@ -34,20 +34,29 @@ export abstract class Panel implements IView { private static HEADER_SIZE = 22; - private _expanded: boolean; + protected _expanded: boolean; + private expandedSize: number | undefined = undefined; private _headerVisible = true; - private _onDidChange = new Emitter(); private _minimumBodySize: number; private _maximumBodySize: number; private ariaHeaderLabel: string; + private styles: IPanelStyles | undefined = undefined; + private el: HTMLElement; private header: HTMLElement; protected disposables: IDisposable[] = []; - get draggable(): HTMLElement { + private _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + get draggableElement(): HTMLElement { return this.header; } + get dropTargetElement(): HTMLElement { + return this.el; + } + private _dropBackground: Color | undefined; get dropBackground(): Color | undefined { return this._dropBackground; @@ -77,7 +86,7 @@ export abstract class Panel implements IView { get minimumSize(): number { const headerSize = this.headerSize; - const expanded = !this.headerVisible || this.expanded; + const expanded = !this.headerVisible || this.isExpanded(); const minimumBodySize = expanded ? this._minimumBodySize : 0; return headerSize + minimumBodySize; @@ -85,34 +94,31 @@ export abstract class Panel implements IView { get maximumSize(): number { const headerSize = this.headerSize; - const expanded = !this.headerVisible || this.expanded; + const expanded = !this.headerVisible || this.isExpanded(); const maximumBodySize = expanded ? this._maximumBodySize : 0; return headerSize + maximumBodySize; } - readonly onDidChange: Event = this._onDidChange.event; - constructor(options: IPanelOptions = {}) { this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded; this.ariaHeaderLabel = options.ariaHeaderLabel || ''; this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120; this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY; - this.header = $('.panel-header'); } - get expanded(): boolean { + isExpanded(): boolean { return this._expanded; } - set expanded(expanded: boolean) { + setExpanded(expanded: boolean): void { if (this._expanded === !!expanded) { return; } this._expanded = !!expanded; this.updateHeader(); - this._onDidChange.fire(); + this._onDidChange.fire(expanded ? this.expandedSize : undefined); } get headerVisible(): boolean { @@ -130,9 +136,10 @@ export abstract class Panel implements IView { } render(container: HTMLElement): void { - const panel = append(container, $('.panel')); + this.el = append(container, $('.panel')); - append(panel, this.header); + this.header = $('.panel-header'); + append(this.el, this.header); this.header.setAttribute('tabindex', '0'); this.header.setAttribute('role', 'toolbar'); this.header.setAttribute('aria-label', this.ariaHeaderLabel); @@ -148,16 +155,16 @@ export abstract class Panel implements IView { .map(e => new StandardKeyboardEvent(e)); onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space) - .event(() => this.expanded = !this.expanded, null, this.disposables); + .event(() => this.setExpanded(!this.isExpanded()), null, this.disposables); onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow) - .event(() => this.expanded = false, null, this.disposables); + .event(() => this.setExpanded(false), null, this.disposables); onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow) - .event(() => this.expanded = true, null, this.disposables); + .event(() => this.setExpanded(true), null, this.disposables); domEvent(this.header, 'click') - (() => this.expanded = !this.expanded, null, this.disposables); + (() => this.setExpanded(!this.isExpanded()), null, this.disposables); // TODO@Joao move this down to panelview // onHeaderKeyDown.filter(e => e.keyCode === KeyCode.UpArrow) @@ -166,30 +173,42 @@ export abstract class Panel implements IView { // onHeaderKeyDown.filter(e => e.keyCode === KeyCode.DownArrow) // .event(focusNext, this, this.disposables); - const body = append(panel, $('.panel-body')); + const body = append(this.el, $('.panel-body')); this.renderBody(body); } layout(size: number): void { const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0; this.layoutBody(size - headerSize); + + if (this.isExpanded()) { + this.expandedSize = size; + } } style(styles: IPanelStyles): void { - this.header.style.color = styles.headerForeground ? styles.headerForeground.toString() : null; - this.header.style.backgroundColor = styles.headerBackground ? styles.headerBackground.toString() : null; - this.header.style.borderTop = styles.headerHighContrastBorder ? `1px solid ${styles.headerHighContrastBorder}` : null; - this._dropBackground = styles.dropBackground; + this.styles = styles; + + if (!this.header) { + return; + } + + this.updateHeader(); } - private updateHeader(): void { - const expanded = !this.headerVisible || this.expanded; + protected updateHeader(): void { + const expanded = !this.headerVisible || this.isExpanded(); this.header.style.height = `${this.headerSize}px`; this.header.style.lineHeight = `${this.headerSize}px`; toggleClass(this.header, 'hidden', !this.headerVisible); toggleClass(this.header, 'expanded', expanded); this.header.setAttribute('aria-expanded', String(expanded)); + + this.header.style.color = this.styles.headerForeground ? this.styles.headerForeground.toString() : null; + this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : null; + this.header.style.borderTop = this.styles.headerHighContrastBorder ? `1px solid ${this.styles.headerHighContrastBorder}` : null; + this._dropBackground = this.styles.dropBackground; } protected abstract renderHeader(container: HTMLElement): void; @@ -217,17 +236,18 @@ class PanelDraggable implements IDisposable { readonly onDidDrop = this._onDidDrop.event; constructor(private panel: Panel, private context: IDndContext) { - domEvent(panel.draggable, 'dragstart')(this.onDragStart, this, this.disposables); - domEvent(panel.draggable, 'dragenter')(this.onDragEnter, this, this.disposables); - domEvent(panel.draggable, 'dragleave')(this.onDragLeave, this, this.disposables); - domEvent(panel.draggable, 'dragend')(this.onDragEnd, this, this.disposables); - domEvent(panel.draggable, 'drop')(this.onDrop, this, this.disposables); + panel.draggableElement.draggable = true; + domEvent(panel.draggableElement, 'dragstart')(this.onDragStart, this, this.disposables); + domEvent(panel.dropTargetElement, 'dragenter')(this.onDragEnter, this, this.disposables); + domEvent(panel.dropTargetElement, 'dragleave')(this.onDragLeave, this, this.disposables); + domEvent(panel.dropTargetElement, 'dragend')(this.onDragEnd, this, this.disposables); + domEvent(panel.dropTargetElement, 'drop')(this.onDrop, this, this.disposables); } private onDragStart(e: DragEvent): void { e.dataTransfer.effectAllowed = 'move'; - const dragImage = append(document.body, $('.monaco-panel-drag-image', {}, this.panel.draggable.textContent)); + const dragImage = append(document.body, $('.monaco-panel-drag-image', {}, this.panel.draggableElement.textContent)); e.dataTransfer.setDragImage(dragImage, -10, -10); setTimeout(() => document.body.removeChild(dragImage), 0); @@ -240,7 +260,7 @@ class PanelDraggable implements IDisposable { } this.dragOverCounter++; - this.renderHeader(); + this.render(); } private onDragLeave(e: DragEvent): void { @@ -251,7 +271,7 @@ class PanelDraggable implements IDisposable { this.dragOverCounter--; if (this.dragOverCounter === 0) { - this.renderHeader(); + this.render(); } } @@ -261,7 +281,7 @@ class PanelDraggable implements IDisposable { } this.dragOverCounter = 0; - this.renderHeader(); + this.render(); this.context.draggable = null; } @@ -271,7 +291,7 @@ class PanelDraggable implements IDisposable { } this.dragOverCounter = 0; - this.renderHeader(); + this.render(); if (this.context.draggable !== this) { this._onDidDrop.fire({ from: this.context.draggable.panel, to: this.panel }); @@ -280,14 +300,14 @@ class PanelDraggable implements IDisposable { this.context.draggable = null; } - private renderHeader(): void { + private render(): void { let backgroundColor: string = null; if (this.dragOverCounter > 0) { backgroundColor = (this.panel.dropBackground || PanelDraggable.DefaultDragOverBackgroundColor).toString(); } - this.panel.draggable.style.backgroundColor = backgroundColor; + this.panel.dropTargetElement.style.backgroundColor = backgroundColor; } dispose(): void { @@ -326,16 +346,15 @@ export class PanelView implements IDisposable { const disposables: IDisposable[] = []; panel.onDidChange(this.setupAnimation, this, disposables); + const panelItem = { panel, disposable: combinedDisposable(disposables) }; + this.panelItems.splice(index, 0, panelItem); + this.splitview.addView(panel, size, index); + if (this.dnd) { const draggable = new PanelDraggable(panel, this.dndContext); disposables.push(draggable); draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop, disposables); } - - const panelItem = { panel, disposable: combinedDisposable(disposables) }; - - this.panelItems.splice(index, 0, panelItem); - this.splitview.addView(panel, size, index); } removePanel(panel: Panel): void { @@ -359,7 +378,7 @@ export class PanelView implements IDisposable { } const [panelItem] = this.panelItems.splice(fromIndex, 1); - this.panelItems.splice(toIndex < fromIndex ? toIndex : toIndex - 1, 0, panelItem); + this.panelItems.splice(toIndex, 0, panelItem); this.splitview.moveView(fromIndex, toIndex); } @@ -374,6 +393,16 @@ export class PanelView implements IDisposable { this.splitview.resizeView(index, size); } + getPanelSize(panel: Panel): number { + const index = firstIndex(this.panelItems, item => item.panel === panel); + + if (index === -1) { + return -1; + } + + return this.splitview.getViewSize(index); + } + layout(size: number): void { this.splitview.layout(size); } diff --git a/src/vs/base/browser/ui/splitview/splitview.css b/src/vs/base/browser/ui/splitview/splitview.css index 4a6de69c521..41d3de5c826 100644 --- a/src/vs/base/browser/ui/splitview/splitview.css +++ b/src/vs/base/browser/ui/splitview/splitview.css @@ -3,96 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-split-view { +.monaco-split-view2 { position: relative; -} - -.monaco-split-view > .split-view-view { overflow: hidden; -} - -.monaco-split-view.vertical > .split-view-view { width: 100%; -} - -.monaco-split-view.horizontal > .split-view-view { height: 100%; } -.monaco-split-view > .split-view-view > .header { - position: relative; - line-height: 22px; - font-size: 11px; - font-weight: bold; - text-transform: uppercase; - padding-left: 20px; +.monaco-split-view2 > .split-view-view { overflow: hidden; - display: flex; } -.monaco-split-view > .split-view-view > .header.hide { - display: none; +.monaco-split-view2.vertical > .split-view-view { + width: 100%; } -/* Bold font style does not go well with CJK fonts */ -.monaco-split-view:lang(zh-Hans) > .split-view-view > .header, -.monaco-split-view:lang(zh-Hant) > .split-view-view > .header, -.monaco-split-view:lang(ja) > .split-view-view > .header, -.monaco-split-view:lang(ko) > .split-view-view > .header { font-weight: normal; } - -.monaco-split-view > .split-view-view > .header.collapsible { - cursor: pointer; +.monaco-split-view2.horizontal > .split-view-view { + height: 100%; } - -.monaco-split-view > .split-view-view > .header.collapsible { - background-image: url('arrow-collapse.svg'); - background-position: 2px center; - background-repeat: no-repeat; -} - -.monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) { - background-image: url('arrow-expand.svg'); - background-position: 2px center; - background-repeat: no-repeat; -} - -.vs-dark .monaco-split-view > .split-view-view > .header.collapsible { - background-image: url('arrow-collapse-dark.svg'); -} - -.vs-dark .monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) { - background-image: url('arrow-expand-dark.svg'); - background-position: 2px center; - background-repeat: no-repeat; -} - -/* Animation */ - -.monaco-split-view.animated > .split-view-view { - transition-duration: 0.15s; - -webkit-transition-duration: 0.15s; - -moz-transition-duration: 0.15s; - transition-timing-function: ease-out; - -webkit-transition-timing-function: ease-out; - -moz-transition-timing-function: ease-out; -} - -.monaco-split-view.vertical.animated > .split-view-view { - transition-property: height; - -webkit-transition-property: height; - -moz-transition-property: height; -} - -.monaco-split-view.horizontal.animated > .split-view-view { - transition-property: width; - -webkit-transition-property: width; - -moz-transition-property: width; -} - -.hc-black .split-view-view .action-label { - background: none; -} - -.hc-black .split-view-view > .header .action-label:before { - top: 4px !important; -} \ No newline at end of file diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 74a4873e181..ba0b37167ad 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -6,1043 +6,353 @@ 'use strict'; import 'vs/css!./splitview'; -import lifecycle = require('vs/base/common/lifecycle'); -import ee = require('vs/base/common/eventEmitter'); +import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import Event, { fromEventEmitter, mapEvent } from 'vs/base/common/event'; import types = require('vs/base/common/types'); import dom = require('vs/base/browser/dom'); -import numbers = require('vs/base/common/numbers'); -import sash = require('vs/base/browser/ui/sash/sash'); -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import Event, { Emitter } from 'vs/base/common/event'; -import { Color } from 'vs/base/common/color'; +import { clamp } from 'vs/base/common/numbers'; +import { range, firstIndex } from 'vs/base/common/arrays'; +import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash'; +export { Orientation } from 'vs/base/browser/ui/sash/sash'; -export enum Orientation { - VERTICAL, - HORIZONTAL -} - -export enum ViewSizing { - Flexible, - Fixed -} - -export interface IOptions { +export interface ISplitViewOptions { orientation?: Orientation; // default Orientation.VERTICAL - canChangeOrderByDragAndDrop?: boolean; } -export interface ISashEvent { +export interface IView { + readonly minimumSize: number; + readonly maximumSize: number; + readonly onDidChange: Event; + render(container: HTMLElement, orientation: Orientation): void; + layout(size: number, orientation: Orientation): void; +} + +interface ISashEvent { + sash: Sash; start: number; current: number; } -export interface IViewOptions { - sizing?: ViewSizing; - fixedSize?: number; - minimumSize?: number; -} - -export interface IView extends ee.IEventEmitter { - preferredSize: number; +interface IViewItem { + view: IView; size: number; - sizing: ViewSizing; - fixedSize: number; - minimumSize: number; - maximumSize: number; - draggableElement?: HTMLElement; - draggableLabel?: string; - render(container: HTMLElement, orientation: Orientation): void; - layout(size: number, orientation: Orientation): void; - focus(): void; + container: HTMLElement; + disposable: IDisposable; + layout(): void; } -interface IState { - start?: number; - sizes?: number[]; - up?: number[]; - down?: number[]; - maxUp?: number; - maxDown?: number; - collapses: number[]; - expands: number[]; +interface ISashItem { + sash: Sash; + disposable: IDisposable; } -export abstract class View extends ee.EventEmitter implements IView { - - size: number; - protected _sizing: ViewSizing; - protected _fixedSize: number; - protected _minimumSize: number; - - constructor(public preferredSize: number, opts: IViewOptions) { - super(); - - this.size = 0; - this._sizing = types.isUndefined(opts.sizing) ? ViewSizing.Flexible : opts.sizing; - this._fixedSize = types.isUndefined(opts.fixedSize) ? 22 : opts.fixedSize; - this._minimumSize = types.isUndefined(opts.minimumSize) ? 22 : opts.minimumSize; - } - - get sizing(): ViewSizing { return this._sizing; } - get fixedSize(): number { return this._fixedSize; } - get minimumSize(): number { return this.sizing === ViewSizing.Fixed ? this.fixedSize : this._minimumSize; } - get maximumSize(): number { return this.sizing === ViewSizing.Fixed ? this.fixedSize : Number.POSITIVE_INFINITY; } - - protected setFlexible(size?: number): void { - this._sizing = ViewSizing.Flexible; - this.emit('change', types.isUndefined(size) ? this._minimumSize : size); - } - - protected setFixed(size?: number): void { - this._sizing = ViewSizing.Fixed; - this._fixedSize = types.isUndefined(size) ? this._fixedSize : size; - this.emit('change', this._fixedSize); - } - - abstract render(container: HTMLElement, orientation: Orientation): void; - abstract focus(): void; - abstract layout(size: number, orientation: Orientation): void; +interface ISashDragState { + index: number; + start: number; + sizes: number[]; + minDelta: number; + maxDelta: number; } -export interface IHeaderViewOptions extends IHeaderViewStyles, IViewOptions { - headerSize?: number; -} - -export interface IHeaderViewStyles { - headerForeground?: Color; - headerBackground?: Color; - headerHighContrastBorder?: Color; -} - -const headerDefaultOpts = { - headerBackground: Color.fromHex('#808080').transparent(0.2) -}; - -export abstract class HeaderView extends View { - - private _headerSize: number; - private _showHeader: boolean; - - protected header: HTMLElement; - protected body: HTMLElement; - - private headerForeground: Color; - private headerBackground: Color; - private headerHighContrastBorder: Color; - - constructor(initialSize: number, opts: IHeaderViewOptions) { - super(initialSize, opts); - - this._headerSize = types.isUndefined(opts.headerSize) ? 22 : opts.headerSize; - this._showHeader = this._headerSize > 0; - - this.headerForeground = opts.headerForeground; - this.headerBackground = opts.headerBackground || headerDefaultOpts.headerBackground; - this.headerHighContrastBorder = opts.headerHighContrastBorder; - } - - style(styles: IHeaderViewStyles): void { - this.headerForeground = styles.headerForeground; - this.headerBackground = styles.headerBackground; - this.headerHighContrastBorder = styles.headerHighContrastBorder; - - this.applyStyles(); - } - - protected get headerSize(): number { - return this._showHeader ? this._headerSize : 0; - } - - protected applyStyles(): void { - if (this.header) { - const headerForegroundColor = this.headerForeground ? this.headerForeground.toString() : null; - const headerBackgroundColor = this.headerBackground ? this.headerBackground.toString() : null; - const headerHighContrastBorderColor = this.headerHighContrastBorder ? this.headerHighContrastBorder.toString() : null; - - this.header.style.color = headerForegroundColor; - this.header.style.backgroundColor = headerBackgroundColor; - this.header.style.borderTop = headerHighContrastBorderColor ? `1px solid ${headerHighContrastBorderColor}` : null; - } - } - - get draggableElement(): HTMLElement { return this.header; } - - render(container: HTMLElement, orientation: Orientation): void { - this.header = document.createElement('div'); - this.header.className = 'header'; - - let headerSize = this.headerSize + 'px'; - - if (orientation === Orientation.HORIZONTAL) { - this.header.style.width = headerSize; - } else { - this.header.style.height = headerSize; - } - - if (this._showHeader) { - this.renderHeader(this.header); - container.appendChild(this.header); - } - - this.body = document.createElement('div'); - this.body.className = 'body'; - - this.layoutBodyContainer(orientation); - this.renderBody(this.body); - container.appendChild(this.body); - - this.applyStyles(); - } - - showHeader(): boolean { - if (!this._showHeader) { - if (!this.body.parentElement.contains(this.header)) { - this.renderHeader(this.header); - this.body.parentElement.insertBefore(this.header, this.body); - } - dom.removeClass(this.header, 'hide'); - this._showHeader = true; - return true; - } - return false; - } - - hideHeader(): boolean { - if (this._showHeader) { - dom.addClass(this.header, 'hide'); - this._showHeader = false; - return true; - } - return false; - } - - layout(size: number, orientation: Orientation): void { - this.layoutBodyContainer(orientation); - this.layoutBody(size - this.headerSize); - } - - private layoutBodyContainer(orientation: Orientation): void { - let size = `calc(100% - ${this.headerSize}px)`; - - if (orientation === Orientation.HORIZONTAL) { - this.body.style.width = size; - } else { - this.body.style.height = size; - } - } - - dispose(): void { - this.header = null; - this.body = null; - - super.dispose(); - } - - protected abstract renderHeader(container: HTMLElement): void; - protected abstract renderBody(container: HTMLElement): void; - protected abstract layoutBody(size: number): void; -} - -export interface IAbstractCollapsibleViewOptions { - sizing: ViewSizing; - ariaHeaderLabel?: string; - bodySize?: number; - initialState?: CollapsibleState; -} - -export enum CollapsibleState { - EXPANDED, - COLLAPSED -} - -export abstract class AbstractCollapsibleView extends HeaderView { - - protected state: CollapsibleState; - - private ariaHeaderLabel: string; - private headerClickListener: lifecycle.IDisposable; - private headerKeyListener: lifecycle.IDisposable; - private focusTracker: dom.IFocusTracker; - private _bodySize: number; - private _previousSize: number = null; - private readonly viewSizing: ViewSizing; - - constructor(initialSize: number | undefined, opts: IAbstractCollapsibleViewOptions) { - super(initialSize, opts); - this.viewSizing = opts.sizing; - this.ariaHeaderLabel = opts.ariaHeaderLabel; - - this.setBodySize(types.isUndefined(opts.bodySize) ? 22 : opts.bodySize); - - if (typeof this.preferredSize === 'undefined') { - this.preferredSize = this._bodySize + this.headerSize; - } - - this.changeState(types.isUndefined(opts.initialState) ? CollapsibleState.EXPANDED : opts.initialState); - } - - get previousSize(): number { - return this._previousSize; - } - - setBodySize(bodySize: number) { - this._bodySize = bodySize; - this.updateSize(); - } - - private updateSize() { - if (this.viewSizing === ViewSizing.Fixed) { - this.setFixed(this.state === CollapsibleState.EXPANDED ? this._bodySize + this.headerSize : this.headerSize); - } else { - this._minimumSize = this._bodySize + this.headerSize; - this._previousSize = !this.previousSize || this._previousSize < this._minimumSize ? this._minimumSize : this._previousSize; - if (this.state === CollapsibleState.EXPANDED) { - this.setFlexible(this._previousSize || this._minimumSize); - } else { - this._previousSize = this.size || this._minimumSize; - this.setFixed(this.headerSize); - } - } - } - - render(container: HTMLElement, orientation: Orientation): void { - super.render(container, orientation); - - dom.addClass(this.header, 'collapsible'); - dom.addClass(this.body, 'collapsible'); - - // Keyboard access - this.header.setAttribute('tabindex', '0'); - this.header.setAttribute('role', 'toolbar'); - if (this.ariaHeaderLabel) { - this.header.setAttribute('aria-label', this.ariaHeaderLabel); - } - this.header.setAttribute('aria-expanded', String(this.state === CollapsibleState.EXPANDED)); - this.headerKeyListener = dom.addDisposableListener(this.header, dom.EventType.KEY_DOWN, (e) => { - let event = new StandardKeyboardEvent(e); - let eventHandled = false; - if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space) || (event.equals(KeyCode.LeftArrow) && this.state === CollapsibleState.EXPANDED) || (event.equals(KeyCode.RightArrow) && this.state === CollapsibleState.COLLAPSED)) { - this.toggleExpansion(); - eventHandled = true; - } else if (event.equals(KeyCode.Escape)) { - this.header.blur(); - eventHandled = true; - } else if (event.equals(KeyCode.UpArrow)) { - this.emit('focusPrevious'); - eventHandled = true; - } else if (event.equals(KeyCode.DownArrow)) { - this.emit('focusNext'); - eventHandled = true; - } - - if (eventHandled) { - dom.EventHelper.stop(event, true); - } - }); - - // Mouse access - this.headerClickListener = dom.addDisposableListener(this.header, dom.EventType.CLICK, () => this.toggleExpansion()); - - // Track state of focus in header so that other components can adjust styles based on that - // (for example show or hide actions based on the state of being focused or not) - this.focusTracker = dom.trackFocus(this.header); - this.focusTracker.addFocusListener(() => { - dom.addClass(this.header, 'focused'); - }); - - this.focusTracker.addBlurListener(() => { - dom.removeClass(this.header, 'focused'); - }); - } - - focus(): void { - if (this.header) { - this.header.focus(); - } - } - - layout(size: number, orientation: Orientation): void { - this.layoutHeader(); - super.layout(size, orientation); - } - - isExpanded(): boolean { - return this.state === CollapsibleState.EXPANDED; - } - - expand(): void { - if (this.isExpanded()) { - return; - } - - this.changeState(CollapsibleState.EXPANDED); - } - - collapse(): void { - if (!this.isExpanded()) { - return; - } - - this.changeState(CollapsibleState.COLLAPSED); - } - - toggleExpansion(): void { - if (this.isExpanded()) { - this.collapse(); - } else { - this.expand(); - } - } - - private layoutHeader(): void { - if (!this.header) { - return; - } - - if (this.state === CollapsibleState.COLLAPSED) { - dom.addClass(this.header, 'collapsed'); - } else { - dom.removeClass(this.header, 'collapsed'); - } - } - - protected changeState(state: CollapsibleState): void { - this.state = state; - - if (this.header) { - this.header.setAttribute('aria-expanded', String(this.state === CollapsibleState.EXPANDED)); - } - - this.layoutHeader(); - this.updateSize(); - } - - showHeader(): boolean { - const result = super.showHeader(); - if (result) { - this.updateSize(); - } - return result; - } - - hideHeader(): boolean { - const result = super.hideHeader(); - if (result) { - this.updateSize(); - } - return result; - } - - dispose(): void { - if (this.headerClickListener) { - this.headerClickListener.dispose(); - this.headerClickListener = null; - } - - if (this.headerKeyListener) { - this.headerKeyListener.dispose(); - this.headerKeyListener = null; - } - - if (this.focusTracker) { - this.focusTracker.dispose(); - this.focusTracker = null; - } - - super.dispose(); - } -} - -class PlainView extends View { - render() { } - focus() { } - layout() { } -} - -class DeadView extends PlainView { - - constructor(view: IView) { - super(view.size, { sizing: ViewSizing.Fixed, fixedSize: 0 }); - } -} - -class VoidView extends PlainView { - - constructor() { - super(0, { sizing: ViewSizing.Fixed, minimumSize: 0, fixedSize: 0 }); - } - - setFlexible(size?: number): void { - super.setFlexible(size); - } - - setFixed(size?: number): void { - super.setFixed(size); - } -} - -function sum(arr: number[]): number { - return arr.reduce((a, b) => a + b); -} - -export interface SplitViewStyles { - dropBackground?: Color; -} - -export class SplitView extends lifecycle.Disposable implements - sash.IHorizontalSashLayoutProvider, - sash.IVerticalSashLayoutProvider { +export class SplitView implements IDisposable { private orientation: Orientation; - private canDragAndDrop: boolean; private el: HTMLElement; - private size: number; - private viewElements: HTMLElement[]; - private views: IView[]; - private viewChangeListeners: lifecycle.IDisposable[]; - private viewFocusPreviousListeners: lifecycle.IDisposable[]; - private viewFocusNextListeners: lifecycle.IDisposable[]; - private viewFocusListeners: lifecycle.IDisposable[]; - private viewDnDListeners: lifecycle.IDisposable[][]; - private sashOrientation: sash.Orientation; - private sashes: sash.Sash[]; - private sashesListeners: lifecycle.IDisposable[]; - private measureContainerSize: () => number; - private layoutViewElement: (viewElement: HTMLElement, size: number) => void; - private eventWrapper: (event: sash.ISashEvent) => ISashEvent; - private animationTimeout: number; - private state: IState; - private draggedView: IView; - private dropBackground: Color; - - private _onFocus: Emitter = this._register(new Emitter()); - readonly onFocus: Event = this._onFocus.event; - - private _onDidOrderChange: Emitter = this._register(new Emitter()); - readonly onDidOrderChange: Event = this._onDidOrderChange.event; + private size = 0; + private contentSize = 0; + private viewItems: IViewItem[] = []; + private sashItems: ISashItem[] = []; + private sashDragState: ISashDragState; get length(): number { - return this.views.length; + return this.viewItems.length; } - constructor(container: HTMLElement, options?: IOptions) { - super(); - options = options || {}; - + constructor(private container: HTMLElement, options: ISplitViewOptions = {}) { this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation; - this.canDragAndDrop = !!options.canChangeOrderByDragAndDrop; this.el = document.createElement('div'); - dom.addClass(this.el, 'monaco-split-view'); + dom.addClass(this.el, 'monaco-split-view2'); dom.addClass(this.el, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal'); container.appendChild(this.el); + } - this.size = null; - this.viewElements = []; - this.views = []; - this.viewChangeListeners = []; - this.viewFocusPreviousListeners = []; - this.viewFocusNextListeners = []; - this.viewFocusListeners = []; - this.viewDnDListeners = []; - this.sashes = []; - this.sashesListeners = []; - this.animationTimeout = null; + addView(view: IView, size: number, index = this.viewItems.length): void { + // Add view + const container = dom.$('.split-view-view'); - this.sashOrientation = this.orientation === Orientation.VERTICAL - ? sash.Orientation.HORIZONTAL - : sash.Orientation.VERTICAL; - - if (this.orientation === Orientation.VERTICAL) { - this.measureContainerSize = () => dom.getContentHeight(container); - this.layoutViewElement = (viewElement, size) => viewElement.style.height = size + 'px'; - this.eventWrapper = e => { return { start: e.startY, current: e.currentY }; }; + if (index === this.viewItems.length) { + this.el.appendChild(container); } else { - this.measureContainerSize = () => dom.getContentWidth(container); - this.layoutViewElement = (viewElement, size) => viewElement.style.width = size + 'px'; - this.eventWrapper = e => { return { start: e.startX, current: e.currentX }; }; + this.el.insertBefore(container, this.el.children.item(index)); } - // The void space exists to handle the case where all other views are fixed size - this.addView(new VoidView(), 0); - } + const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size)); + const containerDisposable = toDisposable(() => this.el.removeChild(container)); + const disposable = combinedDisposable([onChangeDisposable, containerDisposable]); - getView(index: number): IView | undefined { - return this.views[index]; - } + const layoutContainer = this.orientation === Orientation.VERTICAL + ? size => item.container.style.height = `${item.size}px` + : size => item.container.style.width = `${item.size}px`; - getViews(): T[] { - return this.views.slice(0, this.views.length - 1); - } + const layout = () => { + layoutContainer(item.size); + item.view.layout(item.size, this.orientation); + }; - addView(view: IView, index = this.views.length - 1): void { - /** - * Reset size to null. This will layout newly added views to initial weights. - */ - this.size = null; - - let viewCount = this.views.length; - - // Create view container - let viewElement = document.createElement('div'); - dom.addClass(viewElement, 'split-view-view'); - this.viewElements.splice(index, 0, viewElement); - - // Create view - view.render(viewElement, this.orientation); - this.views.splice(index, 0, view); - - // Render view - if (index === viewCount) { - this.el.appendChild(viewElement); - } else { - this.el.insertBefore(viewElement, this.el.children.item(index)); - } - - // Listen to Drag and Drop - this.viewDnDListeners[index] = this.createDnDListeners(view, viewElement); + size = Math.round(size); + const item: IViewItem = { view, container, size, layout, disposable }; + this.viewItems.splice(index, 0, item); // Add sash - if (this.views.length > 2) { - let s = new sash.Sash(this.el, this, { orientation: this.sashOrientation }); - this.sashes.splice(index - 1, 0, s); - this.sashesListeners.push(s.addListener('start', e => this.onSashStart(s, this.eventWrapper(e)))); - this.sashesListeners.push(s.addListener('change', e => this.onSashChange(s, this.eventWrapper(e)))); + if (this.viewItems.length > 1) { + const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; + const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) }; + const sash = new Sash(this.el, layoutProvider, { orientation }); + const sashEventMapper = this.orientation === Orientation.VERTICAL + ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY }) + : (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX }); + + const onStart = mapEvent(fromEventEmitter(sash, 'start'), sashEventMapper); + const onStartDisposable = onStart(this.onSashStart, this); + const onChange = mapEvent(fromEventEmitter(sash, 'change'), sashEventMapper); + const onSashChangeDisposable = onChange(this.onSashChange, this); + const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, sash]); + const sashItem: ISashItem = { sash, disposable }; + + this.sashItems.splice(index - 1, 0, sashItem); } - this.viewChangeListeners.splice(index, 0, view.addListener('change', size => this.onViewChange(view, size))); - this.onViewChange(view, view.minimumSize); - - let viewFocusTracker = dom.trackFocus(viewElement); - this.viewFocusListeners.splice(index, 0, viewFocusTracker); - viewFocusTracker.addFocusListener(() => this._onFocus.fire(view)); - - this.viewFocusPreviousListeners.splice(index, 0, view.addListener('focusPrevious', () => index > 0 && this.views[index - 1].focus())); - this.viewFocusNextListeners.splice(index, 0, view.addListener('focusNext', () => index < this.views.length && this.views[index + 1].focus())); + view.render(container, this.orientation); + this.relayout(); } - removeView(view: IView): void { - let index = this.views.indexOf(view); - - if (index < 0) { + removeView(index: number): void { + if (index < 0 || index >= this.viewItems.length) { return; } - this.size = null; - let deadView = new DeadView(view); - this.views[index] = deadView; - this.onViewChange(deadView, 0); + // Remove view + const viewItem = this.viewItems.splice(index, 1)[0]; + viewItem.disposable.dispose(); - let sashIndex = Math.max(index - 1, 0); - if (sashIndex < this.sashes.length) { - this.sashes[sashIndex].dispose(); - this.sashes.splice(sashIndex, 1); + // Remove sash + if (this.viewItems.length >= 1) { + const sashIndex = Math.max(index - 1, 0); + const sashItem = this.sashItems.splice(sashIndex, 1)[0]; + sashItem.disposable.dispose(); } - this.viewChangeListeners[index].dispose(); - this.viewChangeListeners.splice(index, 1); - - this.viewFocusPreviousListeners[index].dispose(); - this.viewFocusPreviousListeners.splice(index, 1); - - this.viewFocusListeners[index].dispose(); - this.viewFocusListeners.splice(index, 1); - - this.viewFocusNextListeners[index].dispose(); - this.viewFocusNextListeners.splice(index, 1); - - lifecycle.dispose(this.viewDnDListeners[index]); - this.viewDnDListeners.splice(index, 1); - - this.views.splice(index, 1); - this.el.removeChild(this.viewElements[index]); - this.viewElements.splice(index, 1); - - deadView.dispose(); - view.dispose(); + this.relayout(); } - layout(size?: number): void { - size = size || this.measureContainerSize(); - - if (this.size === null) { - this.size = size; - this.initialLayout(); + moveView(from: number, to: number): void { + if (from < 0 || from >= this.viewItems.length) { return; } - size = Math.max(size, this.views.reduce((t, v) => t + v.minimumSize, 0)); - - let diff = Math.abs(this.size - size); - let up = numbers.countToArray(this.views.length - 1, -1); - - let collapses = this.views.map(v => v.size - v.minimumSize); - let expands = this.views.map(v => v.maximumSize - v.size); - - if (size < this.size) { - this.expandCollapse(Math.min(diff, sum(collapses)), collapses, expands, up, []); - } else if (size > this.size) { - this.expandCollapse(Math.min(diff, sum(expands)), collapses, expands, [], up); - } - - this.size = size; - this.layoutViews(); - } - - style(styles: SplitViewStyles): void { - this.dropBackground = styles.dropBackground; - } - - private createDnDListeners(view: IView, viewElement: HTMLElement): lifecycle.IDisposable[] { - if (!this.canDragAndDrop || view instanceof VoidView) { - return []; - } - - const disposables: lifecycle.IDisposable[] = []; - - // Allow to drag - if (view.draggableElement) { - view.draggableElement.draggable = true; - disposables.push(dom.addDisposableListener(view.draggableElement, dom.EventType.DRAG_START, (e: DragEvent) => { - e.dataTransfer.effectAllowed = 'move'; - - const dragImage = document.createElement('div'); - dragImage.className = 'monaco-tree-drag-image'; - dragImage.textContent = view.draggableLabel ? view.draggableLabel : view.draggableElement.textContent; - document.body.appendChild(dragImage); - e.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => document.body.removeChild(dragImage), 0); - - this.draggedView = view; - })); - } - - // Drag enter - let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470 - disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DRAG_ENTER, (e: DragEvent) => { - if (this.draggedView && this.draggedView !== view) { - counter++; - this.updateFromDragging(view, viewElement, true); - } - })); - - // Drag leave - disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DRAG_LEAVE, (e: DragEvent) => { - if (this.draggedView && this.draggedView !== view) { - counter--; - if (counter === 0) { - this.updateFromDragging(view, viewElement, false); - } - } - })); - - // Drag end - disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DRAG_END, (e: DragEvent) => { - if (this.draggedView) { - counter = 0; - this.updateFromDragging(view, viewElement, false); - this.draggedView = null; - } - })); - - // Drop - disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DROP, (e: DragEvent) => { - dom.EventHelper.stop(e, true); - counter = 0; - this.updateFromDragging(view, viewElement, false); - if (this.draggedView && this.draggedView !== view) { - this.move(this.views.indexOf(this.draggedView), this.views.indexOf(view)); - } - this.draggedView = null; - })); - - return disposables; - } - - private updateFromDragging(view: IView, viewElement: HTMLElement, isDragging: boolean): void { - viewElement.style.backgroundColor = isDragging && this.dropBackground ? this.dropBackground.toString() : null; - } - - private move(fromIndex: number, toIndex: number): void { - if (fromIndex < 0 || toIndex > this.views.length - 2) { + if (to < 0 || to >= this.viewItems.length) { return; } - const [viewChangeListener] = this.viewChangeListeners.splice(fromIndex, 1); - this.viewChangeListeners.splice(toIndex, 0, viewChangeListener); - - const [viewFocusPreviousListener] = this.viewFocusPreviousListeners.splice(fromIndex, 1); - this.viewFocusPreviousListeners.splice(toIndex, 0, viewFocusPreviousListener); - - const [viewFocusListener] = this.viewFocusListeners.splice(fromIndex, 1); - this.viewFocusListeners.splice(toIndex, 0, viewFocusListener); - - const [viewFocusNextListener] = this.viewFocusNextListeners.splice(fromIndex, 1); - this.viewFocusNextListeners.splice(toIndex, 0, viewFocusNextListener); - - const [viewDnDListeners] = this.viewDnDListeners.splice(fromIndex, 1); - this.viewDnDListeners.splice(toIndex, 0, viewDnDListeners); - - const [view] = this.views.splice(fromIndex, 1); - this.views.splice(toIndex, 0, view); - - this.el.removeChild(this.viewElements[fromIndex]); - this.el.insertBefore(this.viewElements[fromIndex], this.viewElements[toIndex < fromIndex ? toIndex : toIndex + 1]); - const [viewElement] = this.viewElements.splice(fromIndex, 1); - this.viewElements.splice(toIndex, 0, viewElement); - - this.layout(); - - this._onDidOrderChange.fire(); - } - - private onSashStart(sash: sash.Sash, event: ISashEvent): void { - let i = this.sashes.indexOf(sash); - let collapses = this.views.map(v => v.size - v.minimumSize); - let expands = this.views.map(v => v.maximumSize - v.size); - - let up = numbers.countToArray(i, -1); - let down = numbers.countToArray(i + 1, this.views.length); - - let collapsesUp = up.map(i => collapses[i]); - let collapsesDown = down.map(i => collapses[i]); - let expandsUp = up.map(i => expands[i]); - let expandsDown = down.map(i => expands[i]); - - this.state = { - start: event.start, - sizes: this.views.map(v => v.size), - up: up, - down: down, - maxUp: Math.min(sum(collapsesUp), sum(expandsDown)), - maxDown: Math.min(sum(expandsUp), sum(collapsesDown)), - collapses: collapses, - expands: expands - }; - } - - private onSashChange(sash: sash.Sash, event: ISashEvent): void { - let diff = event.current - this.state.start; - - for (let i = 0; i < this.views.length; i++) { - this.views[i].size = this.views[i].preferredSize = this.state.sizes[i]; + if (from === to) { + return; } - if (diff < 0) { - this.expandCollapse(Math.min(-diff, this.state.maxUp), this.state.collapses, this.state.expands, this.state.up, this.state.down); + const viewItem = this.viewItems.splice(from, 1)[0]; + this.viewItems.splice(to, 0, viewItem); + + if (to + 1 < this.viewItems.length) { + this.el.insertBefore(viewItem.container, this.viewItems[to + 1].container); } else { - this.expandCollapse(Math.min(diff, this.state.maxDown), this.state.collapses, this.state.expands, this.state.down, this.state.up); + this.el.appendChild(viewItem.container); } this.layoutViews(); } - // Main algorithm - private expandCollapse(collapse: number, collapses: number[], expands: number[], collapseIndexes: number[], expandIndexes: number[]): void { - let totalCollapse = collapse; - let totalExpand = totalCollapse; - - collapseIndexes.forEach(i => { - let collapse = Math.min(collapses[i], totalCollapse); - totalCollapse -= collapse; - this.views[i].size -= collapse; - }); - - expandIndexes.forEach(i => { - let expand = Math.min(expands[i], totalExpand); - totalExpand -= expand; - this.views[i].size += expand; - }); + private relayout(): void { + const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); + this.resize(this.viewItems.length - 1, this.contentSize - contentSize); } - private initialLayout(): void { - let totalWeight = 0; - let fixedSize = 0; + layout(size: number): void { + const previousSize = Math.max(this.size, this.contentSize); + this.size = size; + this.resize(this.viewItems.length - 1, size - previousSize); + } - this.views.forEach((v, i) => { - if (v.sizing === ViewSizing.Flexible) { - totalWeight += v.preferredSize; - } else { - fixedSize += v.fixedSize; - } - }); + private onSashStart({ sash, start }: ISashEvent): void { + const index = firstIndex(this.sashItems, item => item.sash === sash); + const sizes = this.viewItems.map(i => i.size); - let flexibleSize = this.size - fixedSize; + const upIndexes = range(index, -1); + const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); + const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); - this.views.forEach((v, i) => { - if (v.sizing === ViewSizing.Flexible) { - if (totalWeight === 0) { - v.size = flexibleSize; - } else { - v.size = v.preferredSize * flexibleSize / totalWeight; - } - } else { - v.size = v.fixedSize; - } - }); + const downIndexes = range(index + 1, this.viewItems.length); + const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); + const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); - // Leftover - let index = this.getLastFlexibleViewIndex(); - if (index >= 0) { - this.views[index].size += this.size - this.views.reduce((t, v) => t + v.size, 0); + const minDelta = -Math.min(collapseUp, expandDown); + const maxDelta = Math.min(collapseDown, expandUp); + + this.sashDragState = { start, index, sizes, minDelta, maxDelta }; + } + + private onSashChange({ sash, current }: ISashEvent): void { + const { index, start, sizes, minDelta, maxDelta } = this.sashDragState; + const delta = clamp(current - start, minDelta, maxDelta); + + this.resize(index, delta, sizes); + } + + private onViewChange(item: IViewItem, size: number | undefined): void { + const index = this.viewItems.indexOf(item); + + if (index < 0 || index >= this.viewItems.length) { + return; } - // Layout + size = typeof size === 'number' ? size : item.size; + size = clamp(size, item.view.minimumSize, item.view.maximumSize); + item.size = size; + this.relayout(); + } + + resizeView(index: number, size: number): void { + if (index < 0 || index >= this.viewItems.length) { + return; + } + + const item = this.viewItems[index]; + size = Math.round(size); + size = clamp(size, item.view.minimumSize, item.view.maximumSize); + let delta = size - item.size; + + if (delta !== 0 && index < this.viewItems.length - 1) { + const downIndexes = range(index + 1, this.viewItems.length); + const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0); + const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); + const deltaDown = clamp(delta, -expandDown, collapseDown); + + this.resize(index, deltaDown); + delta -= deltaDown; + } + + if (delta !== 0 && index > 0) { + const upIndexes = range(index - 1, -1); + const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0); + const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); + const deltaUp = clamp(-delta, -collapseUp, expandUp); + + this.resize(index - 1, deltaUp); + } + } + + getViewSize(index: number): number { + if (index < 0 || index >= this.viewItems.length) { + return -1; + } + + return this.viewItems[index].size; + } + + private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size)): void { + if (index < 0 || index >= this.viewItems.length) { + return; + } + + if (delta !== 0) { + const upIndexes = range(index, -1); + const up = upIndexes.map(i => this.viewItems[i]); + const upSizes = upIndexes.map(i => sizes[i]); + const downIndexes = range(index + 1, this.viewItems.length); + const down = downIndexes.map(i => this.viewItems[i]); + const downSizes = downIndexes.map(i => sizes[i]); + + for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < up.length; i++) { + const item = up[i]; + const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize); + const viewDelta = size - upSizes[i]; + + deltaUp -= viewDelta; + item.size = size; + } + + for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < down.length; i++) { + const item = down[i]; + const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize); + const viewDelta = size - downSizes[i]; + + deltaDown += viewDelta; + item.size = size; + } + } + + let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); + let emptyDelta = this.size - contentSize; + + for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) { + const item = this.viewItems[i]; + const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize); + const viewDelta = size - item.size; + + emptyDelta -= viewDelta; + item.size = size; + } + + this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); + this.layoutViews(); } - private getLastFlexibleViewIndex(exceptIndex: number = null): number { - for (let i = this.views.length - 1; i >= 0; i--) { - if (exceptIndex === i) { - continue; - } - if (this.views[i].sizing === ViewSizing.Flexible) { - return i; - } - } - - return -1; - } - private layoutViews(): void { - for (let i = 0; i < this.views.length; i++) { - // Layout the view elements - this.layoutViewElement(this.viewElements[i], this.views[i].size); - - // Layout the views themselves - this.views[i].layout(this.views[i].size, this.orientation); - } - - // Layout the sashes - this.sashes.forEach(s => s.layout()); + this.viewItems.forEach(item => item.layout()); + this.sashItems.forEach(item => item.sash.layout()); // Update sashes enablement let previous = false; - let collapsesDown = this.views.map(v => previous = (v.size - v.minimumSize > 0) || previous); + const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous); previous = false; - let expandsDown = this.views.map(v => previous = (v.maximumSize - v.size > 0) || previous); + const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous); - let reverseViews = this.views.slice().reverse(); + const reverseViews = [...this.viewItems].reverse(); previous = false; - let collapsesUp = reverseViews.map(v => previous = (v.size - v.minimumSize > 0) || previous).reverse(); + const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse(); previous = false; - let expandsUp = reverseViews.map(v => previous = (v.maximumSize - v.size > 0) || previous).reverse(); + const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse(); - this.sashes.forEach((s, i) => { + this.sashItems.forEach((s, i) => { if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) { - s.enable(); + s.sash.enable(); } else { - s.disable(); + s.sash.disable(); } }); } - private onViewChange(view: IView, size: number): void { - if (view !== this.voidView) { - if (this.areAllViewsFixed()) { - this.voidView.setFlexible(); - } else { - this.voidView.setFixed(); + private getSashPosition(sash: Sash): number { + let position = 0; + + for (let i = 0; i < this.sashItems.length; i++) { + position += this.viewItems[i].size; + + if (this.sashItems[i].sash === sash) { + return position; } } - if (this.size === null) { - return; - } - - if (size === view.size) { - return; - } - - this.setupAnimation(); - - let index = this.views.indexOf(view); - let diff = Math.abs(size - view.size); - let up = numbers.countToArray(index - 1, -1); - let down = numbers.countToArray(index + 1, this.views.length); - let downUp = down.concat(up); - - let collapses = this.views.map(v => Math.max(v.size - v.minimumSize, 0)); - let expands = this.views.map(v => Math.max(v.maximumSize - v.size, 0)); - - let collapse: number, collapseIndexes: number[], expandIndexes: number[]; - - if (size < view.size) { - collapse = Math.min(downUp.reduce((t, i) => t + expands[i], 0), diff); - collapseIndexes = [index]; - expandIndexes = downUp; - - } else { - collapse = Math.min(downUp.reduce((t, i) => t + collapses[i], 0), diff); - collapseIndexes = downUp; - expandIndexes = [index]; - } - - this.expandCollapse(collapse, collapses, expands, collapseIndexes, expandIndexes); - this.layoutViews(); - } - - private setupAnimation(): void { - if (types.isNumber(this.animationTimeout)) { - window.clearTimeout(this.animationTimeout); - } - - dom.addClass(this.el, 'animated'); - this.animationTimeout = window.setTimeout(() => this.clearAnimation(), 200); - } - - private clearAnimation(): void { - this.animationTimeout = null; - dom.removeClass(this.el, 'animated'); - } - - private get voidView(): VoidView { - return this.views[this.views.length - 1] as VoidView; - } - - private areAllViewsFixed(): boolean { - return this.views.every((v, i) => v.sizing === ViewSizing.Fixed || i === this.views.length - 1); - } - - getVerticalSashLeft(sash: sash.Sash): number { - return this.getSashPosition(sash); - } - - getHorizontalSashTop(sash: sash.Sash): number { - return this.getSashPosition(sash); - } - - private getSashPosition(sash: sash.Sash): number { - let index = this.sashes.indexOf(sash); - let position = 0; - - for (let i = 0; i <= index; i++) { - position += this.views[i].size; - } - - return position; + return 0; } dispose(): void { - if (types.isNumber(this.animationTimeout)) { - window.clearTimeout(this.animationTimeout); - } - this.orientation = null; - this.size = null; - this.viewElements.forEach(e => this.el.removeChild(e)); - this.el = null; - this.viewElements = []; - this.views = lifecycle.dispose(this.views); - this.sashes = lifecycle.dispose(this.sashes); - this.sashesListeners = lifecycle.dispose(this.sashesListeners); - this.measureContainerSize = null; - this.layoutViewElement = null; - this.eventWrapper = null; - this.state = null; + this.viewItems.forEach(i => i.disposable.dispose()); + this.viewItems = []; - super.dispose(); + this.sashItems.forEach(i => i.disposable.dispose()); + this.sashItems = []; } } diff --git a/src/vs/base/browser/ui/splitview/splitview2.css b/src/vs/base/browser/ui/splitview/splitview2.css deleted file mode 100644 index 41d3de5c826..00000000000 --- a/src/vs/base/browser/ui/splitview/splitview2.css +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-split-view2 { - position: relative; - overflow: hidden; - width: 100%; - height: 100%; -} - -.monaco-split-view2 > .split-view-view { - overflow: hidden; -} - -.monaco-split-view2.vertical > .split-view-view { - width: 100%; -} - -.monaco-split-view2.horizontal > .split-view-view { - height: 100%; -} diff --git a/src/vs/base/browser/ui/splitview/splitview2.ts b/src/vs/base/browser/ui/splitview/splitview2.ts deleted file mode 100644 index b2deecf27f9..00000000000 --- a/src/vs/base/browser/ui/splitview/splitview2.ts +++ /dev/null @@ -1,342 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import 'vs/css!./splitview2'; -import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import Event, { fromEventEmitter, mapEvent } from 'vs/base/common/event'; -import types = require('vs/base/common/types'); -import dom = require('vs/base/browser/dom'); -import { clamp } from 'vs/base/common/numbers'; -import { range, firstIndex } from 'vs/base/common/arrays'; -import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash'; -export { Orientation } from 'vs/base/browser/ui/sash/sash'; - -export interface ISplitViewOptions { - orientation?: Orientation; // default Orientation.VERTICAL -} - -export interface IView { - readonly minimumSize: number; - readonly maximumSize: number; - readonly onDidChange: Event; - render(container: HTMLElement, orientation: Orientation): void; - layout(size: number, orientation: Orientation): void; -} - -interface ISashEvent { - sash: Sash; - start: number; - current: number; -} - -interface IViewItem { - view: IView; - size: number; - container: HTMLElement; - disposable: IDisposable; - layout(): void; -} - -interface ISashItem { - sash: Sash; - disposable: IDisposable; -} - -interface ISashDragState { - index: number; - start: number; - sizes: number[]; - minDelta: number; - maxDelta: number; -} - -export class SplitView implements IDisposable { - - private orientation: Orientation; - private el: HTMLElement; - private size = 0; - private contentSize = 0; - private viewItems: IViewItem[] = []; - private sashItems: ISashItem[] = []; - private sashDragState: ISashDragState; - - get length(): number { - return this.viewItems.length; - } - - constructor(private container: HTMLElement, options: ISplitViewOptions = {}) { - this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation; - - this.el = document.createElement('div'); - dom.addClass(this.el, 'monaco-split-view2'); - dom.addClass(this.el, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal'); - container.appendChild(this.el); - } - - addView(view: IView, size: number, index = this.viewItems.length): void { - // Add view - const container = dom.$('.split-view-view'); - - if (this.viewItems.length === 1) { - this.el.appendChild(container); - } else { - this.el.insertBefore(container, this.el.children.item(index)); - } - - const onChangeDisposable = mapEvent(view.onDidChange, () => item)(this.onViewChange, this); - const containerDisposable = toDisposable(() => this.el.removeChild(container)); - const disposable = combinedDisposable([onChangeDisposable, containerDisposable]); - - const layoutContainer = this.orientation === Orientation.VERTICAL - ? size => item.container.style.height = `${item.size}px` - : size => item.container.style.width = `${item.size}px`; - - const layout = () => { - layoutContainer(item.size); - item.view.layout(item.size, this.orientation); - }; - - size = Math.round(size); - const item: IViewItem = { view, container, size, layout, disposable }; - this.viewItems.splice(index, 0, item); - - // Add sash - if (this.viewItems.length > 1) { - const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; - const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) }; - const sash = new Sash(this.el, layoutProvider, { orientation }); - const sashEventMapper = this.orientation === Orientation.VERTICAL - ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY }) - : (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX }); - - const onStart = mapEvent(fromEventEmitter(sash, 'start'), sashEventMapper); - const onStartDisposable = onStart(this.onSashStart, this); - const onChange = mapEvent(fromEventEmitter(sash, 'change'), sashEventMapper); - const onSashChangeDisposable = onChange(this.onSashChange, this); - const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, sash]); - const sashItem: ISashItem = { sash, disposable }; - - this.sashItems.splice(index - 1, 0, sashItem); - } - - view.render(container, this.orientation); - this.relayout(); - } - - removeView(index: number): void { - if (index < 0 || index >= this.viewItems.length) { - return; - } - - // Remove view - const viewItem = this.viewItems.splice(index, 1)[0]; - viewItem.disposable.dispose(); - - // Remove sash - if (this.viewItems.length >= 1) { - const sashIndex = Math.max(index - 1, 0); - const sashItem = this.sashItems.splice(sashIndex, 1)[0]; - sashItem.disposable.dispose(); - } - - this.relayout(); - } - - moveView(from: number, to: number): void { - if (from < 0 || from >= this.viewItems.length) { - return; - } - - if (to < 0 || to >= this.viewItems.length) { - return; - } - - if (from === to) { - return; - } - - const viewItem = this.viewItems.splice(from, 1)[0]; - this.viewItems.splice(to, 0, viewItem); - this.layoutViews(); - } - - private relayout(): void { - const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - this.resize(this.viewItems.length - 1, this.contentSize - contentSize); - } - - layout(size: number): void { - const previousSize = Math.max(this.size, this.contentSize); - this.size = size; - this.resize(this.viewItems.length - 1, size - previousSize); - } - - private onSashStart({ sash, start }: ISashEvent): void { - const index = firstIndex(this.sashItems, item => item.sash === sash); - const sizes = this.viewItems.map(i => i.size); - - const upIndexes = range(index, -1); - const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); - const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); - - const downIndexes = range(index + 1, this.viewItems.length); - const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); - const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); - - const minDelta = -Math.min(collapseUp, expandDown); - const maxDelta = Math.min(collapseDown, expandUp); - - this.sashDragState = { start, index, sizes, minDelta, maxDelta }; - } - - private onSashChange({ sash, current }: ISashEvent): void { - const { index, start, sizes, minDelta, maxDelta } = this.sashDragState; - const delta = clamp(current - start, minDelta, maxDelta); - - this.resize(index, delta, sizes); - } - - private onViewChange(item: IViewItem): void { - const index = this.viewItems.indexOf(item); - - if (index < 0 || index >= this.viewItems.length) { - return; - } - - const size = clamp(item.size, item.view.minimumSize, item.view.maximumSize); - item.size = size; - this.relayout(); - } - - resizeView(index: number, size: number): void { - if (index < 0 || index >= this.viewItems.length) { - return; - } - - const item = this.viewItems[index]; - size = Math.round(size); - size = clamp(size, item.view.minimumSize, item.view.maximumSize); - let delta = size - item.size; - - if (delta !== 0 && index < this.viewItems.length - 1) { - const downIndexes = range(index + 1, this.viewItems.length); - const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0); - const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); - const deltaDown = clamp(delta, -expandDown, collapseDown); - - this.resize(index, deltaDown); - delta -= deltaDown; - } - - if (delta !== 0 && index > 0) { - const upIndexes = range(index - 1, -1); - const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0); - const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); - const deltaUp = clamp(-delta, -collapseUp, expandUp); - - this.resize(index - 1, deltaUp); - } - } - - private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size)): void { - if (index < 0 || index >= this.viewItems.length) { - return; - } - - if (delta !== 0) { - const upIndexes = range(index, -1); - const up = upIndexes.map(i => this.viewItems[i]); - const upSizes = upIndexes.map(i => sizes[i]); - const downIndexes = range(index + 1, this.viewItems.length); - const down = downIndexes.map(i => this.viewItems[i]); - const downSizes = downIndexes.map(i => sizes[i]); - - for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < up.length; i++) { - const item = up[i]; - const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize); - const viewDelta = size - upSizes[i]; - - deltaUp -= viewDelta; - item.size = size; - } - - for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < down.length; i++) { - const item = down[i]; - const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize); - const viewDelta = size - downSizes[i]; - - deltaDown += viewDelta; - item.size = size; - } - } - - let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - let emptyDelta = this.size - contentSize; - - for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) { - const item = this.viewItems[i]; - const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize); - const viewDelta = size - item.size; - - emptyDelta -= viewDelta; - item.size = size; - } - - this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - - this.layoutViews(); - } - - private layoutViews(): void { - this.viewItems.forEach(item => item.layout()); - this.sashItems.forEach(item => item.sash.layout()); - - // Update sashes enablement - let previous = false; - const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous); - - previous = false; - const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous); - - const reverseViews = [...this.viewItems].reverse(); - previous = false; - const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse(); - - previous = false; - const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse(); - - this.sashItems.forEach((s, i) => { - if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) { - s.sash.enable(); - } else { - s.sash.disable(); - } - }); - } - - private getSashPosition(sash: Sash): number { - let position = 0; - - for (let i = 0; i < this.sashItems.length; i++) { - position += this.viewItems[i].size; - - if (this.sashItems[i].sash === sash) { - return position; - } - } - - return 0; - } - - dispose(): void { - this.viewItems.forEach(i => i.disposable.dispose()); - this.viewItems = []; - - this.sashItems.forEach(i => i.disposable.dispose()); - this.sashItems = []; - } -} diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index 07fb5816104..85185e428b3 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -5,12 +5,12 @@ import * as assert from 'assert'; import { Emitter } from 'vs/base/common/event'; -import { SplitView, IView, Orientation } from 'vs/base/browser/ui/splitview/splitview2'; +import { SplitView, IView, Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { Sash } from 'vs/base/browser/ui/sash/sash'; class TestView implements IView { - private _onDidChange = new Emitter(); + private _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; get minimumSize(): number { return this._minimumSize; } diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index 867209cab85..31a302fa711 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -23,7 +23,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { PanelView, IPanelOptions, Panel } from 'vs/base/browser/ui/splitview/panelview'; +import { PanelView, IPanelViewOptions, IPanelOptions, Panel } from 'vs/base/browser/ui/splitview/panelview'; export interface IPanelColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -50,8 +50,8 @@ export abstract class ViewletPanel extends Panel { private _onDidFocus = new Emitter(); readonly onDidFocus: Event = this._onDidFocus.event; - private actionRunner: IActionRunner; - private toolbar: ToolBar; + protected actionRunner: IActionRunner; + protected toolbar: ToolBar; constructor( readonly title: string, @@ -114,7 +114,7 @@ export abstract class ViewletPanel extends Panel { } } -export interface IViewsViewletOptions { +export interface IViewsViewletOptions extends IPanelViewOptions { showHeaderInTitleWhenSingleView: boolean; } @@ -135,7 +135,7 @@ export class PanelViewlet extends Viewlet { constructor( id: string, - private options: Partial, + private options: IViewsViewletOptions, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService ) { @@ -146,7 +146,8 @@ export class PanelViewlet extends Viewlet { super.create(parent); const container = parent.getHTMLElement(); - this.panelview = this._register(new PanelView(container)); + this.panelview = this._register(new PanelView(container, this.options)); + this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel)); } getTitle(): string { @@ -241,9 +242,9 @@ export class PanelViewlet extends Viewlet { return; } - const [panelItem] = this.panelItems.splice(fromIndex, 1); - this.panelItems.splice(toIndex < fromIndex ? toIndex : toIndex - 1, 0, panelItem); + this.panelItems.splice(toIndex, 0, panelItem); + this.panelview.movePanel(from, to); } @@ -251,8 +252,13 @@ export class PanelViewlet extends Viewlet { this.panelview.resizePanel(panel, size); } + getPanelSize(panel: ViewletPanel): number { + return this.panelview.getPanelSize(panel); + } + private updateViewHeaders(): void { if (this.isSingleView()) { + this.panelItems[0].panel.setExpanded(true); this.panelItems[0].panel.headerVisible = false; } else { this.panelItems.forEach(i => i.panel.headerVisible = true); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 5a55e468030..351ce329db1 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -27,12 +27,11 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ViewsRegistry } from 'vs/workbench/browser/parts/views/viewsRegistry'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; -import { CollapsibleState, ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; -import { CollapsibleView, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/views'; +import { ViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { TreeItemCollapsibleState, ITreeItem, ITreeViewDataProvider, TreeViewItemHandleArg } from 'vs/workbench/common/views'; -export class TreeView extends CollapsibleView { +export class TreeView extends ViewsViewletPanel { private menus: Menus; private viewFocusContext: IContextKey; @@ -40,10 +39,8 @@ export class TreeView extends CollapsibleView { private treeInputPromise: TPromise; private dataProviderElementChangeListener: IDisposable; - private disposables: IDisposable[] = []; constructor( - initialSize: number, private options: IViewletViewOptions, @IMessageService private messageService: IMessageService, @IKeybindingService keybindingService: IKeybindingService, @@ -55,12 +52,12 @@ export class TreeView extends CollapsibleView { @IExtensionService private extensionService: IExtensionService, @ICommandService private commandService: ICommandService ) { - super(initialSize, { ...(options as IViewOptions), ariaHeaderLabel: options.name, sizing: ViewSizing.Flexible, collapsed: options.collapsed === void 0 ? true : options.collapsed }, keybindingService, contextMenuService); + super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService); this.menus = this.instantiationService.createInstance(Menus, this.id); this.viewFocusContext = this.contextKeyService.createKey(this.id, void 0); this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables); this.themeService.onThemeChange(() => this.tree.refresh() /* soft refresh */, this, this.disposables); - if (!options.collapsed) { + if (options.expanded) { this.activate(); } } @@ -79,9 +76,10 @@ export class TreeView extends CollapsibleView { this.setInput(); } - protected changeState(state: CollapsibleState): void { - super.changeState(state); - if (state === CollapsibleState.EXPANDED) { + setExpanded(expanded: boolean): void { + super.setExpanded(expanded); + + if (expanded) { this.activate(); } } @@ -106,8 +104,8 @@ export class TreeView extends CollapsibleView { keyboardSupport: false }); - this.toDispose.push(attachListStyler(tree, this.themeService)); - this.toDispose.push(this.listService.register(tree, [this.viewFocusContext])); + this.disposables.push(attachListStyler(tree, this.themeService)); + this.disposables.push(this.listService.register(tree, [this.viewFocusContext])); tree.addListener('selection', (event: any) => this.onSelection()); return tree; } diff --git a/src/vs/workbench/browser/parts/views/viewsRegistry.ts b/src/vs/workbench/browser/parts/views/viewsRegistry.ts index 9df2e29f0e6..bb7bcce90d2 100644 --- a/src/vs/workbench/browser/parts/views/viewsRegistry.ts +++ b/src/vs/workbench/browser/parts/views/viewsRegistry.ts @@ -6,7 +6,6 @@ import Event, { Emitter } from 'vs/base/common/event'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ITreeViewDataProvider } from 'vs/workbench/common/views'; -import { IViewConstructorSignature } from 'vs/workbench/browser/parts/views/views'; export class ViewLocation { @@ -39,7 +38,8 @@ export interface IViewDescriptor { readonly location: ViewLocation; - readonly ctor: IViewConstructorSignature; + // TODO do we really need this?! + readonly ctor: any; readonly when?: ContextKeyExpr; diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts similarity index 54% rename from src/vs/workbench/browser/parts/views/views.ts rename to src/vs/workbench/browser/parts/views/viewsViewlet.ts index 1c7c48aa596..0191e434b4c 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -5,24 +5,19 @@ import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IThemable, attachStyler } from 'vs/platform/theme/common/styler'; import * as errors from 'vs/base/common/errors'; import * as DOM from 'vs/base/browser/dom'; import { $, Dimension, Builder } from 'vs/base/browser/builder'; import { Scope } from 'vs/workbench/common/memento'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { prepareActions } from 'vs/workbench/browser/actions'; -import { Viewlet, ViewletRegistry, Extensions } from 'vs/workbench/browser/viewlet'; +import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITree } from 'vs/base/parts/tree/browser/tree'; +import { firstIndex } from 'vs/base/common/arrays'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; -import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { AbstractCollapsibleView, CollapsibleState, IView as IBaseView, SplitView, ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/browser/parts/views/viewsRegistry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -31,124 +26,58 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND } from 'vs/workbench/common/theme'; -import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { PanelViewlet, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IPanelOptions } from 'vs/base/browser/ui/splitview/panelview'; -export interface IViewOptions { +export interface IViewOptions extends IPanelOptions { id: string; name: string; actionRunner: IActionRunner; - collapsed: boolean; } -export interface IViewConstructorSignature { - new(initialSize: number, options: IViewOptions, ...services: { _serviceBrand: any; }[]): IViewletView; +export interface IViewConstructorSignature { + new(options: IViewOptions, ...services: { _serviceBrand: any; }[]): T; } -export interface IViewletView extends IBaseView, IThemable { - id: string; - name: string; - getHeaderElement(): HTMLElement; - create(): TPromise; - setVisible(visible: boolean): TPromise; - isVisible(): boolean; - getActions(): IAction[]; - getSecondaryActions(): IAction[]; - getActionItem(action: IAction): IActionItem; - getActionsContext(): any; - showHeader(): boolean; - hideHeader(): boolean; - focusBody(): void; - isExpanded(): boolean; - expand(): void; - collapse(): void; - getOptimalWidth(): number; - shutdown(): void; -} - -export interface ICollapsibleViewOptions extends IViewOptions { - ariaHeaderLabel?: string; - sizing: ViewSizing; - initialBodySize?: number; -} - -export abstract class CollapsibleView extends AbstractCollapsibleView implements IViewletView { +export abstract class ViewsViewletPanel extends ViewletPanel { readonly id: string; readonly name: string; protected treeContainer: HTMLElement; + + // TODO@sandeep why is tree here? isn't this coming only from TreeView protected tree: ITree; - protected toDispose: IDisposable[]; - protected toolBar: ToolBar; - protected actionRunner: IActionRunner; protected isDisposed: boolean; private _isVisible: boolean; private dragHandler: DelayedDragHandler; constructor( - initialSize: number, - options: ICollapsibleViewOptions, + options: IViewOptions, protected keybindingService: IKeybindingService, protected contextMenuService: IContextMenuService ) { - super(initialSize, { - ariaHeaderLabel: options.ariaHeaderLabel, - sizing: options.sizing, - bodySize: options.initialBodySize ? options.initialBodySize : 4 * 22, - initialState: options.collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED, - }); + super(options.name, options, keybindingService, contextMenuService); this.id = options.id; this.name = options.name; - this.actionRunner = options.actionRunner; - this.toDispose = []; + this._expanded = options.expanded; } - protected changeState(state: CollapsibleState): void { - this.updateTreeVisibility(this.tree, state === CollapsibleState.EXPANDED); - - super.changeState(state); + setExpanded(expanded: boolean): void { + this.updateTreeVisibility(this.tree, expanded); + super.setExpanded(expanded); } - get draggableLabel(): string { return this.name; } - - create(): TPromise { - return TPromise.as(null); - } - - getHeaderElement(): HTMLElement { - return this.header; - } - - renderHeader(container: HTMLElement): void { - - // Tool bar - this.toolBar = new ToolBar($('div.actions').appendTo(container).getHTMLElement(), this.contextMenuService, { - orientation: ActionsOrientation.HORIZONTAL, - actionItemProvider: (action) => this.getActionItem(action), - ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.name), - getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id) - }); - this.toolBar.actionRunner = this.actionRunner; - this.updateActions(); + protected renderHeader(container: HTMLElement): void { + super.renderHeader(container); // Expand on drag over - this.dragHandler = new DelayedDragHandler(container, () => { - if (!this.isExpanded()) { - this.expand(); - } - }); - } - - protected updateActions(): void { - this.toolBar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))(); - this.toolBar.context = this.getActionsContext(); + this.dragHandler = new DelayedDragHandler(container, () => this.setExpanded(true)); } protected renderViewTree(container: HTMLElement): HTMLElement { const treeContainer = document.createElement('div'); container.appendChild(treeContainer); - return treeContainer; } @@ -163,13 +92,14 @@ export abstract class CollapsibleView extends AbstractCollapsibleView implements setVisible(visible: boolean): TPromise { if (this._isVisible !== visible) { this._isVisible = visible; - this.updateTreeVisibility(this.tree, visible && this.state === CollapsibleState.EXPANDED); + this.updateTreeVisibility(this.tree, visible && this.isExpanded()); } return TPromise.as(null); } - focusBody(): void { + focus(): void { + super.focus(); this.focusTree(); } @@ -204,14 +134,18 @@ export abstract class CollapsibleView extends AbstractCollapsibleView implements return undefined; } - shutdown(): void { - // Subclass to implement - } - getOptimalWidth(): number { return 0; } + create(): TPromise { + return TPromise.as(null); + } + + shutdown(): void { + // Subclass to implement + } + dispose(): void { this.isDisposed = true; this.treeContainer = null; @@ -224,12 +158,6 @@ export abstract class CollapsibleView extends AbstractCollapsibleView implements this.dragHandler.dispose(); } - this.toDispose = dispose(this.toDispose); - - if (this.toolBar) { - this.toolBar.dispose(); - } - super.dispose(); } @@ -278,15 +206,14 @@ export interface IViewState { order: number; } -export class ViewsViewlet extends Viewlet { +export class ViewsViewlet extends PanelViewlet { - protected viewletContainer: HTMLElement; - protected lastFocusedView: IViewletView; - private splitView: SplitView; private viewHeaderContextMenuListeners: IDisposable[] = []; - protected dimension: Dimension; private viewletSettings: object; private readonly viewsContextKeys: Set = new Set(); + + private viewsViewletPanels: ViewsViewletPanel[] = []; + protected viewsStates: Map = new Map(); private areExtensionsReady: boolean = false; @@ -300,63 +227,28 @@ export class ViewsViewlet extends Viewlet { @IThemeService themeService: IThemeService, @IContextKeyService protected contextKeyService: IContextKeyService, @IContextMenuService protected contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService + @IExtensionService protected extensionService: IExtensionService ) { - super(id, telemetryService, themeService); + super(id, { showHeaderInTitleWhenSingleView, dnd: true }, telemetryService, themeService); this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE); + } + + async create(parent: Builder): TPromise { + super.create(parent); this._register(ViewsRegistry.onViewsRegistered(this.onViewsRegistered, this)); this._register(ViewsRegistry.onViewsDeregistered(this.onViewsDeregistered, this)); - this._register(contextKeyService.onDidChangeContext(keys => this.onContextChanged(keys))); + this._register(this.contextKeyService.onDidChangeContext(keys => this.onContextChanged(keys))); - extensionService.onReady().then(() => { + this.extensionService.onReady().then(() => { this.areExtensionsReady = true; - this.onViewsUpdated(); + this.updateViews(); + // this.onViewsUpdated(); }); - } - create(parent: Builder): TPromise { - super.create(parent); - - this.viewletContainer = DOM.append(parent.getHTMLElement(), DOM.$('')); - this.splitView = this._register(new SplitView(this.viewletContainer, { canChangeOrderByDragAndDrop: true })); - this.attachSplitViewStyler(this.splitView); - this._register(this.splitView.onFocus((view: IViewletView) => this.lastFocusedView = view)); - this._register(this.splitView.onDidOrderChange(() => { - const views = this.splitView.getViews(); - for (let order = 0; order < views.length; order++) { - this.viewsStates.get(views[order].id).order = order; - } - })); - - return this.onViewsRegistered(ViewsRegistry.getViews(this.location)) - .then(() => { - this.lastFocusedView = this.splitView.getViews()[0]; - this.focus(); - }); - } - - getTitle(): string { - let title = Registry.as(Extensions.Viewlets).getViewlet(this.getId()).name; - if (this.showHeaderInTitleArea() && this.splitView.getViews()[0]) { - title += ': ' + this.splitView.getViews()[0].name; - } - return title; - } - - getActions(): IAction[] { - if (this.showHeaderInTitleArea() && this.splitView.getViews()[0]) { - return this.splitView.getViews()[0].getActions(); - } - return []; - } - - getSecondaryActions(): IAction[] { - if (this.showHeaderInTitleArea() && this.splitView.getViews()[0]) { - return this.splitView.getViews()[0].getSecondaryActions(); - } - return []; + await this.onViewsRegistered(ViewsRegistry.getViews(this.location)); + this.focus(); } getContextMenuActions(): IAction[] { @@ -373,47 +265,43 @@ export class ViewsViewlet extends Viewlet { setVisible(visible: boolean): TPromise { return super.setVisible(visible) - .then(() => TPromise.join(this.splitView.getViews().filter(view => view.isVisible() !== visible) + .then(() => TPromise.join(this.viewsViewletPanels.filter(view => view.isVisible() !== visible) .map((view) => view.setVisible(visible)))) .then(() => void 0); } - focus(): void { - super.focus(); - - if (this.lastFocusedView) { - this.lastFocusedView.focus(); - } else if (this.views.length > 0) { - this.views[0].focus(); - } - } + private didLayout = false; layout(dimension: Dimension): void { - this.dimension = dimension; - this.layoutViews(); + super.layout(dimension); + + if (!this.didLayout) { + this.didLayout = true; + + for (const panel of this.viewsViewletPanels) { + const viewState = this.viewsStates.get(panel.id); + const size = viewState ? viewState.size : 200; + this.resizePanel(panel, size); + } + } + + for (const view of this.viewsViewletPanels) { + let viewState = this.updateViewStateSize(view); + this.viewsStates.set(view.id, viewState); + } } getOptimalWidth(): number { const additionalMargin = 16; - const optimalWidth = Math.max(...this.splitView.getViews().map(view => view.getOptimalWidth() || 0)); + const optimalWidth = Math.max(...this.viewsViewletPanels.map(view => view.getOptimalWidth() || 0)); return optimalWidth + additionalMargin; } shutdown(): void { - this.splitView.getViews().forEach((view) => view.shutdown()); + this.viewsViewletPanels.forEach((view) => view.shutdown()); super.shutdown(); } - private layoutViews(): void { - if (this.splitView) { - this.splitView.layout(this.dimension.height); - for (const view of this.splitView.getViews()) { - let viewState = this.updateViewStateSize(view); - this.viewsStates.set(view.id, viewState); - } - } - } - toggleViewVisibility(id: string, visible?: boolean): void { const view = this.getView(id); let viewState = this.viewsStates.get(id); @@ -433,7 +321,7 @@ export class ViewsViewlet extends Viewlet { this.updateViews(); } - private onViewsRegistered(views: IViewDescriptor[]): TPromise { + private onViewsRegistered(views: IViewDescriptor[]): TPromise { this.viewsContextKeys.clear(); for (const viewDescriptor of this.getViewDescriptorsFromRegistry()) { if (viewDescriptor.when) { @@ -446,7 +334,7 @@ export class ViewsViewlet extends Viewlet { return this.updateViews(); } - private onViewsDeregistered(views: IViewDescriptor[]): TPromise { + private onViewsDeregistered(views: IViewDescriptor[]): TPromise { return this.updateViews(views); } @@ -468,94 +356,103 @@ export class ViewsViewlet extends Viewlet { } } - protected updateViews(unregisteredViews: IViewDescriptor[] = []): TPromise { - if (this.splitView) { + protected updateViews(unregisteredViews: IViewDescriptor[] = []): TPromise { + const registeredViews = this.getViewDescriptorsFromRegistry(); + const [visible, toAdd, toRemove] = registeredViews.reduce<[IViewDescriptor[], IViewDescriptor[], IViewDescriptor[]]>((result, viewDescriptor) => { + const isCurrentlyVisible = this.isCurrentlyVisible(viewDescriptor); + const canBeVisible = this.canBeVisible(viewDescriptor); - const registeredViews = this.getViewDescriptorsFromRegistry(); - const [visible, toAdd, toRemove] = registeredViews.reduce<[IViewDescriptor[], IViewDescriptor[], IViewDescriptor[]]>((result, viewDescriptor) => { - const isCurrentlyVisible = this.isCurrentlyVisible(viewDescriptor); - const canBeVisible = this.canBeVisible(viewDescriptor); - - if (canBeVisible) { - result[0].push(viewDescriptor); - } - - if (!isCurrentlyVisible && canBeVisible) { - result[1].push(viewDescriptor); - } - - if (isCurrentlyVisible && !canBeVisible) { - result[2].push(viewDescriptor); - } - - return result; - - }, [[], [], unregisteredViews]); - - const toCreate: IViewletView[] = []; - - if (toAdd.length || toRemove.length) { - for (const view of this.splitView.getViews()) { - let viewState = this.viewsStates.get(view.id); - if (!viewState || typeof viewState.size === 'undefined' || view.size !== viewState.size || !view.isExpanded() !== viewState.collapsed) { - viewState = this.updateViewStateSize(view); - this.viewsStates.set(view.id, viewState); - } - } - if (toRemove.length) { - for (const viewDescriptor of toRemove) { - let view = this.getView(viewDescriptor.id); - this.splitView.removeView(view); - if (this.lastFocusedView === view) { - this.lastFocusedView = null; - } - } - } - - for (const viewDescriptor of toAdd) { - let viewState = this.viewsStates.get(viewDescriptor.id); - let index = visible.indexOf(viewDescriptor); - const view = this.createView(viewDescriptor, - viewState ? viewState.size : this.getDefaultViewSize(), - { - id: viewDescriptor.id, - name: viewDescriptor.name, - actionRunner: this.getActionRunner(), - collapsed: viewState ? viewState.collapsed : void 0, - viewletSettings: this.viewletSettings - }); - toCreate.push(view); - - this.attachViewStyler(view); - this.splitView.addView(view, index); - } - - return TPromise.join(toCreate.map(view => view.create())) - .then(() => this.onViewsUpdated()) - .then(() => toCreate); + if (canBeVisible) { + result[0].push(viewDescriptor); } + + if (!isCurrentlyVisible && canBeVisible) { + result[1].push(viewDescriptor); + } + + if (isCurrentlyVisible && !canBeVisible) { + result[2].push(viewDescriptor); + } + + return result; + + }, [[], [], unregisteredViews]); + + const toCreate: ViewsViewletPanel[] = []; + + if (toAdd.length || toRemove.length) { + const panels = [...this.viewsViewletPanels]; + + for (const view of panels) { + let viewState = this.viewsStates.get(view.id); + if (!viewState || typeof viewState.size === 'undefined' || !view.isExpanded() !== viewState.collapsed) { + viewState = this.updateViewStateSize(view); + this.viewsStates.set(view.id, viewState); + } + } + + if (toRemove.length) { + for (const viewDescriptor of toRemove) { + let view = this.getView(viewDescriptor.id); + const viewState = this.updateViewStateSize(view); + this.viewsStates.set(view.id, viewState); + this.removePanel(view); + this.viewsViewletPanels.splice(this.viewsViewletPanels.indexOf(view), 1); + } + } + + for (const viewDescriptor of toAdd) { + let viewState = this.viewsStates.get(viewDescriptor.id); + let index = visible.indexOf(viewDescriptor); + const view = this.createView(viewDescriptor, + { + id: viewDescriptor.id, + name: viewDescriptor.name, + actionRunner: this.getActionRunner(), + expanded: !(viewState ? viewState.collapsed : void 0), + viewletSettings: this.viewletSettings + }); + toCreate.push(view); + + const size = viewState ? viewState.size : (viewDescriptor.size || 200); + this.addPanel(view, size, index); + this.viewsViewletPanels.splice(index, 0, view); + } + + return TPromise.join(toCreate.map(view => view.create())) + .then(() => this.onViewsUpdated()) + .then(() => toCreate); } + return TPromise.as([]); } + movePanel(from: ViewletPanel, to: ViewletPanel): void { + const fromIndex = firstIndex(this.viewsViewletPanels, panel => panel === from); + const toIndex = firstIndex(this.viewsViewletPanels, panel => panel === to); + + if (fromIndex < 0 || fromIndex >= this.viewsViewletPanels.length) { + return; + } + + if (toIndex < 0 || toIndex >= this.viewsViewletPanels.length) { + return; + } + + super.movePanel(from, to); + + const [panel] = this.viewsViewletPanels.splice(fromIndex, 1); + this.viewsViewletPanels.splice(toIndex, 0, panel); + + for (let order = 0; order < this.viewsViewletPanels.length; order++) { + this.viewsStates.get(this.viewsViewletPanels[order].id).order = order; + } + } + protected getDefaultViewSize(): number | undefined { return undefined; } - private attachViewStyler(widget: IThemable, options?: { noContrastBorder?: boolean }): IDisposable { - return attachStyler(this.themeService, { - headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND, - headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND, - headerHighContrastBorder: (options && options.noContrastBorder) ? null : contrastBorder - }, widget); - } - - private attachSplitViewStyler(widget: IThemable): IDisposable { - return attachStyler(this.themeService, { - dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND - }, widget); - } - private isCurrentlyVisible(viewDescriptor: IViewDescriptor): boolean { return !!this.getView(viewDescriptor.id); } @@ -569,31 +466,13 @@ export class ViewsViewlet extends Viewlet { } private onViewsUpdated(): TPromise { - if (!this.splitView) { - return TPromise.as(null); - } - - if (this.showHeaderInTitleArea()) { - if (this.splitView.getViews()[0]) { - this.splitView.getViews()[0].hideHeader(); - if (!this.splitView.getViews()[0].isExpanded()) { - this.splitView.getViews()[0].expand(); - } - } - } else { - for (const view of this.splitView.getViews()) { - view.showHeader(); - } - } - - // Update title area since the title actions have changed. - this.updateTitleArea(); - this.viewHeaderContextMenuListeners = dispose(this.viewHeaderContextMenuListeners); + for (const viewDescriptor of this.getViewDescriptorsFromRegistry()) { const view = this.getView(viewDescriptor.id); + if (view) { - this.viewHeaderContextMenuListeners.push(DOM.addDisposableListener(view.getHeaderElement(), DOM.EventType.CONTEXT_MENU, e => { + this.viewHeaderContextMenuListeners.push(DOM.addDisposableListener(view.draggableElement, DOM.EventType.CONTEXT_MENU, e => { e.stopPropagation(); e.preventDefault(); if (viewDescriptor.canToggleVisibility) { @@ -603,14 +482,10 @@ export class ViewsViewlet extends Viewlet { } } - if (this.dimension) { - this.layoutViews(); - } - return this.setVisible(this.isVisible()); } - private onContextMenu(event: StandardMouseEvent, view: IViewletView): void { + private onContextMenu(event: StandardMouseEvent, view: ViewsViewletPanel): void { event.stopPropagation(); event.preventDefault(); @@ -630,7 +505,7 @@ export class ViewsViewlet extends Viewlet { if (!this.showHeaderInTitleWhenSingleView) { return false; } - if (this.splitView.getViews().length > 1) { + if (this.viewsViewletPanels.length > 1) { return false; } if (ViewLocation.getContributedViewLocation(this.location.id) && !this.areExtensionsReady) { @@ -668,32 +543,30 @@ export class ViewsViewlet extends Viewlet { }); } - protected createView(viewDescriptor: IViewDescriptor, initialSize: number, options: IViewletViewOptions): IViewletView { - return this.instantiationService.createInstance(viewDescriptor.ctor, initialSize, options); + protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewsViewletPanel { + return this.instantiationService.createInstance(viewDescriptor.ctor, options); } - protected get views(): IViewletView[] { - return this.splitView ? this.splitView.getViews() : []; + protected get views(): ViewsViewletPanel[] { + return this.viewsViewletPanels; } - protected getView(id: string): IViewletView { - return this.splitView.getViews().filter(view => view.id === id)[0]; + protected getView(id: string): ViewsViewletPanel { + return this.viewsViewletPanels.filter(view => view.id === id)[0]; } - private updateViewStateSize(view: IViewletView): IViewState { + private updateViewStateSize(view: ViewsViewletPanel): IViewState { const currentState = this.viewsStates.get(view.id); const newViewState = this.createViewState(view); return currentState ? { ...currentState, collapsed: newViewState.collapsed, size: newViewState.size } : newViewState; } - protected createViewState(view: IViewletView): IViewState { - const collapsed = !view.isExpanded(); - const size = collapsed && view instanceof CollapsibleView ? view.previousSize : view.size; + protected createViewState(view: ViewsViewletPanel): IViewState { return { - collapsed, - size: size && size > 0 ? size : void 0, + collapsed: !view.isExpanded(), + size: this.getPanelSize(view), isHidden: false, - order: this.splitView.getViews().indexOf(view) + order: this.viewsViewletPanels.indexOf(view) }; } } @@ -728,9 +601,9 @@ export class PersistentViewsViewlet extends ViewsViewlet { const registeredViewDescriptors = this.getViewDescriptorsFromRegistry(); this.viewsStates.forEach((viewState, id) => { const view = this.getView(id); + if (view) { - viewState = this.createViewState(view); - viewsStates[id] = { size: viewState.size, collapsed: viewState.collapsed, isHidden: viewState.isHidden, order: viewState.order }; + viewsStates[id] = this.createViewState(view); } else { const viewDescriptor = registeredViewDescriptors.filter(v => v.id === id)[0]; if (viewDescriptor) { diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index 224227ba714..2c23cc043d6 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -9,7 +9,7 @@ import * as DOM from 'vs/base/browser/dom'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAction } from 'vs/base/common/actions'; import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { PersistentViewsViewlet } from 'vs/workbench/browser/parts/views/views'; +import { PersistentViewsViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDebugService, VIEWLET_ID, State } from 'vs/workbench/parts/debug/common/debug'; import { StartAction, ToggleReplAction, ConfigureAction } from 'vs/workbench/parts/debug/browser/debugActions'; import { StartDebugActionItem } from 'vs/workbench/parts/debug/browser/debugActionItems'; @@ -49,8 +49,11 @@ export class DebugViewlet extends PersistentViewsViewlet { this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateTitleArea())); } - public create(parent: Builder): TPromise { - return super.create(parent).then(() => DOM.addClass(this.viewletContainer, 'debug-viewlet')); + async create(parent: Builder): TPromise { + await super.create(parent); + + const el = parent.getHTMLElement(); + DOM.addClass(el, 'debug-viewlet'); } public focus(): void { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViews.ts b/src/vs/workbench/parts/debug/electron-browser/debugViews.ts index a87b57536b8..fca2abcd572 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViews.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViews.ts @@ -15,9 +15,8 @@ import { IAction } from 'vs/base/common/actions'; import { prepareActions } from 'vs/workbench/browser/actions'; import { IHighlightEvent, ITree } from 'vs/base/parts/tree/browser/tree'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; -import { CollapsibleState, ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; -import { CollapsibleView, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/views'; +import { ViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDebugService, State, IBreakpoint, IExpression, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED } from 'vs/workbench/parts/debug/common/debug'; import { Expression, Variable, ExceptionBreakpoint, FunctionBreakpoint, Thread, StackFrame, Breakpoint, ThreadAndProcessIds } from 'vs/workbench/parts/debug/common/debugModel'; import * as viewer from 'vs/workbench/parts/debug/electron-browser/debugViewer'; @@ -42,7 +41,7 @@ function renderViewTree(container: HTMLElement): HTMLElement { const $ = builder.$; const twistiePixels = 20; -export class VariablesView extends CollapsibleView { +export class VariablesView extends ViewsViewletPanel { private static MEMENTO = 'variablesview.memento'; private onFocusStackFrameScheduler: RunOnceScheduler; @@ -50,7 +49,6 @@ export class VariablesView extends CollapsibleView { private settings: any; constructor( - initialSize: number, private options: IViewletViewOptions, @IContextMenuService contextMenuService: IContextMenuService, @ITelemetryService private telemetryService: ITelemetryService, @@ -61,7 +59,7 @@ export class VariablesView extends CollapsibleView { @IListService private listService: IListService, @IThemeService private themeService: IThemeService ) { - super(initialSize, { ...(options as IViewOptions), sizing: ViewSizing.Flexible, ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService); + super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService); this.settings = options.viewletSettings; this.variablesFocusedContext = CONTEXT_VARIABLES_FOCUSED.bindTo(contextKeyService); @@ -84,13 +82,6 @@ export class VariablesView extends CollapsibleView { }, 400); } - public renderHeader(container: HTMLElement): void { - const titleDiv = $('div.title').appendTo(container); - $('span').text(this.options.name).appendTo(titleDiv); - - super.renderHeader(container); - } - public renderBody(container: HTMLElement): void { dom.addClass(container, 'debug-variables'); this.treeContainer = renderViewTree(container); @@ -106,17 +97,17 @@ export class VariablesView extends CollapsibleView { keyboardSupport: false }); - this.toDispose.push(attachListStyler(this.tree, this.themeService)); - this.toDispose.push(this.listService.register(this.tree, [this.variablesFocusedContext])); + this.disposables.push(attachListStyler(this.tree, this.themeService)); + this.disposables.push(this.listService.register(this.tree, [this.variablesFocusedContext])); const viewModel = this.debugService.getViewModel(); this.tree.setInput(viewModel); const collapseAction = this.instantiationService.createInstance(CollapseAction, this.tree, false, 'explorer-action collapse-explorer'); - this.toolBar.setActions(prepareActions([collapseAction]))(); + this.toolbar.setActions(prepareActions([collapseAction]))(); - this.toDispose.push(viewModel.onDidFocusStackFrame(sf => { + this.disposables.push(viewModel.onDidFocusStackFrame(sf => { // Refresh the tree immediately if it is not visible. // Otherwise postpone the refresh until user stops stepping. if (!this.tree.getContentHeight() || sf.explicit) { @@ -125,11 +116,11 @@ export class VariablesView extends CollapsibleView { this.onFocusStackFrameScheduler.schedule(); } })); - this.toDispose.push(this.debugService.onDidChangeState(state => { + this.disposables.push(this.debugService.onDidChangeState(state => { collapseAction.enabled = state === State.Running || state === State.Stopped; })); - this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(expression => { + this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => { if (!expression || !(expression instanceof Variable)) { return; } @@ -146,12 +137,12 @@ export class VariablesView extends CollapsibleView { } public shutdown(): void { - this.settings[VariablesView.MEMENTO] = (this.state === CollapsibleState.COLLAPSED); + this.settings[VariablesView.MEMENTO] = !this.isExpanded(); super.shutdown(); } } -export class WatchExpressionsView extends CollapsibleView { +export class WatchExpressionsView extends ViewsViewletPanel { private static MEMENTO = 'watchexpressionsview.memento'; private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; @@ -160,7 +151,6 @@ export class WatchExpressionsView extends CollapsibleView { private settings: any; constructor( - size: number, private options: IViewletViewOptions, @IContextMenuService contextMenuService: IContextMenuService, @IDebugService private debugService: IDebugService, @@ -170,13 +160,13 @@ export class WatchExpressionsView extends CollapsibleView { @IListService private listService: IListService, @IThemeService private themeService: IThemeService ) { - super(size, { ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section"), sizing: ViewSizing.Flexible }, keybindingService, contextMenuService); + super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService); this.settings = options.viewletSettings; - this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => { + this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => { // only expand when a new watch expression is added. if (we instanceof Expression) { - this.expand(); + this.setExpanded(true); } })); this.watchExpressionsFocusedContext = CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(contextKeyService); @@ -188,13 +178,6 @@ export class WatchExpressionsView extends CollapsibleView { }, 50); } - public renderHeader(container: HTMLElement): void { - const titleDiv = $('div.title').appendTo(container); - $('span').text(this.options.name).appendTo(titleDiv); - - super.renderHeader(container); - } - public renderBody(container: HTMLElement): void { dom.addClass(container, 'debug-watch'); this.treeContainer = renderViewTree(container); @@ -212,24 +195,24 @@ export class WatchExpressionsView extends CollapsibleView { keyboardSupport: false }); - this.toDispose.push(attachListStyler(this.tree, this.themeService)); - this.toDispose.push(this.listService.register(this.tree, [this.watchExpressionsFocusedContext])); + this.disposables.push(attachListStyler(this.tree, this.themeService)); + this.disposables.push(this.listService.register(this.tree, [this.watchExpressionsFocusedContext])); this.tree.setInput(this.debugService.getModel()); const addWatchExpressionAction = this.instantiationService.createInstance(AddWatchExpressionAction, AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL); const collapseAction = this.instantiationService.createInstance(CollapseAction, this.tree, true, 'explorer-action collapse-explorer'); const removeAllWatchExpressionsAction = this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL); - this.toolBar.setActions(prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))(); + this.toolbar.setActions(prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))(); - this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => { + this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => { if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) { this.onWatchExpressionsUpdatedScheduler.schedule(); } this.toReveal = we; })); - this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(expression => { + this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => { if (!expression || !(expression instanceof Expression)) { return; } @@ -246,12 +229,12 @@ export class WatchExpressionsView extends CollapsibleView { } public shutdown(): void { - this.settings[WatchExpressionsView.MEMENTO] = (this.state === CollapsibleState.COLLAPSED); + this.settings[WatchExpressionsView.MEMENTO] = !this.isExpanded(); super.shutdown(); } } -export class CallStackView extends CollapsibleView { +export class CallStackView extends ViewsViewletPanel { private static MEMENTO = 'callstackview.memento'; private pauseMessage: builder.Builder; @@ -260,7 +243,6 @@ export class CallStackView extends CollapsibleView { private settings: any; constructor( - size: number, private options: IViewletViewOptions, @IContextMenuService contextMenuService: IContextMenuService, @ITelemetryService private telemetryService: ITelemetryService, @@ -270,7 +252,7 @@ export class CallStackView extends CollapsibleView { @IListService private listService: IListService, @IThemeService private themeService: IThemeService ) { - super(size, { ...(options as IViewOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section"), sizing: ViewSizing.Flexible }, keybindingService, contextMenuService); + super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService); this.settings = options.viewletSettings; // Create scheduler to prevent unnecessary flashing of tree when reacting to changes @@ -301,14 +283,12 @@ export class CallStackView extends CollapsibleView { }, 50); } - public renderHeader(container: HTMLElement): void { - const title = $('div.debug-call-stack-title').appendTo(container); - $('span.title').text(this.options.name).appendTo(title); + protected renderHeaderTitle(container: HTMLElement): void { + const title = $('.title.debug-call-stack-title').appendTo(container); + $('span').text(this.options.name).appendTo(title); this.pauseMessage = $('span.pause-message').appendTo(title); this.pauseMessage.hide(); this.pauseMessageLabel = $('span.label').appendTo(this.pauseMessage); - - super.renderHeader(container); } public renderBody(container: HTMLElement): void { @@ -328,10 +308,10 @@ export class CallStackView extends CollapsibleView { keyboardSupport: false }); - this.toDispose.push(attachListStyler(this.tree, this.themeService)); - this.toDispose.push(this.listService.register(this.tree)); + this.disposables.push(attachListStyler(this.tree, this.themeService)); + this.disposables.push(this.listService.register(this.tree)); - this.toDispose.push(this.tree.addListener('selection', event => { + this.disposables.push(this.tree.addListener('selection', event => { if (event && event.payload && event.payload.origin === 'keyboard') { const element = this.tree.getFocus(); if (element instanceof ThreadAndProcessIds) { @@ -342,12 +322,12 @@ export class CallStackView extends CollapsibleView { } })); - this.toDispose.push(this.debugService.getModel().onDidChangeCallStack(() => { + this.disposables.push(this.debugService.getModel().onDidChangeCallStack(() => { if (!this.onCallStackChangeScheduler.isScheduled()) { this.onCallStackChangeScheduler.schedule(); } })); - this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => + this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.updateTreeSelection().done(undefined, errors.onUnexpectedError))); // Schedule the update of the call stack tree if the viewlet is opened after a session started #14684 @@ -386,12 +366,12 @@ export class CallStackView extends CollapsibleView { } public shutdown(): void { - this.settings[CallStackView.MEMENTO] = (this.state === CollapsibleState.COLLAPSED); + this.settings[CallStackView.MEMENTO] = !this.isExpanded(); super.shutdown(); } } -export class BreakpointsView extends CollapsibleView { +export class BreakpointsView extends ViewsViewletPanel { private static MAX_VISIBLE_FILES = 9; private static MEMENTO = 'breakopintsview.memento'; @@ -399,7 +379,6 @@ export class BreakpointsView extends CollapsibleView { private settings: any; constructor( - size: number, private options: IViewletViewOptions, @IContextMenuService contextMenuService: IContextMenuService, @IDebugService private debugService: IDebugService, @@ -409,23 +388,15 @@ export class BreakpointsView extends CollapsibleView { @IListService private listService: IListService, @IThemeService private themeService: IThemeService ) { - super(size, { + super({ ...(options as IViewOptions), - ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section"), - sizing: ViewSizing.Fixed, - initialBodySize: BreakpointsView.getExpandedBodySize(debugService.getModel().getBreakpoints().length + debugService.getModel().getFunctionBreakpoints().length + debugService.getModel().getExceptionBreakpoints().length) + ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService); + this.minimumBodySize = this.maximumBodySize = 0; this.settings = options.viewletSettings; this.breakpointsFocusedContext = CONTEXT_BREAKPOINTS_FOCUSED.bindTo(contextKeyService); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); - } - - public renderHeader(container: HTMLElement): void { - const titleDiv = $('div.title').appendTo(container); - $('span').text(this.options.name).appendTo(titleDiv); - - super.renderHeader(container); + this.disposables.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); } public renderBody(container: HTMLElement): void { @@ -472,10 +443,10 @@ export class BreakpointsView extends CollapsibleView { keyboardSupport: false }); - this.toDispose.push(attachListStyler(this.tree, this.themeService)); - this.toDispose.push(this.listService.register(this.tree, [this.breakpointsFocusedContext])); + this.disposables.push(attachListStyler(this.tree, this.themeService)); + this.disposables.push(this.listService.register(this.tree, [this.breakpointsFocusedContext])); - this.toDispose.push(this.tree.addListener('selection', event => { + this.disposables.push(this.tree.addListener('selection', event => { if (event && event.payload && event.payload.origin === 'keyboard') { const element = this.tree.getFocus(); if (element instanceof Breakpoint) { @@ -488,7 +459,7 @@ export class BreakpointsView extends CollapsibleView { this.tree.setInput(debugModel); - this.toDispose.push(this.debugService.getViewModel().onDidSelectFunctionBreakpoint(fbp => { + this.disposables.push(this.debugService.getViewModel().onDidSelectFunctionBreakpoint(fbp => { if (!fbp || !(fbp instanceof FunctionBreakpoint)) { return; } @@ -514,8 +485,9 @@ export class BreakpointsView extends CollapsibleView { private onBreakpointsChange(): void { const model = this.debugService.getModel(); - this.setBodySize(BreakpointsView.getExpandedBodySize( - model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length)); + + const bodySize = BreakpointsView.getExpandedBodySize(model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length); + this.minimumBodySize = this.maximumBodySize = bodySize; if (this.tree) { this.tree.refresh(); @@ -527,7 +499,7 @@ export class BreakpointsView extends CollapsibleView { } public shutdown(): void { - this.settings[BreakpointsView.MEMENTO] = (this.state === CollapsibleState.COLLAPSED); + this.settings[BreakpointsView.MEMENTO] = !this.isExpanded(); super.shutdown(); } } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 12559c4f210..ccbee9dd3a1 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -46,7 +46,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { inputForeground, inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/browser/parts/views/viewsRegistry'; -import { PersistentViewsViewlet, IViewletView } from 'vs/workbench/browser/parts/views/views'; +import { PersistentViewsViewlet, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -136,7 +136,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens name: localize('marketPlace', "Marketplace"), location: ViewLocation.Extensions, ctor: ExtensionsListView, - when: ContextKeyExpr.and(ContextKeyExpr.has('extensionsViewletVisible'), ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions')), + when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions')), size: 100 }; } @@ -147,7 +147,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens name: localize('installedExtensions', "Installed"), location: ViewLocation.Extensions, ctor: InstalledExtensionsView, - when: ContextKeyExpr.and(ContextKeyExpr.has('extensionsViewletVisible'), ContextKeyExpr.not('searchExtensions')), + when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions')), size: 50 }; } @@ -158,7 +158,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens name: localize('searchInstalledExtensions', "Installed"), location: ViewLocation.Extensions, ctor: InstalledExtensionsView, - when: ContextKeyExpr.and(ContextKeyExpr.has('extensionsViewletVisible'), ContextKeyExpr.has('searchInstalledExtensions')), + when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')), size: 50 }; } @@ -169,13 +169,13 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens name: localize('recommendedExtensions', "Recommended"), location: ViewLocation.Extensions, ctor: RecommendedExtensionsView, - when: ContextKeyExpr.and(ContextKeyExpr.has('extensionsViewletVisible'), ContextKeyExpr.not('searchExtensions')), + when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions')), size: 50, canToggleVisibility: true }; } - create(parent: Builder): TPromise { + async create(parent: Builder): TPromise { parent.addClass('extensions-viewlet'); this.root = parent.getHTMLElement(); @@ -204,14 +204,14 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens this.onSearchChange = mapEvent(onSearchInput, e => e.target.value); - return this.extensionManagementService.getInstalled(LocalExtensionType.User) - .then(installed => { - if (installed.length === 0) { - this.searchBox.value = '@sort:installs'; - this.searchExtensionsContextKey.set(true); - } - return super.create(new Builder(this.extensionsBox)); - }); + await super.create(new Builder(this.extensionsBox)); + + const installed = await this.extensionManagementService.getInstalled(LocalExtensionType.User); + + if (installed.length === 0) { + this.searchBox.value = '@sort:installs'; + this.searchExtensionsContextKey.set(true); + } } public updateStyles(): void { @@ -310,7 +310,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens await this.updateViews([], !!value); } - protected async updateViews(unregisteredViews: IViewDescriptor[] = [], showAll = false): TPromise { + protected async updateViews(unregisteredViews: IViewDescriptor[] = [], showAll = false): TPromise { const created = await super.updateViews(); const toShow = showAll ? this.views : created; if (toShow.length) { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index 37f9a6e4e16..d8fd1c8ec8f 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -7,14 +7,13 @@ import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { dispose } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { distinct } from 'vs/base/common/arrays'; import { chain } from 'vs/base/common/event'; import { isPromiseCanceledError, create as createError } from 'vs/base/common/errors'; import Severity from 'vs/base/common/severity'; import { PagedModel, IPagedModel, mergePagers, IPager } from 'vs/base/common/paging'; -import { ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; import { IMessageService, CloseAction } from 'vs/platform/message/common/message'; import { SortBy, SortOrder, IQueryOptions, LocalExtensionType, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -32,7 +31,7 @@ import { IListService } from 'vs/platform/list/browser/listService'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachListStyler, attachBadgeStyler } from 'vs/platform/theme/common/styler'; -import { CollapsibleView, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/views'; +import { ViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { OpenGlobalSettingsAction } from 'vs/workbench/parts/preferences/browser/preferencesActions'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; @@ -41,17 +40,15 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -export class ExtensionsListView extends CollapsibleView { +export class ExtensionsListView extends ViewsViewletPanel { private messageBox: HTMLElement; private extensionsList: HTMLElement; private badge: CountBadge; private list: PagedList; - private disposables: IDisposable[] = []; constructor( - initialSize: number, private options: IViewletViewOptions, @IMessageService private messageService: IMessageService, @IKeybindingService keybindingService: IKeybindingService, @@ -70,7 +67,7 @@ export class ExtensionsListView extends CollapsibleView { @ITelemetryService private telemetryService: ITelemetryService, @IProgressService private progressService: IProgressService ) { - super(initialSize, { ...(options as IViewOptions), ariaHeaderLabel: options.name, sizing: ViewSizing.Flexible, collapsed: !!options.collapsed, initialBodySize: 1 * 62 }, keybindingService, contextMenuService); + super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService); } renderHeader(container: HTMLElement): void { diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css index 35a4e5bcb39..ee20c1d51a3 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css @@ -33,11 +33,11 @@ visibility: hidden; } -.extensions-viewlet > .extensions .header { +.extensions-viewlet > .extensions .panel-header { padding-right: 12px; } -.extensions-viewlet > .extensions .header > .title { +.extensions-viewlet > .extensions .panel-header > .title { flex: 1; } @@ -94,7 +94,6 @@ overflow: hidden; flex: 1; min-width: 0; - } .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .name { diff --git a/src/vs/workbench/parts/files/browser/explorerViewlet.ts b/src/vs/workbench/parts/files/browser/explorerViewlet.ts index 0e582a83c5e..8136242490e 100644 --- a/src/vs/workbench/parts/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/browser/explorerViewlet.ts @@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as DOM from 'vs/base/browser/dom'; import { Builder } from 'vs/base/browser/builder'; import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, OpenEditorsVisibleCondition } from 'vs/workbench/parts/files/common/files'; -import { PersistentViewsViewlet, IViewletView, IViewletViewOptions } from 'vs/workbench/browser/parts/views/views'; +import { PersistentViewsViewlet, ViewsViewletPanel, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ActionRunner, FileViewletState } from 'vs/workbench/parts/files/browser/views/explorerViewer'; @@ -70,8 +70,11 @@ export class ExplorerViewlet extends PersistentViewsViewlet { this._register(this.contextService.onDidChangeWorkbenchState(() => this.registerViews())); } - public create(parent: Builder): TPromise { - return super.create(parent).then(() => DOM.addClass(this.viewletContainer, 'explorer-viewlet')); + async create(parent: Builder): TPromise { + await super.create(parent); + + const el = parent.getHTMLElement(); + DOM.addClass(el, 'explorer-viewlet'); } private registerViews(): void { @@ -152,7 +155,7 @@ export class ExplorerViewlet extends PersistentViewsViewlet { this.openEditorsVisibleContextKey.set(this.contextService.getWorkbenchState() === WorkbenchState.EMPTY || (this.configurationService.getConfiguration()).explorer.openEditors.visible !== 0); } - protected createView(viewDescriptor: IViewDescriptor, initialSize: number, options: IViewletViewOptions): IViewletView { + protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewsViewletPanel { if (viewDescriptor.id === ExplorerView.ID) { // Create a delegating editor service for the explorer to be able to delay the refresh in the opened // editors view above. This is a workaround for being able to double click on a file to make it pinned @@ -192,9 +195,9 @@ export class ExplorerViewlet extends PersistentViewsViewlet { }); const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService])); - return explorerInstantiator.createInstance(ExplorerView, initialSize, { ...options, viewletState: this.viewletState }); + return explorerInstantiator.createInstance(ExplorerView, { ...options, viewletState: this.viewletState }); } - return super.createView(viewDescriptor, initialSize, options); + return super.createView(viewDescriptor, options); } public getExplorerView(): ExplorerView { @@ -218,28 +221,28 @@ export class ExplorerViewlet extends PersistentViewsViewlet { const hasOpenedEditors = !!this.editorGroupService.getStacksModel().activeGroup; let openEditorsView = this.getOpenEditorsView(); - if (this.lastFocusedView && this.lastFocusedView.isExpanded() && this.hasSelectionOrFocus(this.lastFocusedView)) { - if (this.lastFocusedView !== openEditorsView || hasOpenedEditors) { - this.lastFocusedView.focusBody(); + if (this.lastFocusedPanel && this.lastFocusedPanel.isExpanded() && this.hasSelectionOrFocus(this.lastFocusedPanel as ViewsViewletPanel)) { + if (this.lastFocusedPanel !== openEditorsView || hasOpenedEditors) { + this.lastFocusedPanel.focus(); return; } } if (this.hasSelectionOrFocus(openEditorsView) && hasOpenedEditors) { - return openEditorsView.focusBody(); + return openEditorsView.focus(); } let explorerView = this.getExplorerView(); if (this.hasSelectionOrFocus(explorerView)) { - return explorerView.focusBody(); + return explorerView.focus(); } if (openEditorsView && openEditorsView.isExpanded() && hasOpenedEditors) { - return openEditorsView.focusBody(); // we have entries in the opened editors view to focus on + return openEditorsView.focus(); // we have entries in the opened editors view to focus on } if (explorerView && explorerView.isExpanded()) { - return explorerView.focusBody(); + return explorerView.focus(); } let emptyView = this.getEmptyView(); @@ -250,7 +253,7 @@ export class ExplorerViewlet extends PersistentViewsViewlet { super.focus(); } - private hasSelectionOrFocus(view: IViewletView): boolean { + private hasSelectionOrFocus(view: ViewsViewletPanel): boolean { if (!view) { return false; } diff --git a/src/vs/workbench/parts/files/browser/fileActions.ts b/src/vs/workbench/parts/files/browser/fileActions.ts index 30b6d67ec62..fa5d98003d8 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -497,7 +497,7 @@ export abstract class BaseGlobalNewAction extends Action { } if (!explorerView.isExpanded()) { - explorerView.expand(); + explorerView.setExpanded(true); } const action = this.toDispose = this.instantiationService.createInstance(this.getAction(), explorerView.getViewer(), null); @@ -1716,7 +1716,7 @@ export class FocusOpenEditorsView extends Action { return this.viewletService.openViewlet(VIEWLET_ID, true).then((viewlet: ExplorerViewlet) => { const openEditorsView = viewlet.getOpenEditorsView(); if (openEditorsView) { - openEditorsView.expand(); + openEditorsView.setExpanded(true); openEditorsView.getViewer().DOMFocus(); } }); @@ -1740,7 +1740,7 @@ export class FocusFilesExplorer extends Action { return this.viewletService.openViewlet(VIEWLET_ID, true).then((viewlet: ExplorerViewlet) => { const view = viewlet.getExplorerView(); if (view) { - view.expand(); + view.setExpanded(true); view.getViewer().DOMFocus(); } }); diff --git a/src/vs/workbench/parts/files/browser/fileCommands.ts b/src/vs/workbench/parts/files/browser/fileCommands.ts index 157239a2b27..ffad1a5c8b5 100644 --- a/src/vs/workbench/parts/files/browser/fileCommands.ts +++ b/src/vs/workbench/parts/files/browser/fileCommands.ts @@ -97,13 +97,13 @@ export const revealInExplorerCommand = (accessor: ServicesAccessor, resource: UR if (isInsideWorkspace) { const explorerView = viewlet.getExplorerView(); if (explorerView) { - explorerView.expand(); + explorerView.setExpanded(true); explorerView.select(resource, true); } } else { const openEditorsView = viewlet.getOpenEditorsView(); if (openEditorsView) { - openEditorsView.expand(); + openEditorsView.setExpanded(true); } } }); diff --git a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css index b02fed754ab..8c7564fac80 100644 --- a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css @@ -53,7 +53,7 @@ visibility: hidden; } -.explorer-viewlet .header .monaco-count-badge.hidden { +.explorer-viewlet .panel-header .monaco-count-badge.hidden { display: none; visibility: hidden; } diff --git a/src/vs/workbench/parts/files/browser/views/emptyView.ts b/src/vs/workbench/parts/files/browser/views/emptyView.ts index 97ad8d8d9ad..19625a384ee 100644 --- a/src/vs/workbench/parts/files/browser/views/emptyView.ts +++ b/src/vs/workbench/parts/files/browser/views/emptyView.ts @@ -13,16 +13,15 @@ import { IAction } from 'vs/base/common/actions'; import { Button } from 'vs/base/browser/ui/button/button'; import { $ } from 'vs/base/browser/builder'; import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CollapsibleView, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/views'; +import { ViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; -export class EmptyView extends CollapsibleView { +export class EmptyView extends ViewsViewletPanel { public static ID: string = 'workbench.explorer.emptyView'; public static NAME = nls.localize('noWorkspace', "No Folder Opened"); @@ -30,14 +29,13 @@ export class EmptyView extends CollapsibleView { private openFolderButton: Button; constructor( - initialSize: number, options: IViewletViewOptions, @IThemeService private themeService: IThemeService, @IInstantiationService private instantiationService: IInstantiationService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService ) { - super(initialSize, { ...(options as IViewOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section"), sizing: ViewSizing.Flexible }, keybindingService, contextMenuService); + super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService); } public renderHeader(container: HTMLElement): void { diff --git a/src/vs/workbench/parts/files/browser/views/explorerView.ts b/src/vs/workbench/parts/files/browser/views/explorerView.ts index 05a68b4275f..1a84751abf2 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerView.ts @@ -27,7 +27,7 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import * as DOM from 'vs/base/browser/dom'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; -import { CollapsibleView, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/views'; +import { ViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { FileStat, Model } from 'vs/workbench/parts/files/common/explorerModel'; import { IListService } from 'vs/platform/list/browser/listService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -45,13 +45,12 @@ import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/th import { isLinux } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { attachListStyler } from 'vs/platform/theme/common/styler'; -import { ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; export interface IExplorerViewOptions extends IViewletViewOptions { viewletState: FileViewletState; } -export class ExplorerView extends CollapsibleView { +export class ExplorerView extends ViewsViewletPanel { public static ID: string = 'workbench.explorer.fileView'; private static EXPLORER_FILE_CHANGES_REACT_DELAY = 500; // delay in ms to react to file changes to give our internal events a chance to react first @@ -84,7 +83,6 @@ export class ExplorerView extends CollapsibleView { private settings: object; constructor( - initialSize: number, options: IExplorerViewOptions, @IMessageService private messageService: IMessageService, @IContextMenuService contextMenuService: IContextMenuService, @@ -102,11 +100,10 @@ export class ExplorerView extends CollapsibleView { @IWorkbenchThemeService private themeService: IWorkbenchThemeService, @IEnvironmentService private environmentService: IEnvironmentService ) { - super(initialSize, { ...(options as IViewOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section"), sizing: ViewSizing.Flexible }, keybindingService, contextMenuService); + super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService); this.settings = options.viewletSettings; this.viewletState = options.viewletState; - this.actionRunner = options.actionRunner; this.autoReveal = true; this.explorerRefreshDelayer = new ThrottledDelayer(ExplorerView.EXPLORER_FILE_CHANGES_REFRESH_DELAY); @@ -128,18 +125,19 @@ export class ExplorerView extends CollapsibleView { return (configuration && configuration.files && configuration.files.exclude) || Object.create(null); } - public renderHeader(container: HTMLElement): void { - const titleDiv = $('div.title').appendTo(container); - const titleSpan = $('span').appendTo(titleDiv); + protected renderHeader(container: HTMLElement): void { + super.renderHeader(container); + + const titleElement = container.querySelector('.title') as HTMLElement; const setHeader = () => { const workspace = this.contextService.getWorkspace(); const title = workspace.folders.map(folder => folder.name).join(); - titleSpan.text(this.name).title(title); + titleElement.textContent = this.name; + titleElement.title = title; }; - this.toDispose.push(this.contextService.onDidChangeWorkspaceName(() => setHeader())); - setHeader(); - super.renderHeader(container); + this.disposables.push(this.contextService.onDidChangeWorkspaceName(setHeader)); + setHeader(); } public get name(): string { @@ -157,16 +155,16 @@ export class ExplorerView extends CollapsibleView { this.tree = this.createViewer($(this.treeContainer)); - if (this.toolBar) { - this.toolBar.setActions(prepareActions(this.getActions()), this.getSecondaryActions())(); + if (this.toolbar) { + this.toolbar.setActions(prepareActions(this.getActions()), this.getSecondaryActions())(); } const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => { DOM.toggleClass(this.treeContainer, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); }; - this.toDispose.push(this.themeService.onDidFileIconThemeChange(onFileIconThemeChange)); - this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders((e) => this.refreshFromEvent(e.added))); + this.disposables.push(this.themeService.onDidFileIconThemeChange(onFileIconThemeChange)); + this.disposables.push(this.contextService.onDidChangeWorkspaceFolders(e => this.refreshFromEvent(e.added))); onFileIconThemeChange(this.themeService.getFileIconTheme()); } @@ -201,10 +199,10 @@ export class ExplorerView extends CollapsibleView { return this.doRefresh(targetsToExpand).then(() => { // When the explorer viewer is loaded, listen to changes to the editor input - this.toDispose.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + this.disposables.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); // Also handle configuration updates - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration(), true))); + this.disposables.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration(), true))); }); } @@ -276,7 +274,9 @@ export class ExplorerView extends CollapsibleView { } } - public focusBody(): void { + public focus(): void { + super.focus(); + let keepFocus = false; // Make sure the current selected element is revealed @@ -379,7 +379,7 @@ export class ExplorerView extends CollapsibleView { @memoize private get model(): Model { const model = this.instantiationService.createInstance(Model); - this.toDispose.push(model); + this.disposables.push(model); return model; } @@ -389,9 +389,9 @@ export class ExplorerView extends CollapsibleView { const renderer = this.instantiationService.createInstance(FileRenderer, this.viewletState); const controller = this.instantiationService.createInstance(FileController, this.viewletState); const sorter = this.instantiationService.createInstance(FileSorter); - this.toDispose.push(sorter); + this.disposables.push(sorter); this.filter = this.instantiationService.createInstance(FileFilter); - this.toDispose.push(this.filter); + this.disposables.push(this.filter); const dnd = this.instantiationService.createInstance(FileDragAndDrop); const accessibilityProvider = this.instantiationService.createInstance(FileAccessibilityProvider); @@ -412,23 +412,23 @@ export class ExplorerView extends CollapsibleView { }); // Theme styler - this.toDispose.push(attachListStyler(this.explorerViewer, this.themeService)); + this.disposables.push(attachListStyler(this.explorerViewer, this.themeService)); // Register to list service - this.toDispose.push(this.listService.register(this.explorerViewer, [this.explorerFocusedContext, this.filesExplorerFocusedContext])); + this.disposables.push(this.listService.register(this.explorerViewer, [this.explorerFocusedContext, this.filesExplorerFocusedContext])); // Update Viewer based on File Change Events - this.toDispose.push(this.fileService.onAfterOperation(e => this.onFileOperation(e))); - this.toDispose.push(this.fileService.onFileChanges(e => this.onFileChanges(e))); + this.disposables.push(this.fileService.onAfterOperation(e => this.onFileOperation(e))); + this.disposables.push(this.fileService.onFileChanges(e => this.onFileChanges(e))); // Update resource context based on focused element - this.toDispose.push(this.explorerViewer.addListener('focus', (e: { focus: FileStat }) => { + this.disposables.push(this.explorerViewer.addListener('focus', (e: { focus: FileStat }) => { this.resourceContext.set(e.focus && e.focus.resource); this.folderContext.set(e.focus && e.focus.isDirectory); })); // Open when selecting via keyboard - this.toDispose.push(this.explorerViewer.addListener('selection', event => { + this.disposables.push(this.explorerViewer.addListener('selection', event => { if (event && event.payload && event.payload.origin === 'keyboard') { const element = this.tree.getSelection(); @@ -933,12 +933,4 @@ export class ExplorerView extends CollapsibleView { super.shutdown(); } - - public dispose(): void { - if (this.toolBar) { - this.toolBar.dispose(); - } - - super.dispose(); - } } diff --git a/src/vs/workbench/parts/files/browser/views/openEditorsView.ts b/src/vs/workbench/parts/files/browser/views/openEditorsView.ts index 9401bf431f6..25f69fcd76a 100644 --- a/src/vs/workbench/parts/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/parts/files/browser/views/openEditorsView.ts @@ -18,7 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup } from 'vs/workbench/common/editor'; import { SaveAllAction } from 'vs/workbench/parts/files/browser/fileActions'; -import { CollapsibleView, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/views'; +import { ViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IFilesConfiguration, VIEWLET_ID, OpenEditorsFocusedContext, ExplorerFocusedContext } from 'vs/workbench/parts/files/common/files'; import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -33,11 +33,10 @@ import { EditorGroup } from 'vs/workbench/common/editor/editorStacksModel'; import { attachListStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; const $ = dom.$; -export class OpenEditorsView extends CollapsibleView { +export class OpenEditorsView extends ViewsViewletPanel { private static DEFAULT_VISIBLE_OPEN_EDITORS = 9; private static DEFAULT_DYNAMIC_HEIGHT = true; @@ -58,7 +57,6 @@ export class OpenEditorsView extends CollapsibleView { private explorerFocusedContext: IContextKey; constructor( - initialSize: number, options: IViewletViewOptions, @IInstantiationService private instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, @@ -72,11 +70,9 @@ export class OpenEditorsView extends CollapsibleView { @IViewletService private viewletService: IViewletService, @IThemeService private themeService: IThemeService ) { - super(initialSize, { + super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize({ key: 'openEditosrSection', comment: ['Open is an adjective'] }, "Open Editors Section"), - sizing: ViewSizing.Fixed, - initialBodySize: OpenEditorsView.computeExpandedBodySize(editorGroupService.getStacksModel()) }, keybindingService, contextMenuService); this.model = editorGroupService.getStacksModel(); @@ -88,14 +84,14 @@ export class OpenEditorsView extends CollapsibleView { this.structuralTreeRefreshScheduler = new RunOnceScheduler(() => this.structuralTreeUpdate(), this.structuralRefreshDelay); } - public renderHeader(container: HTMLElement): void { - const titleDiv = dom.append(container, $('.title')); - const titleSpan = dom.append(titleDiv, $('span')); - titleSpan.textContent = this.name; + protected renderHeaderTitle(container: HTMLElement): void { + const title = dom.append(container, $('.title')); + title.textContent = this.name; - this.dirtyCountElement = dom.append(titleDiv, $('.monaco-count-badge')); + const count = dom.append(container, $('.count')); + this.dirtyCountElement = dom.append(count, $('.monaco-count-badge')); - this.toDispose.push((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => { + this.disposables.push((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => { const background = colors.badgeBackground ? colors.badgeBackground.toString() : null; const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : null; const border = colors.contrastBorder ? colors.contrastBorder.toString() : null; @@ -109,8 +105,6 @@ export class OpenEditorsView extends CollapsibleView { }))); this.updateDirtyIndicator(); - - super.renderHeader(container); } public getActions(): IAction[] { @@ -148,20 +142,20 @@ export class OpenEditorsView extends CollapsibleView { }); // Theme styler - this.toDispose.push(attachListStyler(this.tree, this.themeService)); + this.disposables.push(attachListStyler(this.tree, this.themeService)); // Register to list service - this.toDispose.push(this.listService.register(this.tree, [this.explorerFocusedContext, this.openEditorsFocusedContext])); + this.disposables.push(this.listService.register(this.tree, [this.explorerFocusedContext, this.openEditorsFocusedContext])); // Open when selecting via keyboard - this.toDispose.push(this.tree.addListener('selection', event => { + this.disposables.push(this.tree.addListener('selection', event => { if (event && event.payload && event.payload.origin === 'keyboard') { controller.openEditor(this.tree.getFocus(), { pinned: false, sideBySide: false, preserveFocus: false }); } })); // Prevent collapsing of editor groups - this.toDispose.push(this.tree.addListener('item:collapsed', (event: IItemCollapseEvent) => { + this.disposables.push(this.tree.addListener('item:collapsed', (event: IItemCollapseEvent) => { if (event.item && event.item.getElement() instanceof EditorGroup) { setTimeout(() => this.tree.expand(event.item.getElement())); // unwind from callback } @@ -186,20 +180,20 @@ export class OpenEditorsView extends CollapsibleView { private registerListeners(): void { // update on model changes - this.toDispose.push(this.model.onModelChanged(e => this.onEditorStacksModelChanged(e))); + this.disposables.push(this.model.onModelChanged(e => this.onEditorStacksModelChanged(e))); // Also handle configuration updates - this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); + this.disposables.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration()))); // Handle dirty counter - this.toDispose.push(this.untitledEditorService.onDidChangeDirty(e => this.updateDirtyIndicator())); - this.toDispose.push(this.textFileService.models.onModelsDirty(e => this.updateDirtyIndicator())); - this.toDispose.push(this.textFileService.models.onModelsSaved(e => this.updateDirtyIndicator())); - this.toDispose.push(this.textFileService.models.onModelsSaveError(e => this.updateDirtyIndicator())); - this.toDispose.push(this.textFileService.models.onModelsReverted(e => this.updateDirtyIndicator())); + this.disposables.push(this.untitledEditorService.onDidChangeDirty(e => this.updateDirtyIndicator())); + this.disposables.push(this.textFileService.models.onModelsDirty(e => this.updateDirtyIndicator())); + this.disposables.push(this.textFileService.models.onModelsSaved(e => this.updateDirtyIndicator())); + this.disposables.push(this.textFileService.models.onModelsSaveError(e => this.updateDirtyIndicator())); + this.disposables.push(this.textFileService.models.onModelsReverted(e => this.updateDirtyIndicator())); // We are not updating the tree while the viewlet is not visible. Thus refresh when viewlet becomes visible #6702 - this.toDispose.push(this.viewletService.onDidViewletOpen(viewlet => { + this.disposables.push(this.viewletService.onDidViewletOpen(viewlet => { if (viewlet.getId() === VIEWLET_ID) { this.fullRefreshNeeded = true; this.structuralTreeUpdate(); @@ -231,7 +225,7 @@ export class OpenEditorsView extends CollapsibleView { private structuralTreeUpdate(): void { // View size - this.setBodySize(this.getExpandedBodySize(this.model)); + this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(this.model); // Show groups only if there is more than 1 group const treeInput = this.model.groups.length === 1 ? this.model.groups[0] : this.model; // TODO@Isidor temporary workaround due to a partial tree refresh issue @@ -285,7 +279,7 @@ export class OpenEditorsView extends CollapsibleView { } // Adjust expanded body size - this.setBodySize(this.getExpandedBodySize(this.model)); + this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(this.model); } private updateDirtyIndicator(): void { diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index af4cfb3ed80..dea5d1800d4 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -550,7 +550,7 @@ export class RepositoryPanel extends ViewletPanel { focus(): void { super.focus(); - if (this.expanded) { + if (this.isExpanded()) { this.inputBox.focus(); } }