mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 20:13:32 +01:00
Merge branch 'splitview' into scm-viewlet
This commit is contained in:
@@ -11,7 +11,7 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import uri from 'vs/base/common/uri';
|
||||
import paths = require('vs/base/common/paths');
|
||||
import { IRootProvider, getPathLabel, IUserHomeProvider } from 'vs/base/common/labels';
|
||||
import { IWorkspaceFolderProvider, getPathLabel, IUserHomeProvider } from 'vs/base/common/labels';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IIconLabelCreationOptions {
|
||||
@@ -152,13 +152,13 @@ export class IconLabel {
|
||||
|
||||
export class FileLabel extends IconLabel {
|
||||
|
||||
constructor(container: HTMLElement, file: uri, provider: IRootProvider, userHome?: IUserHomeProvider) {
|
||||
constructor(container: HTMLElement, file: uri, provider: IWorkspaceFolderProvider, userHome?: IUserHomeProvider) {
|
||||
super(container);
|
||||
|
||||
this.setFile(file, provider, userHome);
|
||||
}
|
||||
|
||||
public setFile(file: uri, provider: IRootProvider, userHome: IUserHomeProvider): void {
|
||||
public setFile(file: uri, provider: IWorkspaceFolderProvider, userHome: IUserHomeProvider): void {
|
||||
const parent = paths.dirname(file.fsPath);
|
||||
|
||||
this.setValue(paths.basename(file.fsPath), parent && parent !== '.' ? getPathLabel(parent, provider, userHome) : '', { title: file.fsPath });
|
||||
|
||||
@@ -398,7 +398,7 @@ class MouseController<T> implements IDisposable {
|
||||
if (isSelectionRangeChangeEvent(e) && reference !== undefined) {
|
||||
const min = Math.min(reference, focus);
|
||||
const max = Math.max(reference, focus);
|
||||
const rangeSelection = range(max + 1, min);
|
||||
const rangeSelection = range(min, max + 1);
|
||||
const selection = this.list.getSelection();
|
||||
const contiguousRange = getContiguousRangeContaining(disjunction(selection, [reference]), reference);
|
||||
|
||||
|
||||
@@ -268,6 +268,10 @@ export class Sash extends EventEmitter {
|
||||
this.isDisabled = true;
|
||||
}
|
||||
|
||||
get enabled(): boolean {
|
||||
return !this.isDisabled;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.$e) {
|
||||
this.$e.destroy();
|
||||
|
||||
361
src/vs/base/browser/ui/splitview/panelview.ts
Normal file
361
src/vs/base/browser/ui/splitview/panelview.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./splitview';
|
||||
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter, chain } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { $, append, addClass, removeClass, toggleClass } 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';
|
||||
|
||||
export interface IPanelOptions {
|
||||
ariaHeaderLabel?: string;
|
||||
minimumBodySize?: number;
|
||||
maximumBodySize?: number;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
export interface IPanelStyles {
|
||||
dropBackground?: Color;
|
||||
}
|
||||
|
||||
export abstract class Panel implements IView {
|
||||
|
||||
private static HEADER_SIZE = 22;
|
||||
|
||||
private _expanded: boolean;
|
||||
private _headerVisible: boolean;
|
||||
private _onDidChange = new Emitter<void>();
|
||||
private _minimumBodySize: number;
|
||||
private _maximumBodySize: number;
|
||||
private ariaHeaderLabel: string;
|
||||
|
||||
readonly header: HTMLElement;
|
||||
protected disposables: IDisposable[] = [];
|
||||
|
||||
get minimumBodySize(): number {
|
||||
return this._minimumBodySize;
|
||||
}
|
||||
|
||||
set minimumBodySize(size: number) {
|
||||
this._minimumBodySize = size;
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
get maximumBodySize(): number {
|
||||
return this._maximumBodySize;
|
||||
}
|
||||
|
||||
set maximumBodySize(size: number) {
|
||||
this._maximumBodySize = size;
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
get minimumSize(): number {
|
||||
const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
|
||||
const expanded = !this.headerVisible || this.expanded;
|
||||
const minimumBodySize = expanded ? this._minimumBodySize : 0;
|
||||
|
||||
return headerSize + minimumBodySize;
|
||||
}
|
||||
|
||||
get maximumSize(): number {
|
||||
const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
|
||||
const expanded = !this.headerVisible || this.expanded;
|
||||
const maximumBodySize = expanded ? this._maximumBodySize : 0;
|
||||
|
||||
return headerSize + maximumBodySize;
|
||||
}
|
||||
|
||||
readonly onDidChange: Event<void> = 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 : 44;
|
||||
this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
get expanded(): boolean {
|
||||
return this._expanded;
|
||||
}
|
||||
|
||||
set expanded(expanded: boolean) {
|
||||
if (this._expanded === !!expanded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._expanded = !!expanded;
|
||||
this.renderHeader();
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
get headerVisible(): boolean {
|
||||
return this._headerVisible;
|
||||
}
|
||||
|
||||
set headerVisible(visible: boolean) {
|
||||
if (this._headerVisible === !!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._headerVisible = !!visible;
|
||||
this.renderHeader();
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
const panel = append(container, $('.panel'));
|
||||
const header = append(panel, $('.panel-header'));
|
||||
|
||||
header.setAttribute('tabindex', '0');
|
||||
header.setAttribute('role', 'toolbar');
|
||||
header.setAttribute('aria-label', this.ariaHeaderLabel);
|
||||
this.renderHeader();
|
||||
|
||||
const onHeaderKeyDown = chain(domEvent(header, 'keydown'))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space)
|
||||
.event(() => this.expanded = !this.expanded, null, this.disposables);
|
||||
|
||||
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow)
|
||||
.event(() => this.expanded = false, null, this.disposables);
|
||||
|
||||
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow)
|
||||
.event(() => this.expanded = true, null, this.disposables);
|
||||
|
||||
domEvent(header, 'click')
|
||||
(() => this.expanded = !this.expanded, null, this.disposables);
|
||||
|
||||
// TODO@Joao move this down to panelview
|
||||
// onHeaderKeyDown.filter(e => e.keyCode === KeyCode.UpArrow)
|
||||
// .event(focusPrevious, this, this.disposables);
|
||||
|
||||
// onHeaderKeyDown.filter(e => e.keyCode === KeyCode.DownArrow)
|
||||
// .event(focusNext, this, this.disposables);
|
||||
|
||||
const body = append(panel, $('.panel-body'));
|
||||
this.renderBody(body);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
|
||||
this.layoutBody(size - headerSize);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
// TODO@joao what to do
|
||||
}
|
||||
|
||||
private renderHeader(): void {
|
||||
const expanded = !this.headerVisible || this.expanded;
|
||||
|
||||
toggleClass(this.header, 'hidden', !this.headerVisible);
|
||||
toggleClass(this.header, 'expanded', expanded);
|
||||
this.header.setAttribute('aria-expanded', String(expanded));
|
||||
}
|
||||
|
||||
protected abstract renderBody(container: HTMLElement): void;
|
||||
protected abstract layoutBody(size: number): void;
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
interface IDndContext {
|
||||
dropBackground: Color | undefined;
|
||||
draggable: PanelDraggable | null;
|
||||
}
|
||||
|
||||
class PanelDraggable implements IDisposable {
|
||||
|
||||
private static DefaultDragOverBackgroundColor = new Color(new RGBA(128, 128, 128, 0.5));
|
||||
|
||||
// see https://github.com/Microsoft/vscode/issues/14470
|
||||
private dragOverCounter = 0;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
private _onDidDrop = new Emitter<{ from: Panel, to: Panel }>();
|
||||
readonly onDidDrop = this._onDidDrop.event;
|
||||
|
||||
constructor(private panel: Panel, private context: IDndContext) {
|
||||
domEvent(panel.header, 'dragstart')(this.onDragStart, this, this.disposables);
|
||||
domEvent(panel.header, 'dragenter')(this.onDragEnter, this, this.disposables);
|
||||
domEvent(panel.header, 'dragleave')(this.onDragLeave, this, this.disposables);
|
||||
domEvent(panel.header, 'dragend')(this.onDragEnd, this, this.disposables);
|
||||
domEvent(panel.header, '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.header.textContent));
|
||||
e.dataTransfer.setDragImage(dragImage, -10, -10);
|
||||
setTimeout(() => document.body.removeChild(dragImage), 0);
|
||||
|
||||
this.context.draggable = this;
|
||||
}
|
||||
|
||||
private onDragEnter(e: DragEvent): void {
|
||||
if (!this.context.draggable || this.context.draggable === this) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dragOverCounter++;
|
||||
this.renderHeader();
|
||||
}
|
||||
|
||||
private onDragLeave(e: DragEvent): void {
|
||||
if (!this.context.draggable || this.context.draggable === this) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dragOverCounter--;
|
||||
|
||||
if (this.dragOverCounter === 0) {
|
||||
this.renderHeader();
|
||||
}
|
||||
}
|
||||
|
||||
private onDragEnd(e: DragEvent): void {
|
||||
if (!this.context.draggable) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dragOverCounter = 0;
|
||||
this.renderHeader();
|
||||
this.context.draggable = null;
|
||||
}
|
||||
|
||||
private onDrop(e: DragEvent): void {
|
||||
if (!this.context.draggable) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dragOverCounter = 0;
|
||||
this.renderHeader();
|
||||
|
||||
if (this.context.draggable !== this) {
|
||||
this._onDidDrop.fire({ from: this.context.draggable.panel, to: this.panel });
|
||||
}
|
||||
|
||||
this.context.draggable = null;
|
||||
}
|
||||
|
||||
private renderHeader(): void {
|
||||
let backgroundColor: string = null;
|
||||
|
||||
if (this.dragOverCounter > 0) {
|
||||
backgroundColor = (this.context.dropBackground || PanelDraggable.DefaultDragOverBackgroundColor).toString();
|
||||
}
|
||||
|
||||
this.panel.header.style.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export class IPanelViewOptions {
|
||||
dnd?: boolean;
|
||||
}
|
||||
|
||||
interface IPanelItem {
|
||||
panel: Panel;
|
||||
disposable: IDisposable;
|
||||
}
|
||||
|
||||
export class PanelView implements IDisposable {
|
||||
|
||||
private dnd: boolean;
|
||||
private dndContext: IDndContext = { dropBackground: undefined, draggable: null };
|
||||
private el: HTMLElement;
|
||||
private panelItems: IPanelItem[] = [];
|
||||
private splitview: SplitView;
|
||||
private animationTimer: number | null = null;
|
||||
|
||||
private _onDidDrop = new Emitter<{ from: Panel, to: Panel }>();
|
||||
readonly onDidDrop: Event<{ from: Panel, to: Panel }> = this._onDidDrop.event;
|
||||
|
||||
constructor(private container: HTMLElement, options?: IPanelViewOptions) {
|
||||
this.dnd = !!options.dnd;
|
||||
this.el = append(container, $('.monaco-panel-view'));
|
||||
this.splitview = new SplitView(container);
|
||||
}
|
||||
|
||||
addPanel(panel: Panel, size: number, index = this.splitview.length): void {
|
||||
const disposables: IDisposable[] = [];
|
||||
panel.onDidChange(this.setupAnimation, this, disposables);
|
||||
|
||||
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 {
|
||||
const index = firstIndex(this.panelItems, item => item.panel === panel);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.splitview.removeView(index);
|
||||
const panelItem = this.panelItems.splice(index, 1)[0];
|
||||
panelItem.disposable.dispose();
|
||||
}
|
||||
|
||||
movePanel(from: Panel, to: Panel): void {
|
||||
const fromIndex = firstIndex(this.panelItems, item => item.panel === from);
|
||||
const toIndex = firstIndex(this.panelItems, item => item.panel === to);
|
||||
|
||||
if (fromIndex === -1 || toIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.splitview.moveView(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
this.splitview.layout(size);
|
||||
}
|
||||
|
||||
style(styles: IPanelStyles): void {
|
||||
this.dndContext.dropBackground = styles.dropBackground;
|
||||
}
|
||||
|
||||
private setupAnimation(): void {
|
||||
if (typeof this.animationTimer === 'number') {
|
||||
window.clearTimeout(this.animationTimer);
|
||||
}
|
||||
|
||||
addClass(this.el, 'animated');
|
||||
|
||||
this.animationTimer = window.setTimeout(() => {
|
||||
this.animationTimer = null;
|
||||
removeClass(this.el, 'animated');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.panelItems.forEach(i => i.disposable.dispose());
|
||||
this.splitview.dispose();
|
||||
}
|
||||
}
|
||||
293
src/vs/base/browser/ui/splitview/splitview2.ts
Normal file
293
src/vs/base/browser/ui/splitview/splitview2.ts
Normal file
@@ -0,0 +1,293 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./splitview';
|
||||
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<void>;
|
||||
render(container: HTMLElement, orientation: Orientation): void;
|
||||
layout(size: number, orientation: Orientation): void;
|
||||
focus(): void;
|
||||
}
|
||||
|
||||
interface ISashEvent {
|
||||
sash: Sash;
|
||||
start: number;
|
||||
current: number;
|
||||
}
|
||||
|
||||
interface IViewItem {
|
||||
view: IView;
|
||||
size: number;
|
||||
explicitSize: number;
|
||||
container: HTMLElement;
|
||||
disposable: IDisposable;
|
||||
}
|
||||
|
||||
interface ISashItem {
|
||||
sash: Sash;
|
||||
disposable: IDisposable;
|
||||
}
|
||||
|
||||
interface ISashDragState {
|
||||
index: number;
|
||||
start: number;
|
||||
sizes: number[];
|
||||
}
|
||||
|
||||
function layoutViewItem(item: IViewItem, orientation: Orientation): void {
|
||||
if (orientation === Orientation.VERTICAL) {
|
||||
item.container.style.height = `${item.size}px`;
|
||||
} else {
|
||||
item.container.style.width = `${item.size}px`;
|
||||
}
|
||||
|
||||
item.view.layout(item.size, orientation);
|
||||
}
|
||||
|
||||
export class SplitView implements IDisposable {
|
||||
|
||||
private orientation: Orientation;
|
||||
private el: HTMLElement;
|
||||
private size = 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-view');
|
||||
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 explicitSize = size;
|
||||
const item: IViewItem = { view, container, explicitSize, size, 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<IBaseSashEvent>(sash, 'start'), sashEventMapper);
|
||||
const onStartDisposable = onStart(this.onSashStart, this);
|
||||
const onChange = mapEvent(fromEventEmitter<IBaseSashEvent>(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.relayoutPreferredSizes();
|
||||
}
|
||||
|
||||
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.relayoutPreferredSizes();
|
||||
}
|
||||
|
||||
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 relayoutPreferredSizes(): void {
|
||||
this.viewItems.forEach(i => i.size = clamp(i.explicitSize, i.view.minimumSize, i.view.maximumSize));
|
||||
this.relayout();
|
||||
}
|
||||
|
||||
private relayout(): void {
|
||||
const previousSize = this.size;
|
||||
this.size = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
this.layout(previousSize);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
this.resize(this.viewItems.length - 1, size - this.size);
|
||||
this.size = Math.max(size, this.viewItems.reduce((r, i) => r + i.size, 0));
|
||||
}
|
||||
|
||||
private onSashStart({ sash, start }: ISashEvent): void {
|
||||
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
||||
const sizes = this.viewItems.map(i => i.size);
|
||||
|
||||
this.sashDragState = { start, index, sizes };
|
||||
}
|
||||
|
||||
private onSashChange({ sash, current }: ISashEvent): void {
|
||||
const { index, start, sizes } = this.sashDragState;
|
||||
|
||||
this.resize(index, current - start, sizes);
|
||||
this.viewItems.forEach(viewItem => viewItem.explicitSize = viewItem.size);
|
||||
}
|
||||
|
||||
private onViewChange(item: IViewItem): void {
|
||||
item.size = clamp(item.size, item.view.minimumSize, item.view.maximumSize);
|
||||
this.relayout();
|
||||
}
|
||||
|
||||
resizeView(index: number, size: number): void {
|
||||
if (index < 0 || index >= this.viewItems.length - 1) {
|
||||
throw new Error('Cant resize view');
|
||||
}
|
||||
|
||||
this.resize(index, size - 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;
|
||||
}
|
||||
}
|
||||
|
||||
this.layoutViews();
|
||||
}
|
||||
|
||||
private layoutViews(): void {
|
||||
this.viewItems.forEach(item => layoutViewItem(item, this.orientation));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Sash not found');
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.viewItems.forEach(i => i.disposable.dispose());
|
||||
this.viewItems = [];
|
||||
|
||||
this.sashItems.forEach(i => i.disposable.dispose());
|
||||
this.sashItems = [];
|
||||
}
|
||||
}
|
||||
@@ -325,11 +325,43 @@ export function flatten<T>(arr: T[][]): T[] {
|
||||
return arr.reduce((r, v) => r.concat(v), []);
|
||||
}
|
||||
|
||||
export function range(to: number, from = 0): number[] {
|
||||
export function range(to: number): number[];
|
||||
export function range(from: number, to: number): number[];
|
||||
export function range(arg: number, to?: number): number[] {
|
||||
let from = typeof to === 'number' ? arg : 0;
|
||||
|
||||
if (typeof to === 'number') {
|
||||
from = arg;
|
||||
} else {
|
||||
from = 0;
|
||||
to = arg;
|
||||
}
|
||||
|
||||
const result: number[] = [];
|
||||
|
||||
for (let i = from; i < to; i++) {
|
||||
result.push(i);
|
||||
if (from <= to) {
|
||||
for (let i = from; i < to; i++) {
|
||||
result.push(i);
|
||||
}
|
||||
} else {
|
||||
for (let i = from; i > to; i--) {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function weave<T>(a: T[], b: T[]): T[] {
|
||||
const result: T[] = [];
|
||||
let ai = 0, bi = 0;
|
||||
|
||||
for (let i = 0, length = a.length + b.length; i < length; i++) {
|
||||
if ((i % 2 === 0 && ai < a.length) || bi >= b.length) {
|
||||
result.push(a[ai++]);
|
||||
} else {
|
||||
result.push(b[bi++]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface IJSONSchema {
|
||||
patternErrorMessage?: string; // VSCode extension
|
||||
deprecationMessage?: string; // VSCode extension
|
||||
enumDescriptions?: string[]; // VSCode extension
|
||||
doNotSuggest?: boolean; // VSCode extension
|
||||
}
|
||||
|
||||
export interface IJSONSchemaMap {
|
||||
|
||||
@@ -17,10 +17,10 @@ export interface ILabelProvider {
|
||||
getLabel(element: any): string;
|
||||
}
|
||||
|
||||
export interface IRootProvider {
|
||||
getRoot(resource: URI): URI;
|
||||
export interface IWorkspaceFolderProvider {
|
||||
getWorkspaceFolder(resource: URI): URI;
|
||||
getWorkspace(): {
|
||||
roots: URI[];
|
||||
folders: URI[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface IUserHomeProvider {
|
||||
userHome: string;
|
||||
}
|
||||
|
||||
export function getPathLabel(resource: URI | string, rootProvider?: IRootProvider, userHomeProvider?: IUserHomeProvider): string {
|
||||
export function getPathLabel(resource: URI | string, rootProvider?: IWorkspaceFolderProvider, userHomeProvider?: IUserHomeProvider): string {
|
||||
if (!resource) {
|
||||
return null;
|
||||
}
|
||||
@@ -38,9 +38,9 @@ export function getPathLabel(resource: URI | string, rootProvider?: IRootProvide
|
||||
}
|
||||
|
||||
// return early if we can resolve a relative path label from the root
|
||||
const baseResource = rootProvider ? rootProvider.getRoot(resource) : null;
|
||||
const baseResource = rootProvider ? rootProvider.getWorkspaceFolder(resource) : null;
|
||||
if (baseResource) {
|
||||
const hasMultipleRoots = rootProvider.getWorkspace().roots.length > 1;
|
||||
const hasMultipleRoots = rootProvider.getWorkspace().folders.length > 1;
|
||||
|
||||
let pathLabel: string;
|
||||
if (isEqual(baseResource.fsPath, resource.fsPath, !platform.isLinux /* ignorecase */)) {
|
||||
|
||||
@@ -46,3 +46,8 @@ export function countToArray(fromOrTo: number, to?: number): number[] {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
@@ -238,9 +238,19 @@ export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean {
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize}
|
||||
*/
|
||||
export const canNormalize = typeof ((<any>'').normalize) === 'function';
|
||||
const nonAsciiCharactersPattern = /[^\u0000-\u0080]/;
|
||||
const normalizedCache = new BoundedMap<string>(10000); // bounded to 10000 elements
|
||||
|
||||
const nfcCache = new BoundedMap<string>(10000); // bounded to 10000 elements
|
||||
export function normalizeNFC(str: string): string {
|
||||
return normalize(str, 'NFC', nfcCache);
|
||||
}
|
||||
|
||||
const nfdCache = new BoundedMap<string>(10000); // bounded to 10000 elements
|
||||
export function normalizeNFD(str: string): string {
|
||||
return normalize(str, 'NFD', nfdCache);
|
||||
}
|
||||
|
||||
const nonAsciiCharactersPattern = /[^\u0000-\u0080]/;
|
||||
function normalize(str: string, form: string, normalizedCache: BoundedMap<string>): string {
|
||||
if (!canNormalize || !str) {
|
||||
return str;
|
||||
}
|
||||
@@ -252,7 +262,7 @@ export function normalizeNFC(str: string): string {
|
||||
|
||||
let res: string;
|
||||
if (nonAsciiCharactersPattern.test(str)) {
|
||||
res = (<any>str).normalize('NFC');
|
||||
res = (<any>str).normalize(form);
|
||||
} else {
|
||||
res = str;
|
||||
}
|
||||
|
||||
@@ -301,8 +301,20 @@ export default class URI {
|
||||
parts.push('//');
|
||||
}
|
||||
if (authority) {
|
||||
let idx = authority.indexOf('@');
|
||||
if (idx !== -1) {
|
||||
const userinfo = authority.substr(0, idx);
|
||||
authority = authority.substr(idx + 1);
|
||||
idx = userinfo.indexOf(':');
|
||||
if (idx === -1) {
|
||||
parts.push(encoder(userinfo));
|
||||
} else {
|
||||
parts.push(encoder(userinfo.substr(0, idx)), ':', encoder(userinfo.substr(idx + 1)));
|
||||
}
|
||||
parts.push('@');
|
||||
}
|
||||
authority = authority.toLowerCase();
|
||||
let idx = authority.indexOf(':');
|
||||
idx = authority.indexOf(':');
|
||||
if (idx === -1) {
|
||||
parts.push(encoder(authority));
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { dirname, basename } from 'path';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
@@ -49,9 +49,11 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
||||
private timeoutHandle: NodeJS.Timer;
|
||||
private disposables: IDisposable[];
|
||||
private _onDidUpdateConfiguration: Emitter<IConfigurationChangeEvent<T>>;
|
||||
private configName: string;
|
||||
|
||||
constructor(private _path: string, private options: IConfigOptions<T> = { changeBufferDelay: 0, defaultConfig: Object.create(null), onError: error => console.error(error) }) {
|
||||
this.disposables = [];
|
||||
this.configName = basename(this._path);
|
||||
|
||||
this._onDidUpdateConfiguration = new Emitter<IConfigurationChangeEvent<T>>();
|
||||
this.disposables.push(this._onDidUpdateConfiguration);
|
||||
@@ -121,8 +123,8 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
||||
private registerWatcher(): void {
|
||||
|
||||
// Watch the parent of the path so that we detect ADD and DELETES
|
||||
const parentFolder = path.dirname(this._path);
|
||||
this.watch(parentFolder);
|
||||
const parentFolder = dirname(this._path);
|
||||
this.watch(parentFolder, true);
|
||||
|
||||
// Check if the path is a symlink and watch its target if so
|
||||
fs.lstat(this._path, (err, stat) => {
|
||||
@@ -137,20 +139,20 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
||||
return; // path is not a valid symlink
|
||||
}
|
||||
|
||||
this.watch(realPath);
|
||||
this.watch(realPath, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private watch(path: string): void {
|
||||
private watch(path: string, isParentFolder: boolean): void {
|
||||
if (this.disposed) {
|
||||
return; // avoid watchers that will never get disposed by checking for being disposed
|
||||
}
|
||||
|
||||
try {
|
||||
const watcher = fs.watch(path);
|
||||
watcher.on('change', () => this.onConfigFileChange());
|
||||
watcher.on('change', (type, file) => this.onConfigFileChange(type, file.toString(), isParentFolder));
|
||||
watcher.on('error', (code, signal) => this.options.onError(`Error watching ${path} for configuration changes (${code}, ${signal})`));
|
||||
|
||||
this.disposables.push(toDisposable(() => {
|
||||
@@ -166,7 +168,11 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private onConfigFileChange(): void {
|
||||
private onConfigFileChange(eventType: string, filename: string, isParentFolder: boolean): void {
|
||||
if (isParentFolder && filename !== this.configName) {
|
||||
return; // a change to a sibling file that is not our config file
|
||||
}
|
||||
|
||||
if (this.timeoutHandle) {
|
||||
global.clearTimeout(this.timeoutHandle);
|
||||
this.timeoutHandle = null;
|
||||
|
||||
@@ -189,3 +189,18 @@ const tmpDir = os.tmpdir();
|
||||
export function del(path: string, tmp = tmpDir): TPromise<void> {
|
||||
return nfcall(extfs.del, path, tmp);
|
||||
}
|
||||
|
||||
export function whenDeleted(path: string): TPromise<void> {
|
||||
|
||||
// Complete when wait marker file is deleted
|
||||
return new TPromise<void>(c => {
|
||||
const interval = setInterval(() => {
|
||||
fs.exists(path, exists => {
|
||||
if (!exists) {
|
||||
clearInterval(interval);
|
||||
c(null);
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
338
src/vs/base/test/browser/ui/splitview/splitview.test.ts
Normal file
338
src/vs/base/test/browser/ui/splitview/splitview.test.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { SplitView, IView, Orientation } from 'vs/base/browser/ui/splitview/splitview2';
|
||||
import { Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
class TestView implements IView {
|
||||
|
||||
private _onDidChange = new Emitter<void>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
get minimumSize(): number { return this._minimumSize; }
|
||||
set minimumSize(size: number) { this._minimumSize = size; this._onDidChange.fire(); }
|
||||
|
||||
get maximumSize(): number { return this._maximumSize; }
|
||||
set maximumSize(size: number) { this._maximumSize = size; this._onDidChange.fire(); }
|
||||
|
||||
private _onDidRender = new Emitter<{ container: HTMLElement; orientation: Orientation }>();
|
||||
readonly onDidRender = this._onDidRender.event;
|
||||
|
||||
private _size = 0;
|
||||
get size(): number { return this._size; }
|
||||
private _onDidLayout = new Emitter<{ size: number; orientation: Orientation }>();
|
||||
readonly onDidLayout = this._onDidLayout.event;
|
||||
|
||||
private _onDidFocus = new Emitter<void>();
|
||||
readonly onDidFocus = this._onDidFocus.event;
|
||||
|
||||
constructor(
|
||||
private _minimumSize: number,
|
||||
private _maximumSize: number
|
||||
) {
|
||||
assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size');
|
||||
}
|
||||
|
||||
render(container: HTMLElement, orientation: Orientation): void {
|
||||
this._onDidRender.fire({ container, orientation });
|
||||
}
|
||||
|
||||
layout(size: number, orientation: Orientation): void {
|
||||
this._size = size;
|
||||
this._onDidLayout.fire({ size, orientation });
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this._onDidFocus.fire();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDidChange.dispose();
|
||||
this._onDidRender.dispose();
|
||||
this._onDidLayout.dispose();
|
||||
this._onDidFocus.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function getSashes(splitview: SplitView): Sash[] {
|
||||
return (splitview as any).sashItems.map(i => i.sash) as Sash[];
|
||||
}
|
||||
|
||||
suite('Splitview', () => {
|
||||
let container: HTMLElement;
|
||||
|
||||
setup(() => {
|
||||
container = document.createElement('div');
|
||||
container.style.position = 'absolute';
|
||||
container.style.width = `${200}px`;
|
||||
container.style.height = `${200}px`;
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
container = null;
|
||||
});
|
||||
|
||||
test('empty splitview has empty DOM', () => {
|
||||
const splitview = new SplitView(container);
|
||||
assert.equal(container.firstElementChild.childElementCount, 0, 'split view should be empty');
|
||||
splitview.dispose();
|
||||
});
|
||||
|
||||
test('has views as sashes as children', () => {
|
||||
const view1 = new TestView(20, 20);
|
||||
const view2 = new TestView(20, 20);
|
||||
const view3 = new TestView(20, 20);
|
||||
const splitview = new SplitView(container);
|
||||
|
||||
splitview.addView(view1, 20);
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
let viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
|
||||
assert.equal(viewQuery.length, 3, 'split view should have 3 views');
|
||||
|
||||
let sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
|
||||
assert.equal(sashQuery.length, 2, 'split view should have 2 sashes');
|
||||
|
||||
splitview.removeView(2);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
|
||||
assert.equal(viewQuery.length, 2, 'split view should have 2 views');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
|
||||
assert.equal(sashQuery.length, 1, 'split view should have 1 sash');
|
||||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
|
||||
assert.equal(viewQuery.length, 1, 'split view should have 1 view');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
|
||||
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
|
||||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
|
||||
assert.equal(viewQuery.length, 0, 'split view should have no views');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
|
||||
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
|
||||
|
||||
splitview.dispose();
|
||||
view1.dispose();
|
||||
view2.dispose();
|
||||
view3.dispose();
|
||||
});
|
||||
|
||||
test('calls view methods on addView and removeView', () => {
|
||||
const view = new TestView(20, 20);
|
||||
const splitview = new SplitView(container);
|
||||
|
||||
let didLayout = false;
|
||||
const layoutDisposable = view.onDidLayout(() => didLayout = true);
|
||||
|
||||
let didRender = false;
|
||||
const renderDisposable = view.onDidRender(() => didRender = true);
|
||||
|
||||
splitview.addView(view, 20);
|
||||
|
||||
assert.equal(view.size, 20, 'view has right size');
|
||||
assert(didLayout, 'layout is called');
|
||||
assert(didLayout, 'render is called');
|
||||
|
||||
splitview.dispose();
|
||||
layoutDisposable.dispose();
|
||||
renderDisposable.dispose();
|
||||
view.dispose();
|
||||
});
|
||||
|
||||
test('stretches view to viewport', () => {
|
||||
const view = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view, 20);
|
||||
assert.equal(view.size, 200, 'view is stretched');
|
||||
|
||||
splitview.layout(200);
|
||||
assert.equal(view.size, 200, 'view stayed the same');
|
||||
|
||||
splitview.layout(100);
|
||||
assert.equal(view.size, 100, 'view is collapsed');
|
||||
|
||||
splitview.layout(20);
|
||||
assert.equal(view.size, 20, 'view is collapsed');
|
||||
|
||||
splitview.layout(10);
|
||||
assert.equal(view.size, 20, 'view is clamped');
|
||||
|
||||
splitview.layout(200);
|
||||
assert.equal(view.size, 200, 'view is stretched');
|
||||
|
||||
splitview.dispose();
|
||||
view.dispose();
|
||||
});
|
||||
|
||||
test('respects preferred sizes with structural changes', () => {
|
||||
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view1, 20);
|
||||
assert.equal(view1.size, 200, 'view1 is stretched');
|
||||
|
||||
splitview.addView(view2, 20);
|
||||
assert.equal(view1.size, 20, 'view1 size is restored');
|
||||
assert.equal(view2.size, 200 - 20, 'view2 is stretched');
|
||||
|
||||
splitview.addView(view3, 20);
|
||||
assert.equal(view1.size, 20, 'view1 size is restored');
|
||||
assert.equal(view2.size, 20, 'view2 size is restored');
|
||||
assert.equal(view3.size, 160, 'view3 is stretched');
|
||||
|
||||
splitview.dispose();
|
||||
view3.dispose();
|
||||
view2.dispose();
|
||||
view1.dispose();
|
||||
});
|
||||
|
||||
test('can resize views', () => {
|
||||
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view1, 20);
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 size is the default');
|
||||
assert.equal(view2.size, 20, 'view2 size the the default');
|
||||
assert.equal(view3.size, 160, 'view3 is stretched');
|
||||
|
||||
splitview.resizeView(1, 40);
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 is untouched');
|
||||
assert.equal(view2.size, 40, 'view2 is stretched');
|
||||
assert.equal(view3.size, 140, 'view3 is collapsed');
|
||||
|
||||
splitview.resizeView(0, 70);
|
||||
|
||||
assert.equal(view1.size, 70, 'view1 is stretched');
|
||||
assert.equal(view2.size, 20, 'view2 is collapsed');
|
||||
assert.equal(view3.size, 110, 'view3 is collapsed');
|
||||
|
||||
assert.throws(() => splitview.resizeView(2, 20));
|
||||
|
||||
assert.equal(view1.size, 70, 'view1 stays the same');
|
||||
assert.equal(view2.size, 20, 'view2 stays the same');
|
||||
assert.equal(view3.size, 110, 'view3 stays the same');
|
||||
|
||||
splitview.dispose();
|
||||
view3.dispose();
|
||||
view2.dispose();
|
||||
view1.dispose();
|
||||
});
|
||||
|
||||
test('reacts to view changes', () => {
|
||||
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view1, 20);
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 size is restored');
|
||||
assert.equal(view2.size, 20, 'view2 size is restored');
|
||||
assert.equal(view3.size, 160, 'view3 is stretched');
|
||||
|
||||
view3.maximumSize = 20;
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 stays the same');
|
||||
assert.equal(view2.size, 160, 'view2 is stretched');
|
||||
assert.equal(view3.size, 20, 'view3 is collapsed');
|
||||
|
||||
view2.maximumSize = 40;
|
||||
|
||||
assert.equal(view1.size, 140, 'view1 is stretched');
|
||||
assert.equal(view2.size, 40, 'view2 is collapsed');
|
||||
assert.equal(view3.size, 20, 'view3 is collapsed');
|
||||
|
||||
view3.maximumSize = 200;
|
||||
|
||||
assert.equal(view1.size, 140, 'view1 stays the same');
|
||||
assert.equal(view2.size, 40, 'view2 stays the same');
|
||||
assert.equal(view3.size, 20, 'view3 stays the same');
|
||||
|
||||
view3.minimumSize = 100;
|
||||
|
||||
assert.equal(view1.size, 80, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 20, 'view2 stays the same');
|
||||
assert.equal(view3.size, 100, 'view3 is stretched');
|
||||
|
||||
splitview.dispose();
|
||||
view3.dispose();
|
||||
view2.dispose();
|
||||
view1.dispose();
|
||||
});
|
||||
|
||||
test('sashes are properly enabled/disabled', () => {
|
||||
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view1, 20);
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
let sashes = getSashes(splitview);
|
||||
assert.equal(sashes.length, 2, 'there are two sashes');
|
||||
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
splitview.layout(60);
|
||||
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
|
||||
assert.equal(sashes[1].enabled, false, 'second sash is disabled');
|
||||
|
||||
splitview.layout(20);
|
||||
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
|
||||
assert.equal(sashes[1].enabled, false, 'second sash is disabled');
|
||||
|
||||
splitview.layout(200);
|
||||
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
view1.maximumSize = 20;
|
||||
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
view2.maximumSize = 20;
|
||||
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
|
||||
assert.equal(sashes[1].enabled, false, 'second sash is disabled');
|
||||
|
||||
view1.maximumSize = 300;
|
||||
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
view2.maximumSize = 200;
|
||||
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
|
||||
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
|
||||
|
||||
splitview.dispose();
|
||||
view3.dispose();
|
||||
view2.dispose();
|
||||
view1.dispose();
|
||||
});
|
||||
});
|
||||
@@ -368,6 +368,23 @@ suite('URI', () => {
|
||||
assert.equal(value.toString(), 'http://l%C3%B6calhost:8080/far');
|
||||
});
|
||||
|
||||
test('URI#toString, user information in authority', () => {
|
||||
var value = URI.parse('http://foo:bar@localhost/far');
|
||||
assert.equal(value.toString(), 'http://foo:bar@localhost/far');
|
||||
|
||||
value = URI.parse('http://foo@localhost/far');
|
||||
assert.equal(value.toString(), 'http://foo@localhost/far');
|
||||
|
||||
value = URI.parse('http://foo:bAr@localhost:8080/far');
|
||||
assert.equal(value.toString(), 'http://foo:bAr@localhost:8080/far');
|
||||
|
||||
value = URI.parse('http://foo@localhost:8080/far');
|
||||
assert.equal(value.toString(), 'http://foo@localhost:8080/far');
|
||||
|
||||
value = URI.from({ scheme: 'http', authority: 'föö:bör@löcalhost:8080', path: '/far', query: undefined, fragment: undefined });
|
||||
assert.equal(value.toString(), 'http://f%C3%B6%C3%B6:b%C3%B6r@l%C3%B6calhost:8080/far');
|
||||
});
|
||||
|
||||
test('correctFileUriToFilePath2', () => {
|
||||
|
||||
var test = (input: string, expected: string) => {
|
||||
|
||||
Reference in New Issue
Block a user