mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
tree: abstract tree
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./tree';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IListOptions, List, IIdentityProvider, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IVirtualDelegate, IRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list';
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import { Event, Relay, chain } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ITreeModel, ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
function toTreeListOptions<T>(options?: IListOptions<T>): IListOptions<ITreeNode<T, any>> {
|
||||
if (!options) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let identityProvider: IIdentityProvider<ITreeNode<T, any>> | undefined = undefined;
|
||||
let multipleSelectionController: IMultipleSelectionController<ITreeNode<T, any>> | undefined = undefined;
|
||||
|
||||
if (options.identityProvider) {
|
||||
identityProvider = el => options.identityProvider(el.element);
|
||||
}
|
||||
|
||||
if (options.multipleSelectionController) {
|
||||
multipleSelectionController = {
|
||||
isSelectionSingleChangeEvent(e) {
|
||||
return options.multipleSelectionController.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
|
||||
},
|
||||
isSelectionRangeChangeEvent(e) {
|
||||
return options.multipleSelectionController.isSelectionRangeChangeEvent({ ...e, element: e.element } as any);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...options,
|
||||
identityProvider,
|
||||
multipleSelectionController
|
||||
};
|
||||
}
|
||||
|
||||
class TreeDelegate<T> implements IVirtualDelegate<ITreeNode<T, any>> {
|
||||
|
||||
constructor(private delegate: IVirtualDelegate<T>) { }
|
||||
|
||||
getHeight(element: ITreeNode<T, any>): number {
|
||||
return this.delegate.getHeight(element.element);
|
||||
}
|
||||
|
||||
getTemplateId(element: ITreeNode<T, any>): string {
|
||||
return this.delegate.getTemplateId(element.element);
|
||||
}
|
||||
}
|
||||
|
||||
interface ITreeListTemplateData<T> {
|
||||
twistie: HTMLElement;
|
||||
count: HTMLElement;
|
||||
templateData: T;
|
||||
}
|
||||
|
||||
function renderTwistie<T>(node: ITreeNode<T, any>, twistie: HTMLElement): void {
|
||||
if (node.children.length === 0 && !node.collapsible) {
|
||||
twistie.innerText = '';
|
||||
} else {
|
||||
twistie.innerText = node.collapsed ? '▹' : '◢';
|
||||
}
|
||||
}
|
||||
|
||||
class TreeRenderer<T, TFilterData, TTemplateData> implements IRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {
|
||||
|
||||
readonly templateId: string;
|
||||
private renderedNodes = new Map<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>>();
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private renderer: IRenderer<T, TTemplateData>,
|
||||
onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>
|
||||
) {
|
||||
this.templateId = renderer.templateId;
|
||||
onDidChangeCollapseState(this.onDidChangeCollapseState, this, this.disposables);
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ITreeListTemplateData<TTemplateData> {
|
||||
const el = append(container, $('.monaco-tl-row'));
|
||||
const twistie = append(el, $('.tl-twistie'));
|
||||
const contents = append(el, $('.tl-contents'));
|
||||
const count = append(el, $('.tl-count'));
|
||||
const templateData = this.renderer.renderTemplate(contents);
|
||||
|
||||
return { twistie, count, templateData };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>): void {
|
||||
this.renderedNodes.set(node, templateData);
|
||||
|
||||
templateData.twistie.style.width = `${10 + node.depth * 10}px`;
|
||||
renderTwistie(node, templateData.twistie);
|
||||
templateData.count.textContent = `${node.revealedCount}`;
|
||||
|
||||
this.renderer.renderElement(node.element, index, templateData.templateData);
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<T, TFilterData>): void {
|
||||
this.renderedNodes.delete(node);
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ITreeListTemplateData<TTemplateData>): void {
|
||||
this.renderer.disposeTemplate(templateData.templateData);
|
||||
}
|
||||
|
||||
private onDidChangeCollapseState(node: ITreeNode<T, TFilterData>): void {
|
||||
const templateData = this.renderedNodes.get(node);
|
||||
|
||||
if (!templateData) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderTwistie(node, templateData.twistie);
|
||||
templateData.count.textContent = `${node.revealedCount}`;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.renderedNodes.clear();
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
function isInputElement(e: HTMLElement): boolean {
|
||||
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
|
||||
}
|
||||
|
||||
export interface ITreeOptions<T, TFilterData = void> extends IListOptions<T>, IIndexTreeModelOptions<T, TFilterData> { }
|
||||
|
||||
export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable {
|
||||
|
||||
private view: List<ITreeNode<T, TFilterData>>;
|
||||
protected model: ITreeModel<T, TFilterData, TRef>;
|
||||
protected disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IVirtualDelegate<T>,
|
||||
renderers: IRenderer<T, any>[],
|
||||
options?: ITreeOptions<T, TFilterData>
|
||||
) {
|
||||
const treeDelegate = new TreeDelegate(delegate);
|
||||
|
||||
const onDidChangeCollapseStateRelay = new Relay<ITreeNode<T, TFilterData>>();
|
||||
const treeRenderers = renderers.map(r => new TreeRenderer(r, onDidChangeCollapseStateRelay.event));
|
||||
this.disposables.push(...treeRenderers);
|
||||
|
||||
this.view = new List(container, treeDelegate, treeRenderers, toTreeListOptions(options));
|
||||
this.model = this.createModel(this.view, options);
|
||||
onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;
|
||||
|
||||
this.view.onMouseClick(this.onMouseClick, this, this.disposables);
|
||||
|
||||
const onKeyDown = chain(this.view.onKeyDown)
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow).on(this.onLeftArrow, this, this.disposables);
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.RightArrow).on(this.onRightArrow, this, this.disposables);
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables);
|
||||
}
|
||||
|
||||
protected abstract createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: ITreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, TRef>;
|
||||
|
||||
// collapseAll(): void {
|
||||
// this.model.setCollapsedAll(true);
|
||||
// }
|
||||
|
||||
refilter(): void {
|
||||
this.model.refilter();
|
||||
}
|
||||
|
||||
private onMouseClick(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
|
||||
const node = e.element;
|
||||
const location = this.model.getNodeLocation(node);
|
||||
|
||||
this.model.toggleCollapsed(location);
|
||||
}
|
||||
|
||||
private onLeftArrow(e: StandardKeyboardEvent): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const nodes = this.view.getFocusedElements();
|
||||
|
||||
if (nodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = nodes[0];
|
||||
const location = this.model.getNodeLocation(node);
|
||||
const didChange = this.model.setCollapsed(location, true);
|
||||
|
||||
if (!didChange) {
|
||||
const parentLocation = this.model.getParentNodeLocation(location);
|
||||
|
||||
if (parentLocation === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentListIndex = this.model.getListIndex(parentLocation);
|
||||
|
||||
this.view.reveal(parentListIndex);
|
||||
this.view.setFocus([parentListIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
private onRightArrow(e: StandardKeyboardEvent): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const nodes = this.view.getFocusedElements();
|
||||
|
||||
if (nodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = nodes[0];
|
||||
const location = this.model.getNodeLocation(node);
|
||||
const didChange = this.model.setCollapsed(location, false);
|
||||
|
||||
if (!didChange) {
|
||||
if (node.children.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [focusedIndex] = this.view.getFocus();
|
||||
const firstChildIndex = focusedIndex + 1;
|
||||
|
||||
this.view.reveal(firstChildIndex);
|
||||
this.view.setFocus([firstChildIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
private onSpace(e: StandardKeyboardEvent): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const nodes = this.view.getFocusedElements();
|
||||
|
||||
if (nodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = nodes[0];
|
||||
const location = this.model.getNodeLocation(node);
|
||||
this.model.toggleCollapsed(location);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
this.view.dispose();
|
||||
this.view = null;
|
||||
this.model = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./tree';
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, ITreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { ITreeElement, ITreeModel, ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
|
||||
export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterData, number[]> {
|
||||
|
||||
protected model: IndexTreeModel<T, TFilterData>;
|
||||
|
||||
splice(location: number[], deleteCount: number, toInsert: ISequence<ITreeElement<T>> = Iterator.empty()): Iterator<ITreeElement<T>> {
|
||||
return this.model.splice(location, deleteCount, toInsert);
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: ITreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
|
||||
return new IndexTreeModel(view, options);
|
||||
}
|
||||
}
|
||||
+46
-71
@@ -6,24 +6,8 @@
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
|
||||
export interface ITreeElement<T> {
|
||||
readonly element: T;
|
||||
readonly children?: Iterator<ITreeElement<T>> | ITreeElement<T>[];
|
||||
readonly collapsible?: boolean;
|
||||
readonly collapsed?: boolean;
|
||||
}
|
||||
|
||||
export interface ITreeNode<T, TFilterData = void> {
|
||||
readonly parent: ITreeNode<T, TFilterData> | undefined;
|
||||
readonly element: T;
|
||||
readonly children: ITreeNode<T, TFilterData>[];
|
||||
readonly depth: number;
|
||||
readonly collapsible: boolean;
|
||||
readonly collapsed: boolean;
|
||||
readonly revealedCount: number;
|
||||
readonly filterData: TFilterData | undefined;
|
||||
}
|
||||
import { tail2 } from 'vs/base/common/arrays';
|
||||
import { ITreeFilterResult, TreeVisibility, ITreeFilter, ITreeOptions, ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree';
|
||||
|
||||
interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
|
||||
readonly parent: IMutableTreeNode<T, TFilterData> | undefined;
|
||||
@@ -35,25 +19,10 @@ interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export const enum Visibility {
|
||||
Hidden,
|
||||
Visible,
|
||||
Recurse // TODO@joao come up with a better name
|
||||
}
|
||||
|
||||
export interface IFilterResult<TFilterData> {
|
||||
visibility: Visibility;
|
||||
data: TFilterData;
|
||||
}
|
||||
|
||||
function isFilterResult<T>(obj: any): obj is IFilterResult<T> {
|
||||
function isFilterResult<T>(obj: any): obj is ITreeFilterResult<T> {
|
||||
return typeof obj === 'object' && 'visibility' in obj && 'data' in obj;
|
||||
}
|
||||
|
||||
export interface ITreeFilter<T, TFilterData = void> {
|
||||
filter(element: T): boolean | Visibility | IFilterResult<TFilterData>;
|
||||
}
|
||||
|
||||
function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
|
||||
const { element, collapsed } = node;
|
||||
const children = Iterator.map(Iterator.fromArray(node.children), treeNodeToElement);
|
||||
@@ -61,31 +30,17 @@ function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
|
||||
return { element, children, collapsed };
|
||||
}
|
||||
|
||||
function getVisibleState(visibility: Visibility): boolean | undefined {
|
||||
function getVisibleState(visibility: TreeVisibility): boolean | undefined {
|
||||
switch (visibility) {
|
||||
case Visibility.Hidden: return false;
|
||||
case Visibility.Visible: return true;
|
||||
case Visibility.Recurse: return undefined;
|
||||
case TreeVisibility.Hidden: return false;
|
||||
case TreeVisibility.Visible: return true;
|
||||
case TreeVisibility.Recurse: return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITreeModelOptions<T, TFilterData = void> {
|
||||
filter?: ITreeFilter<T, TFilterData>;
|
||||
}
|
||||
export interface IIndexTreeModelOptions<T, TFilterData> extends ITreeOptions<T, TFilterData> { }
|
||||
|
||||
export class TreeModel<T, TFilterData = void> {
|
||||
|
||||
// TODO@joao perf!
|
||||
static getNodeLocation<T>(node: ITreeNode<T, any>): number[] {
|
||||
const location = [];
|
||||
|
||||
while (node.parent) {
|
||||
location.push(node.parent.children.indexOf(node));
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
return location.reverse();
|
||||
}
|
||||
export class IndexTreeModel<T, TFilterData = void> implements ITreeModel<T, TFilterData, number[]> {
|
||||
|
||||
private root: IMutableTreeNode<T, TFilterData> = {
|
||||
parent: undefined,
|
||||
@@ -104,7 +59,7 @@ export class TreeModel<T, TFilterData = void> {
|
||||
|
||||
private filter?: ITreeFilter<T, TFilterData>;
|
||||
|
||||
constructor(private list: ISpliceable<ITreeNode<T, TFilterData>>, options: ITreeModelOptions<T, TFilterData> = {}) {
|
||||
constructor(private list: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeModelOptions<T, TFilterData> = {}) {
|
||||
this.filter = options.filter;
|
||||
}
|
||||
|
||||
@@ -167,28 +122,28 @@ export class TreeModel<T, TFilterData = void> {
|
||||
this._setCollapsed(node, listIndex, revealed);
|
||||
}
|
||||
|
||||
// TODO@joao cleanup
|
||||
setCollapsedAll(collapsed: boolean): void {
|
||||
if (collapsed) {
|
||||
const queue = [...this.root.children]; // TODO@joao use a linked list
|
||||
let listIndex = 0;
|
||||
// // TODO@joao cleanup
|
||||
// setCollapsedAll(collapsed: boolean): void {
|
||||
// if (collapsed) {
|
||||
// const queue = [...this.root.children]; // TODO@joao use a linked list
|
||||
// let listIndex = 0;
|
||||
|
||||
while (queue.length > 0) {
|
||||
const node = queue.shift();
|
||||
const revealed = listIndex < this.root.children.length;
|
||||
this._setCollapsed(node, listIndex, revealed, collapsed);
|
||||
// while (queue.length > 0) {
|
||||
// const node = queue.shift();
|
||||
// const revealed = listIndex < this.root.children.length;
|
||||
// this._setCollapsed(node, listIndex, revealed, collapsed);
|
||||
|
||||
queue.push(...node.children);
|
||||
listIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// queue.push(...node.children);
|
||||
// listIndex++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
isCollapsed(location: number[]): boolean {
|
||||
return this.findNode(location).node.collapsed;
|
||||
}
|
||||
|
||||
refilter(/* location?: number[] */): void {
|
||||
refilter(): void {
|
||||
const previousRevealedCount = this.root.revealedCount;
|
||||
const toInsert = this.updateNodeAfterFilterChange(this.root);
|
||||
this.list.splice(0, previousRevealedCount, toInsert);
|
||||
@@ -369,7 +324,7 @@ export class TreeModel<T, TFilterData = void> {
|
||||
}
|
||||
|
||||
private _filterNode(node: IMutableTreeNode<T, TFilterData>): boolean | undefined {
|
||||
const result = this.filter ? this.filter.filter(node.element) : Visibility.Visible;
|
||||
const result = this.filter ? this.filter.filter(node.element) : TreeVisibility.Visible;
|
||||
|
||||
if (typeof result === 'boolean') {
|
||||
node.filterData = undefined;
|
||||
@@ -416,4 +371,24 @@ export class TreeModel<T, TFilterData = void> {
|
||||
|
||||
return this.findParentNode(rest, node.children[index], listIndex + 1, revealed);
|
||||
}
|
||||
|
||||
// TODO@joao perf!
|
||||
getNodeLocation(node: ITreeNode<T, TFilterData>): number[] {
|
||||
const location = [];
|
||||
|
||||
while (node.parent) {
|
||||
location.push(node.parent.children.indexOf(node));
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
return location.reverse();
|
||||
}
|
||||
|
||||
getParentNodeLocation(location: number[]): number[] | null {
|
||||
if (location.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tail2(location)[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./tree';
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, ITreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
|
||||
export class ObjectTree<T, TFilterData = void> extends AbstractTree<T, TFilterData, ITreeNode<T, TFilterData>> {
|
||||
|
||||
protected model: ObjectTreeModel<T, TFilterData>;
|
||||
|
||||
setChildren(element: T | null, children?: ISequence<ITreeElement<T>>): Iterator<ITreeElement<T>> {
|
||||
return this.model.setChildren(element, children);
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: ITreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, ITreeNode<T, TFilterData>> {
|
||||
return new ObjectTreeModel(view, options);
|
||||
}
|
||||
}
|
||||
@@ -7,25 +7,28 @@
|
||||
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { TreeModel, ITreeNode, ITreeModelOptions, ITreeElement } from 'vs/base/browser/ui/tree/treeModel';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree';
|
||||
|
||||
export class TreeObjectModel<T extends NonNullable<any>, TFilterData = void> {
|
||||
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> { }
|
||||
|
||||
private model: TreeModel<T, TFilterData>;
|
||||
export class ObjectTreeModel<T extends NonNullable<any>, TFilterData = void> implements ITreeModel<T, TFilterData, ITreeNode<T, TFilterData>> {
|
||||
|
||||
private model: IndexTreeModel<T, TFilterData>;
|
||||
private nodes = new Map<T, ITreeNode<T, TFilterData>>();
|
||||
|
||||
readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>;
|
||||
|
||||
get size(): number { return this.nodes.size; }
|
||||
|
||||
constructor(list: ISpliceable<ITreeNode<T, TFilterData>>, options: ITreeModelOptions<T, TFilterData> = {}) {
|
||||
this.model = new TreeModel(list, options);
|
||||
constructor(list: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeModelOptions<T, TFilterData> = {}) {
|
||||
this.model = new IndexTreeModel(list, options);
|
||||
this.onDidChangeCollapseState = this.model.onDidChangeCollapseState;
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children?: ISequence<ITreeElement<T>>): Iterator<ITreeElement<T>> {
|
||||
const location = this.getLocation(element);
|
||||
const location = this.getElementLocation(element);
|
||||
const insertedElements = new Set<T>();
|
||||
|
||||
const onDidCreateNode = (node: ITreeNode<T, TFilterData>) => {
|
||||
@@ -42,23 +45,23 @@ export class TreeObjectModel<T extends NonNullable<any>, TFilterData = void> {
|
||||
return this.model.splice([...location, 0], Number.MAX_VALUE, children, onDidCreateNode, onDidDeleteNode);
|
||||
}
|
||||
|
||||
getListIndex(element: T): number {
|
||||
const location = this.getLocation(element);
|
||||
getListIndex(node: ITreeNode<T, TFilterData>): number {
|
||||
const location = this.getElementLocation(node.element);
|
||||
return this.model.getListIndex(location);
|
||||
}
|
||||
|
||||
setCollapsed(element: T, collapsed: boolean): boolean {
|
||||
const location = this.getLocation(element);
|
||||
setCollapsed(node: ITreeNode<T, TFilterData>, collapsed: boolean): boolean {
|
||||
const location = this.getElementLocation(node.element);
|
||||
return this.model.setCollapsed(location, collapsed);
|
||||
}
|
||||
|
||||
toggleCollapsed(element: T): void {
|
||||
const location = this.getLocation(element);
|
||||
toggleCollapsed(node: ITreeNode<T, TFilterData>): void {
|
||||
const location = this.getElementLocation(node.element);
|
||||
this.model.toggleCollapsed(location);
|
||||
}
|
||||
|
||||
isCollapsed(element: T): boolean {
|
||||
const location = this.getLocation(element);
|
||||
isCollapsed(node: ITreeNode<T, TFilterData>): boolean {
|
||||
const location = this.getElementLocation(node.element);
|
||||
return this.model.isCollapsed(location);
|
||||
}
|
||||
|
||||
@@ -66,7 +69,7 @@ export class TreeObjectModel<T extends NonNullable<any>, TFilterData = void> {
|
||||
this.model.refilter();
|
||||
}
|
||||
|
||||
private getLocation(element: T | null): number[] {
|
||||
private getElementLocation(element: T | null): number[] {
|
||||
if (element === null) {
|
||||
return [];
|
||||
}
|
||||
@@ -77,6 +80,14 @@ export class TreeObjectModel<T extends NonNullable<any>, TFilterData = void> {
|
||||
throw new Error(`Tree element not found: ${element}`);
|
||||
}
|
||||
|
||||
return TreeModel.getNodeLocation(node);
|
||||
return this.model.getNodeLocation(node);
|
||||
}
|
||||
|
||||
getNodeLocation(node: ITreeNode<T, TFilterData>): ITreeNode<T, TFilterData> {
|
||||
return node;
|
||||
}
|
||||
|
||||
getParentNodeLocation(node: ITreeNode<T, TFilterData>): ITreeNode<T, TFilterData> | null {
|
||||
return node.parent || null;
|
||||
}
|
||||
}
|
||||
@@ -3,265 +3,55 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./tree';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IListOptions, List, IIdentityProvider, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { TreeModel, ITreeNode, ITreeElement, ITreeModelOptions } from 'vs/base/browser/ui/tree/treeModel';
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { IVirtualDelegate, IRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list';
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import { Event, Relay, chain } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { tail2 } from 'vs/base/common/arrays';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
|
||||
function toTreeListOptions<T>(options?: IListOptions<T>): IListOptions<ITreeNode<T, any>> {
|
||||
if (!options) {
|
||||
return undefined;
|
||||
export const enum TreeVisibility {
|
||||
Hidden,
|
||||
Visible,
|
||||
Recurse // TODO@joao come up with a better name
|
||||
}
|
||||
|
||||
let identityProvider: IIdentityProvider<ITreeNode<T, any>> | undefined = undefined;
|
||||
let multipleSelectionController: IMultipleSelectionController<ITreeNode<T, any>> | undefined = undefined;
|
||||
|
||||
if (options.identityProvider) {
|
||||
identityProvider = el => options.identityProvider(el.element);
|
||||
export interface ITreeFilterResult<TFilterData> {
|
||||
visibility: TreeVisibility;
|
||||
data: TFilterData;
|
||||
}
|
||||
|
||||
if (options.multipleSelectionController) {
|
||||
multipleSelectionController = {
|
||||
isSelectionSingleChangeEvent(e) {
|
||||
return options.multipleSelectionController.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
|
||||
},
|
||||
isSelectionRangeChangeEvent(e) {
|
||||
return options.multipleSelectionController.isSelectionRangeChangeEvent({ ...e, element: e.element } as any);
|
||||
}
|
||||
};
|
||||
export interface ITreeFilter<T, TFilterData = void> {
|
||||
filter(element: T): boolean | TreeVisibility | ITreeFilterResult<TFilterData>;
|
||||
}
|
||||
|
||||
return {
|
||||
...options,
|
||||
identityProvider,
|
||||
multipleSelectionController
|
||||
};
|
||||
export interface ITreeOptions<T, TFilterData = void> {
|
||||
filter?: ITreeFilter<T, TFilterData>;
|
||||
}
|
||||
|
||||
class TreeDelegate<T> implements IVirtualDelegate<ITreeNode<T, any>> {
|
||||
|
||||
constructor(private delegate: IVirtualDelegate<T>) { }
|
||||
|
||||
getHeight(element: ITreeNode<T, any>): number {
|
||||
return this.delegate.getHeight(element.element);
|
||||
export interface ITreeElement<T> {
|
||||
readonly element: T;
|
||||
readonly children?: Iterator<ITreeElement<T>> | ITreeElement<T>[];
|
||||
readonly collapsible?: boolean;
|
||||
readonly collapsed?: boolean;
|
||||
}
|
||||
|
||||
getTemplateId(element: ITreeNode<T, any>): string {
|
||||
return this.delegate.getTemplateId(element.element);
|
||||
}
|
||||
export interface ITreeNode<T, TFilterData = void> {
|
||||
readonly parent: ITreeNode<T, TFilterData> | undefined;
|
||||
readonly element: T;
|
||||
readonly children: ITreeNode<T, TFilterData>[];
|
||||
readonly depth: number;
|
||||
readonly collapsible: boolean;
|
||||
readonly collapsed: boolean;
|
||||
readonly revealedCount: number;
|
||||
readonly filterData: TFilterData | undefined;
|
||||
}
|
||||
|
||||
interface ITreeListTemplateData<T> {
|
||||
twistie: HTMLElement;
|
||||
count: HTMLElement;
|
||||
templateData: T;
|
||||
}
|
||||
|
||||
function renderTwistie<T>(node: ITreeNode<T, any>, twistie: HTMLElement): void {
|
||||
if (node.children.length === 0 && !node.collapsible) {
|
||||
twistie.innerText = '';
|
||||
} else {
|
||||
twistie.innerText = node.collapsed ? '▹' : '◢';
|
||||
}
|
||||
}
|
||||
|
||||
class TreeRenderer<T, TFilterData, TTemplateData> implements IRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {
|
||||
|
||||
readonly templateId: string;
|
||||
private renderedNodes = new Map<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>>();
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private renderer: IRenderer<T, TTemplateData>,
|
||||
onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>
|
||||
) {
|
||||
this.templateId = renderer.templateId;
|
||||
onDidChangeCollapseState(this.onDidChangeCollapseState, this, this.disposables);
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ITreeListTemplateData<TTemplateData> {
|
||||
const el = append(container, $('.monaco-tl-row'));
|
||||
const twistie = append(el, $('.tl-twistie'));
|
||||
const contents = append(el, $('.tl-contents'));
|
||||
const count = append(el, $('.tl-count'));
|
||||
const templateData = this.renderer.renderTemplate(contents);
|
||||
|
||||
return { twistie, count, templateData };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>): void {
|
||||
this.renderedNodes.set(node, templateData);
|
||||
|
||||
templateData.twistie.style.width = `${10 + node.depth * 10}px`;
|
||||
renderTwistie(node, templateData.twistie);
|
||||
templateData.count.textContent = `${node.revealedCount}`;
|
||||
|
||||
this.renderer.renderElement(node.element, index, templateData.templateData);
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<T, TFilterData>): void {
|
||||
this.renderedNodes.delete(node);
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ITreeListTemplateData<TTemplateData>): void {
|
||||
this.renderer.disposeTemplate(templateData.templateData);
|
||||
}
|
||||
|
||||
private onDidChangeCollapseState(node: ITreeNode<T, TFilterData>): void {
|
||||
const templateData = this.renderedNodes.get(node);
|
||||
|
||||
if (!templateData) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderTwistie(node, templateData.twistie);
|
||||
templateData.count.textContent = `${node.revealedCount}`;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.renderedNodes.clear();
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
function isInputElement(e: HTMLElement): boolean {
|
||||
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
|
||||
}
|
||||
|
||||
export interface ITreeOptions<T, TFilterData = void> extends IListOptions<T>, ITreeModelOptions<T, TFilterData> { }
|
||||
|
||||
export class Tree<T, TFilterData = void> implements IDisposable {
|
||||
|
||||
private view: List<ITreeNode<T, TFilterData>>;
|
||||
private model: TreeModel<T, TFilterData>;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IVirtualDelegate<T>,
|
||||
renderers: IRenderer<T, any>[],
|
||||
options?: ITreeOptions<T, TFilterData>
|
||||
) {
|
||||
const treeDelegate = new TreeDelegate(delegate);
|
||||
|
||||
const onDidChangeCollapseStateRelay = new Relay<ITreeNode<T, TFilterData>>();
|
||||
const treeRenderers = renderers.map(r => new TreeRenderer(r, onDidChangeCollapseStateRelay.event));
|
||||
this.disposables.push(...treeRenderers);
|
||||
|
||||
this.view = new List(container, treeDelegate, treeRenderers, toTreeListOptions(options));
|
||||
this.model = new TreeModel<T, TFilterData>(this.view, options);
|
||||
onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;
|
||||
|
||||
this.view.onMouseClick(this.onMouseClick, this, this.disposables);
|
||||
|
||||
const onKeyDown = chain(this.view.onKeyDown)
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow).on(this.onLeftArrow, this, this.disposables);
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.RightArrow).on(this.onRightArrow, this, this.disposables);
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables);
|
||||
}
|
||||
|
||||
splice(location: number[], deleteCount: number, toInsert: ISequence<ITreeElement<T>> = Iterator.empty()): Iterator<ITreeElement<T>> {
|
||||
return this.model.splice(location, deleteCount, toInsert);
|
||||
}
|
||||
|
||||
collapseAll(): void {
|
||||
this.model.setCollapsedAll(true);
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
this.model.refilter();
|
||||
}
|
||||
|
||||
private onMouseClick(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
|
||||
const node = e.element;
|
||||
const location = TreeModel.getNodeLocation(node);
|
||||
|
||||
this.model.toggleCollapsed(location);
|
||||
}
|
||||
|
||||
private onLeftArrow(e: StandardKeyboardEvent): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const nodes = this.view.getFocusedElements();
|
||||
|
||||
if (nodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = nodes[0];
|
||||
const location = TreeModel.getNodeLocation(node);
|
||||
const didChange = this.model.setCollapsed(location, true);
|
||||
|
||||
if (!didChange) {
|
||||
if (location.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [parentLocation] = tail2(location);
|
||||
const parentListIndex = this.model.getListIndex(parentLocation);
|
||||
|
||||
this.view.reveal(parentListIndex);
|
||||
this.view.setFocus([parentListIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
private onRightArrow(e: StandardKeyboardEvent): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const nodes = this.view.getFocusedElements();
|
||||
|
||||
if (nodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = nodes[0];
|
||||
const location = TreeModel.getNodeLocation(node);
|
||||
const didChange = this.model.setCollapsed(location, false);
|
||||
|
||||
if (!didChange) {
|
||||
if (node.children.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [focusedIndex] = this.view.getFocus();
|
||||
const firstChildIndex = focusedIndex + 1;
|
||||
|
||||
this.view.reveal(firstChildIndex);
|
||||
this.view.setFocus([firstChildIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
private onSpace(e: StandardKeyboardEvent): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const nodes = this.view.getFocusedElements();
|
||||
|
||||
if (nodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = nodes[0];
|
||||
const location = TreeModel.getNodeLocation(node);
|
||||
this.model.toggleCollapsed(location);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
this.view.dispose();
|
||||
this.view = null;
|
||||
this.model = null;
|
||||
}
|
||||
export interface ITreeModel<T, TFilterData, TRef> {
|
||||
onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>;
|
||||
|
||||
getListIndex(ref: TRef): number;
|
||||
setCollapsed(ref: TRef, collapsed: boolean): boolean;
|
||||
toggleCollapsed(ref: TRef): void;
|
||||
isCollapsed(ref: TRef): boolean;
|
||||
refilter(): void;
|
||||
|
||||
getNodeLocation(node: ITreeNode<T, any>): TRef;
|
||||
getParentNodeLocation(location: TRef): TRef | null;
|
||||
}
|
||||
+46
-45
@@ -4,9 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { TreeModel, ITreeNode, ITreeFilter, Visibility } from 'vs/base/browser/ui/tree/treeModel';
|
||||
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
return {
|
||||
@@ -20,18 +21,18 @@ function toArray<T>(list: ITreeNode<T>[]): T[] {
|
||||
return list.map(i => i.element);
|
||||
}
|
||||
|
||||
suite('TreeModel2', function () {
|
||||
suite('IndexTreeModel', function () {
|
||||
|
||||
test('ctor', () => {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
});
|
||||
|
||||
test('insert', () => {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{ element: 0 },
|
||||
@@ -53,7 +54,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('deep insert', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -90,7 +91,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('deep insert collapsed', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -118,7 +119,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('delete', () => {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{ element: 0 },
|
||||
@@ -143,7 +144,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('nested delete', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -177,7 +178,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('deep delete', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -205,7 +206,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('hidden delete', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -230,7 +231,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('collapse', () => {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -261,7 +262,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('expand', () => {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -301,7 +302,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('collapse should recursively adjust visible count', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -335,12 +336,12 @@ suite('TreeModel2', function () {
|
||||
test('simple filter', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const filter = new class implements ITreeFilter<number> {
|
||||
filter(element: number): Visibility {
|
||||
return element % 2 === 0 ? Visibility.Visible : Visibility.Hidden;
|
||||
filter(element: number): TreeVisibility {
|
||||
return element % 2 === 0 ? TreeVisibility.Visible : TreeVisibility.Hidden;
|
||||
}
|
||||
};
|
||||
|
||||
const model = new TreeModel<number>(toSpliceable(list), { filter });
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -369,12 +370,12 @@ suite('TreeModel2', function () {
|
||||
test('recursive filter on initial model', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const filter = new class implements ITreeFilter<number> {
|
||||
filter(element: number): Visibility {
|
||||
return element === 0 ? Visibility.Recurse : Visibility.Hidden;
|
||||
filter(element: number): TreeVisibility {
|
||||
return element === 0 ? TreeVisibility.Recurse : TreeVisibility.Hidden;
|
||||
}
|
||||
};
|
||||
|
||||
const model = new TreeModel<number>(toSpliceable(list), { filter });
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -392,12 +393,12 @@ suite('TreeModel2', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
let shouldFilter = false;
|
||||
const filter = new class implements ITreeFilter<number> {
|
||||
filter(element: number): Visibility {
|
||||
return (!shouldFilter || element % 2 === 0) ? Visibility.Visible : Visibility.Hidden;
|
||||
filter(element: number): TreeVisibility {
|
||||
return (!shouldFilter || element % 2 === 0) ? TreeVisibility.Visible : TreeVisibility.Hidden;
|
||||
}
|
||||
};
|
||||
|
||||
const model = new TreeModel<number>(toSpliceable(list), { filter });
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -431,12 +432,12 @@ suite('TreeModel2', function () {
|
||||
const list = [] as ITreeNode<string>[];
|
||||
let query = new RegExp('');
|
||||
const filter = new class implements ITreeFilter<string> {
|
||||
filter(element: string): Visibility {
|
||||
return query.test(element) ? Visibility.Visible : Visibility.Recurse;
|
||||
filter(element: string): TreeVisibility {
|
||||
return query.test(element) ? TreeVisibility.Visible : TreeVisibility.Recurse;
|
||||
}
|
||||
};
|
||||
|
||||
const model = new TreeModel<string>(toSpliceable(list), { filter });
|
||||
const model = new IndexTreeModel<string>(toSpliceable(list), { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -477,12 +478,12 @@ suite('TreeModel2', function () {
|
||||
const list = [] as ITreeNode<string>[];
|
||||
let query = new RegExp('');
|
||||
const filter = new class implements ITreeFilter<string> {
|
||||
filter(element: string): Visibility {
|
||||
return query.test(element) ? Visibility.Visible : Visibility.Recurse;
|
||||
filter(element: string): TreeVisibility {
|
||||
return query.test(element) ? TreeVisibility.Visible : TreeVisibility.Recurse;
|
||||
}
|
||||
};
|
||||
|
||||
const model = new TreeModel<string>(toSpliceable(list), { filter });
|
||||
const model = new IndexTreeModel<string>(toSpliceable(list), { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -523,12 +524,12 @@ suite('TreeModel2', function () {
|
||||
const list = [] as ITreeNode<string>[];
|
||||
let query = new RegExp('');
|
||||
const filter = new class implements ITreeFilter<string> {
|
||||
filter(element: string): Visibility {
|
||||
return query.test(element) ? Visibility.Visible : Visibility.Recurse;
|
||||
filter(element: string): TreeVisibility {
|
||||
return query.test(element) ? TreeVisibility.Visible : TreeVisibility.Recurse;
|
||||
}
|
||||
};
|
||||
|
||||
const model = new TreeModel<string>(toSpliceable(list), { filter });
|
||||
const model = new IndexTreeModel<string>(toSpliceable(list), { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -576,7 +577,7 @@ suite('TreeModel2', function () {
|
||||
|
||||
test('simple', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeModel<number>(toSpliceable(list));
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -590,23 +591,23 @@ suite('TreeModel2', function () {
|
||||
{ element: 2 }
|
||||
]));
|
||||
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[0]), [0]);
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[1]), [0, 0]);
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[2]), [0, 1]);
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[3]), [0, 2]);
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[4]), [1]);
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[5]), [2]);
|
||||
assert.deepEqual(model.getNodeLocation(list[0]), [0]);
|
||||
assert.deepEqual(model.getNodeLocation(list[1]), [0, 0]);
|
||||
assert.deepEqual(model.getNodeLocation(list[2]), [0, 1]);
|
||||
assert.deepEqual(model.getNodeLocation(list[3]), [0, 2]);
|
||||
assert.deepEqual(model.getNodeLocation(list[4]), [1]);
|
||||
assert.deepEqual(model.getNodeLocation(list[5]), [2]);
|
||||
});
|
||||
|
||||
test('with filter', function () {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const filter = new class implements ITreeFilter<number> {
|
||||
filter(element: number): Visibility {
|
||||
return element % 2 === 0 ? Visibility.Visible : Visibility.Hidden;
|
||||
filter(element: number): TreeVisibility {
|
||||
return element % 2 === 0 ? TreeVisibility.Visible : TreeVisibility.Hidden;
|
||||
}
|
||||
};
|
||||
|
||||
const model = new TreeModel<number>(toSpliceable(list), { filter });
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -622,10 +623,10 @@ suite('TreeModel2', function () {
|
||||
}
|
||||
]));
|
||||
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[0]), [0]);
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[1]), [0, 1]);
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[2]), [0, 3]);
|
||||
assert.deepEqual(TreeModel.getNodeLocation(list[3]), [0, 5]);
|
||||
assert.deepEqual(model.getNodeLocation(list[0]), [0]);
|
||||
assert.deepEqual(model.getNodeLocation(list[1]), [0, 1]);
|
||||
assert.deepEqual(model.getNodeLocation(list[2]), [0, 3]);
|
||||
assert.deepEqual(model.getNodeLocation(list[3]), [0, 5]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ITreeNode } from 'vs/base/browser/ui/tree/treeModel';
|
||||
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { TreeObjectModel } from 'vs/base/browser/ui/tree/treeObjectModel';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
@@ -21,11 +21,11 @@ function toArray<T>(list: ITreeNode<T>[]): T[] {
|
||||
return list.map(i => i.element);
|
||||
}
|
||||
|
||||
suite('TreeObjectModel', function () {
|
||||
suite('ObjectTreeModel', function () {
|
||||
|
||||
test('ctor', () => {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeObjectModel<number>(toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>(toSpliceable(list));
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
assert.equal(model.size, 0);
|
||||
@@ -33,7 +33,7 @@ suite('TreeObjectModel', function () {
|
||||
|
||||
test('flat', () => {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeObjectModel<number>(toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{ element: 0 },
|
||||
@@ -60,7 +60,7 @@ suite('TreeObjectModel', function () {
|
||||
|
||||
test('nested', () => {
|
||||
const list = [] as ITreeNode<number>[];
|
||||
const model = new TreeObjectModel<number>(toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
require.config({ baseUrl: '/static' });
|
||||
|
||||
require(['vs/base/browser/ui/tree/tree', 'vs/base/browser/ui/tree/treeModel', 'vs/base/common/iterator'], ({ Tree }, { Visibility }, { iter }) => {
|
||||
require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { TreeVisibility }, { iter }) => {
|
||||
const delegate = {
|
||||
getHeight() { return 22; },
|
||||
getTemplateId() { return 'template'; }
|
||||
@@ -71,11 +71,11 @@
|
||||
perf('refilter', () => tree.refilter());
|
||||
}
|
||||
filter(el) {
|
||||
return (this.pattern ? this.pattern.test(el) : true) ? Visibility.Visible : Visibility.Recurse;
|
||||
return (this.pattern ? this.pattern.test(el) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse;
|
||||
}
|
||||
};
|
||||
|
||||
const tree = new Tree(container, delegate, [renderer], { filter: treeFilter });
|
||||
const tree = new IndexTree(container, delegate, [renderer], { filter: treeFilter });
|
||||
|
||||
function setModel(model) {
|
||||
performance.mark('before splice');
|
||||
|
||||
Reference in New Issue
Block a user