未验证 提交 4f399951 编写于 作者: J João Moreno 提交者: GitHub

Introduce `CompressibleObjectTree` (#77876)

Introduce `CompressibleObjectTree`
......@@ -862,7 +862,7 @@ export interface IListStyles {
}
const defaultStyles: IListStyles = {
listFocusBackground: Color.fromHex('#073655'),
listFocusBackground: Color.fromHex('#7FB0D0'),
listActiveSelectionBackground: Color.fromHex('#0E639C'),
listActiveSelectionForeground: Color.fromHex('#FFFFFF'),
listFocusAndSelectionBackground: Color.fromHex('#094771'),
......
......@@ -98,9 +98,11 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
}
if (result.bubble === TreeDragOverBubble.Up) {
const parentNode = targetNode.parent;
const model = this.modelProvider();
const parentIndex = parentNode && model.getListIndex(model.getNodeLocation(parentNode));
const ref = model.getNodeLocation(targetNode);
const parentRef = model.getParentNodeLocation(ref);
const parentNode = model.getNode(parentRef);
const parentIndex = parentRef && model.getListIndex(parentRef);
return this.onDragOver(data, parentNode, parentIndex, originalEvent, false);
}
......@@ -155,7 +157,12 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
enableKeyboardNavigation: options.simpleKeyboardNavigation,
ariaProvider: {
getSetSize(node) {
return node.parent!.visibleChildrenCount;
const model = modelProvider();
const ref = model.getNodeLocation(node);
const parentRef = model.getParentNodeLocation(ref);
const parentNode = model.getNode(parentRef);
return parentNode.visibleChildrenCount;
},
getPosInSet(node) {
return node.visibleChildIndex + 1;
......@@ -233,7 +240,7 @@ class EventCollection<T> implements Collection<T> {
}
}
class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {
class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {
private static DefaultIndent = 8;
......@@ -251,6 +258,7 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
constructor(
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
private modelProvider: () => ITreeModel<T, TFilterData, TRef>,
onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>>,
private activeNodes: Collection<ITreeNode<T, TFilterData>>,
options: ITreeRendererOptions = {}
......@@ -381,10 +389,19 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
}
const disposableStore = new DisposableStore();
const model = this.modelProvider();
let node = target;
while (node.parent && node.parent.parent) {
const parent = node.parent;
while (true) {
const ref = model.getNodeLocation(node);
const parentRef = model.getParentNodeLocation(ref);
if (!parentRef) {
break;
}
const parent = model.getNode(parentRef);
const guide = $<HTMLDivElement>('.indent-guide', { style: `width: ${this.indent}px` });
if (this.activeIndentNodes.has(parent)) {
......@@ -412,12 +429,16 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
}
const set = new Set<ITreeNode<T, TFilterData>>();
const model = this.modelProvider();
nodes.forEach(node => {
const ref = model.getNodeLocation(node);
const parentRef = model.getParentNodeLocation(ref);
if (node.collapsible && node.children.length > 0 && !node.collapsed) {
set.add(node);
} else if (node.parent) {
set.add(node.parent);
} else if (parentRef) {
set.add(model.getNode(parentRef));
}
});
......@@ -1153,7 +1174,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable {
protected view: TreeNodeList<T, TFilterData, TRef>;
private renderers: TreeRenderer<T, TFilterData, any>[];
private renderers: TreeRenderer<T, TFilterData, TRef, any>[];
protected model: ITreeModel<T, TFilterData, TRef>;
private focus: Trait<T>;
private selection: Trait<T>;
......@@ -1211,7 +1232,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
const activeNodes = new EventCollection(onDidChangeActiveNodes.event);
this.disposables.push(activeNodes);
this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, any>(r, onDidChangeCollapseStateRelay.event, activeNodes, _options));
this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, TRef, any>(r, () => this.model, onDidChangeCollapseStateRelay.event, activeNodes, _options));
this.disposables.push(...this.renderers);
let filter: TypeFilter<T> | undefined;
......@@ -1383,7 +1404,9 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
// Tree navigation
getParentElement(location: TRef): T {
return this.model.getParentElement(location);
const parentRef = this.model.getParentNodeLocation(location);
const parentNode = this.model.getNode(parentRef);
return parentNode.element;
}
getFirstElementChild(location: TRef): T | undefined {
......@@ -1539,7 +1562,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
if (!didChange) {
const parentLocation = this.model.getParentNodeLocation(location);
if (parentLocation === null) {
if (!parentLocation) {
return;
}
......@@ -1641,22 +1664,6 @@ class TreeNavigator<T extends NonNullable<any>, TFilterData, TRef> implements IT
return this.current();
}
parent(): T | null {
if (this.index < 0 || this.index >= this.view.length) {
return null;
}
const node = this.view.element(this.index);
if (!node.parent) {
this.index = -1;
return this.current();
}
this.index = this.model.getListIndex(this.model.getNodeLocation(node.parent));
return this.current();
}
first(): T | null {
this.index = 0;
return this.current();
......
......@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError } from 'vs/base/browser/ui/tree/tree';
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
......@@ -18,6 +18,7 @@ import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors
import { toggleClass } from 'vs/base/browser/dom';
import { values } from 'vs/base/common/map';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
interface IAsyncDataTreeNode<TInput, T> {
element: TInput | T;
......@@ -66,10 +67,11 @@ interface IDataTreeListTemplateData<T> {
templateData: T;
}
type AsyncDataTreeNodeMapper<TInput, T, TFilterData> = WeakMapper<ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>, ITreeNode<TInput | T, TFilterData>>;
class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInput | T, TFilterData> {
get element(): T { return this.node.element!.element as T; }
get parent(): ITreeNode<T, TFilterData> | undefined { return this.node.parent && new AsyncDataTreeNodeWrapper(this.node.parent); }
get children(): ITreeNode<T, TFilterData>[] { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); }
get depth(): number { return this.node.depth; }
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
......@@ -82,14 +84,15 @@ class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInp
constructor(private node: ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>) { }
}
class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
readonly templateId: string;
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
private disposables: IDisposable[] = [];
constructor(
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
protected renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
protected nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData>,
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
) {
this.templateId = renderer.templateId;
......@@ -101,7 +104,7 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
}
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
this.renderer.renderElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
......@@ -111,7 +114,7 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
}
......@@ -243,25 +246,6 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
};
}
function asTreeElement<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
let collapsed: boolean | undefined;
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
collapsed = false;
} else {
collapsed = node.collapsedByDefault;
}
node.collapsedByDefault = undefined;
return {
element: node,
children: node.hasChildren ? Iterator.map(Iterator.fromArray(node.children), child => asTreeElement(child, viewStateContext)) : [],
collapsible: node.hasChildren,
collapsed
};
}
export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { }
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptionsUpdate, Pick<IAbstractTreeOptions<T, TFilterData>, Exclude<keyof IAbstractTreeOptions<T, TFilterData>, 'collapseByDefault'>> {
......@@ -304,7 +288,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
private readonly autoExpandSingleChildren: boolean;
private readonly _onDidRender = new Emitter<void>();
private readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
protected readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
protected readonly nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new AsyncDataTreeNodeWrapper(node));
protected readonly disposables: IDisposable[] = [];
......@@ -351,11 +337,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.sorter = options.sorter;
this.collapseByDefault = options.collapseByDefault;
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeSlowState.event));
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
this.tree = new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
this.tree = this.createTree(user, container, delegate, renderers, options);
this.root = createAsyncDataTreeNode({
element: undefined!,
......@@ -375,6 +357,20 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables);
}
protected createTree(
user: string,
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<T, TFilterData, any>[],
options: IAsyncDataTreeOptions<T, TFilterData>
): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
const objectTreeRenderers = renderers.map(r => new AsyncDataTreeRenderer(r, this.nodeMapper, this._onDidChangeNodeSlowState.event));
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
return new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
}
updateOptions(options: IAsyncDataTreeOptionsUpdate = {}): void {
this.tree.updateOptions(options);
}
......@@ -510,7 +506,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
getNode(element: TInput | T = this.root.element): ITreeNode<TInput | T, TFilterData> {
const dataNode = this.getDataNode(element);
const node = this.tree.getNode(dataNode === this.root ? null : dataNode);
return new AsyncDataTreeNodeWrapper<TInput, T, TFilterData>(node);
return this.nodeMapper.map(node);
}
collapse(element: T, recursive: boolean = false): boolean {
......@@ -876,7 +872,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
}
private render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
const children = node.children.map(c => asTreeElement(c, viewStateContext));
const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
this.tree.setChildren(node === this.root ? null : node, children);
if (node !== this.root) {
......@@ -886,6 +882,25 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this._onDidRender.fire();
}
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
let collapsed: boolean | undefined;
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
collapsed = false;
} else {
collapsed = node.collapsedByDefault;
}
node.collapsedByDefault = undefined;
return {
element: node,
children: node.hasChildren ? Iterator.map(Iterator.fromArray(node.children), child => this.asTreeElement(child, viewStateContext)) : [],
collapsible: node.hasChildren,
collapsed
};
}
// view state
getViewState(): IAsyncDataTreeViewState {
......@@ -918,3 +933,117 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
dispose(this.disposables);
}
}
type CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = WeakMapper<ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData>>;
class CompressibleAsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData> {
get element(): ICompressedTreeNode<TInput | T> {
return {
elements: this.node.element.elements.map(e => e.element),
incompressible: this.node.element.incompressible
};
}
get children(): ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData>[] { return this.node.children.map(node => new CompressibleAsyncDataTreeNodeWrapper(node)); }
get depth(): number { return this.node.depth; }
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
get visibleChildIndex(): number { return this.node.visibleChildIndex; }
get collapsible(): boolean { return this.node.collapsible; }
get collapsed(): boolean { return this.node.collapsed; }
get visible(): boolean { return this.node.visible; }
get filterData(): TFilterData | undefined { return this.node.filterData; }
constructor(private node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>) { }
}
class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ICompressibleTreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
readonly templateId: string;
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
private disposables: IDisposable[] = [];
constructor(
protected renderer: ICompressibleTreeRenderer<T, TFilterData, TTemplateData>,
protected nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData>,
private compressibleNodeMapperProvider: () => CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData>,
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
) {
this.templateId = renderer.templateId;
}
renderTemplate(container: HTMLElement): IDataTreeListTemplateData<TTemplateData> {
const templateData = this.renderer.renderTemplate(container);
return { templateData };
}
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
this.renderer.renderElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
this.renderer.renderCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
}
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
toggleClass(twistieElement, 'loading', element.slow);
return false;
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
this.renderer.disposeTemplate(templateData.templateData);
}
dispose(): void {
this.renderedNodes.clear();
this.disposables = dispose(this.disposables);
}
}
export interface ITreeCompressionDelegate<T> {
isIncompressible(element: T): boolean;
}
export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
constructor(
user: string,
container: HTMLElement,
virtualDelegate: IListVirtualDelegate<T>,
private compressionDelegate: ITreeCompressionDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
dataSource: IAsyncDataSource<TInput, T>,
options: IAsyncDataTreeOptions<T, TFilterData> = {}
) {
super(user, container, virtualDelegate, renderers, dataSource, options);
}
protected createTree(
user: string,
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
options: IAsyncDataTreeOptions<T, TFilterData>
): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
const objectTreeRenderers = renderers.map(r => new CompressibleAsyncDataTreeRenderer(r, this.nodeMapper, () => this.compressibleNodeMapper, this._onDidChangeNodeSlowState.event));
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
}
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ICompressedTreeElement<IAsyncDataTreeNode<TInput, T>> {
return {
incompressible: this.compressionDelegate.isIncompressible(node.element as T),
...super.asTreeElement(node, viewStateContext)
};
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Iterator, ISequence } from 'vs/base/common/iterator';
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ISpliceable } from 'vs/base/common/sequence';
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { Event } from 'vs/base/common/event';
import { CompressedTreeModel, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
sorter?: ITreeSorter<T>;
}
export class CompressedObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<ICompressedTreeNode<T> | null, TFilterData, T | null> {
protected model!: CompressedTreeModel<T, TFilterData>;
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<ICompressedTreeNode<T> | null, TFilterData>> { return this.model.onDidChangeCollapseState; }
constructor(
user: string,
container: HTMLElement,
delegate: IListVirtualDelegate<ICompressedTreeNode<T>>,
renderers: ITreeRenderer<ICompressedTreeNode<T>, TFilterData, any>[],
options: IObjectTreeOptions<ICompressedTreeNode<T>, TFilterData> = {}
) {
super(user, container, delegate, renderers, options);
}
setChildren(
element: T | null,
children?: ISequence<ITreeElement<T>>
): Iterator<ITreeElement<T | null>> {
return this.model.setChildren(element, children);
}
rerender(element?: T): void {
if (element === undefined) {
this.view.rerender();
return;
}
this.model.rerender(element);
}
resort(element: T, recursive = true): void {
this.model.resort(element, recursive);
}
protected createModel(user: string, view: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>, options: IObjectTreeOptions<ICompressedTreeNode<T>, TFilterData>): ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
return new CompressedTreeModel(user, view, options);
}
}
......@@ -6,19 +6,32 @@
import { ISpliceable } from 'vs/base/common/sequence';
import { Iterator, ISequence } from 'vs/base/common/iterator';
import { Event } from 'vs/base/common/event';
import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
// Exported only for test reasons, do not use directly
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
readonly children?: Iterator<ICompressedTreeElement<T>> | ICompressedTreeElement<T>[];
readonly incompressible?: boolean;
}
// Exported only for test reasons, do not use directly
export interface ICompressedTreeNode<T> {
readonly elements: T[];
readonly incompressible: boolean;
}
function noCompress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> {
const elements = [element.element];
const incompressible = element.incompressible || false;
return {
element: { elements, incompressible },
children: Iterator.map(Iterator.from(element.children), noCompress)
};
}
// Exported only for test reasons, do not use directly
export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> {
const elements = [element.element];
const incompressible = element.incompressible || false;
......@@ -49,7 +62,7 @@ export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<IC
};
}
export function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> {
function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> {
let children: Iterator<ICompressedTreeElement<T>>;
if (index < element.element.elements.length - 1) {
......@@ -65,11 +78,12 @@ export function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, in
return { element: element.element.elements[index], children };
}
// Exported only for test reasons, do not use directly
export function decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>): ICompressedTreeElement<T> {
return _decompress(element, 0);
}
export function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, children: Iterator<ICompressedTreeElement<T>>): ICompressedTreeElement<T> {
function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, children: Iterator<ICompressedTreeElement<T>>): ICompressedTreeElement<T> {
if (treeElement.element === element) {
return { element, children };
}
......@@ -80,9 +94,10 @@ export function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, ch
};
}
export interface ICompressedTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
// Exported only for test reasons, do not use directly
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
readonly rootRef = null;
......@@ -92,39 +107,77 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
private model: ObjectTreeModel<ICompressedTreeNode<T>, TFilterData>;
private nodes = new Map<T | null, ICompressedTreeNode<T>>();
private enabled: boolean = true;
get size(): number { return this.nodes.size; }
constructor(
private user: string,
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
options: ICompressedTreeModelOptions<T, TFilterData> = {}
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
) {
this.model = new ObjectTreeModel(user, list, options);
}
setChildren(
element: T | null,
children: ISequence<ICompressedTreeElement<T>> | undefined,
onDidCreateNode?: (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => void,
onDidDeleteNode?: (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => void
): Iterator<ITreeElement<T | null>> {
children: ISequence<ICompressedTreeElement<T>> | undefined
): void {
if (element === null) {
const compressedChildren = Iterator.map(Iterator.from(children), this.enabled ? compress : noCompress);
this._setChildren(null, compressedChildren);
return;
}
const compressedNode = this.nodes.get(element);
if (!compressedNode) {
throw new Error('Unknown compressed tree node');
}
const node = this.model.getNode(compressedNode) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
const compressedParentNode = this.model.getParentNodeLocation(compressedNode);
const parent = this.model.getNode(compressedParentNode) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
const decompressedElement = decompress(node);
const splicedElement = splice(decompressedElement, element, Iterator.from(children));
const recompressedElement = (this.enabled ? compress : noCompress)(splicedElement);
const parentChildren = parent.children
.map(child => child === node ? recompressedElement : child);
this._setChildren(parent.element, parentChildren);
}
isCompressionEnabled(): boolean {
return this.enabled;
}
setCompressionEnabled(enabled: boolean): void {
if (enabled === this.enabled) {
return;
}
this.enabled = enabled;
const root = this.model.getNode();
const rootChildren = Iterator.from(root.children as ITreeNode<ICompressedTreeNode<T>>[]);
const decompressedRootChildren = Iterator.map(rootChildren, decompress);
const recompressedRootChildren = Iterator.map(decompressedRootChildren, enabled ? compress : noCompress);
this._setChildren(null, recompressedRootChildren);
}
private _setChildren(
node: ICompressedTreeNode<T> | null,
children: ISequence<ITreeElement<ICompressedTreeNode<T>>> | undefined
): void {
const insertedElements = new Set<T | null>();
const _onDidCreateNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
for (const element of node.element.elements) {
insertedElements.add(element);
this.nodes.set(element, node.element);
}
// if (this.identityProvider) {
// const id = this.identityProvider.getId(node.element).toString();
// insertedElementIds.add(id);
// this.nodesByIdentity.set(id, node);
// }
if (onDidCreateNode) {
onDidCreateNode(node);
}
};
const _onDidDeleteNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
......@@ -133,40 +186,9 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
this.nodes.delete(element);
}
}
// if (this.identityProvider) {
// const id = this.identityProvider.getId(node.element).toString();
// if (!insertedElementIds.has(id)) {
// this.nodesByIdentity.delete(id);
// }
// }
if (onDidDeleteNode) {
onDidDeleteNode(node);
}
};
if (element === null) {
const compressedChildren = Iterator.map(Iterator.from(children), compress);
const result = this.model.setChildren(null, compressedChildren, _onDidCreateNode, _onDidDeleteNode);
return Iterator.map(result, decompress);
}
const compressedNode = this.nodes.get(element);
const node = this.model.getNode(compressedNode) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
const parent = node.parent!;
const decompressedElement = decompress(node);
const splicedElement = splice(decompressedElement, element, Iterator.from(children));
const recompressedElement = compress(splicedElement);
const parentChildren = parent.children
.map(child => child === node ? recompressedElement : child);
this.model.setChildren(parent.element, parentChildren, _onDidCreateNode, _onDidDeleteNode);
// TODO
return Iterator.empty();
this.model.setChildren(node, children, _onDidCreateNode, _onDidDeleteNode);
}
getListIndex(location: T | null): number {
......@@ -211,11 +233,6 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
return parentNode.elements[parentNode.elements.length - 1];
}
getParentElement(location: T | null): ICompressedTreeNode<T> | null {
const compressedNode = this.getCompressedNode(location);
return this.model.getParentElement(compressedNode);
}
getFirstElementChild(location: T | null): ICompressedTreeNode<T> | null | undefined {
const compressedNode = this.getCompressedNode(location);
return this.model.getFirstElementChild(compressedNode);
......@@ -265,7 +282,7 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
this.model.resort(compressedNode, recursive);
}
private getCompressedNode(element: T | null): ICompressedTreeNode<T> | null {
getCompressedNode(element: T | null): ICompressedTreeNode<T> | null {
if (element === null) {
return null;
}
......@@ -280,72 +297,113 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
}
}
// Compressible Object Tree
export type ElementMapper<T> = (elements: T[]) => T;
export const DefaultElementMapper: ElementMapper<any> = elements => elements[elements.length - 1];
export type NodeMapper<T, TFilterData> = (node: ITreeNode<ICompressedTreeNode<T> | null, TFilterData>) => ITreeNode<T | null, TFilterData>;
export type CompressedNodeUnwrapper<T> = (node: ICompressedTreeNode<T>) => T;
type CompressedNodeWeakMapper<T, TFilterData> = WeakMapper<ITreeNode<ICompressedTreeNode<T> | null, TFilterData>, ITreeNode<T | null, TFilterData>>;
class CompressedTreeNodeWrapper<T, TFilterData> implements ITreeNode<T | null, TFilterData> {
get element(): T | null { return this.node.element === null ? null : this.unwrapper(this.node.element); }
get children(): ITreeNode<T | null, TFilterData>[] { return this.node.children.map(node => new CompressedTreeNodeWrapper(this.unwrapper, node)); }
get depth(): number { return this.node.depth; }
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
get visibleChildIndex(): number { return this.node.visibleChildIndex; }
get collapsible(): boolean { return this.node.collapsible; }
get collapsed(): boolean { return this.node.collapsed; }
get visible(): boolean { return this.node.visible; }
get filterData(): TFilterData | undefined { return this.node.filterData; }
function mapNode<T, TFilterData>(elementMapper: ElementMapper<T>, node: ITreeNode<ICompressedTreeNode<T> | null, TFilterData>): ITreeNode<T | null, TFilterData> {
constructor(
private unwrapper: CompressedNodeUnwrapper<T>,
private node: ITreeNode<ICompressedTreeNode<T> | null, TFilterData>
) { }
}
function mapList<T, TFilterData>(nodeMapper: CompressedNodeWeakMapper<T, TFilterData>, list: ISpliceable<ITreeNode<T, TFilterData>>): ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>> {
return {
...node,
element: node.element === null ? null : elementMapper(node.element.elements),
children: node.children.map(child => mapNode(elementMapper, child)),
parent: typeof node.parent === 'undefined' ? node.parent : mapNode(elementMapper, node.parent)
splice(start: number, deleteCount: number, toInsert: ITreeNode<ICompressedTreeNode<T>, TFilterData>[]): void {
list.splice(start, deleteCount, toInsert.map(node => nodeMapper.map(node)) as ITreeNode<T, TFilterData>[]);
}
};
}
function createNodeMapper<T, TFilterData>(elementMapper: ElementMapper<T>): NodeMapper<T, TFilterData> {
return node => mapNode(elementMapper, node);
function mapOptions<T, TFilterData>(compressedNodeUnwrapper: CompressedNodeUnwrapper<T>, options: ICompressibleObjectTreeModelOptions<T, TFilterData>): ICompressedObjectTreeModelOptions<T, TFilterData> {
return {
...options,
sorter: options.sorter && {
compare(node: ICompressedTreeNode<T>, otherNode: ICompressedTreeNode<T>): number {
return options.sorter!.compare(compressedNodeUnwrapper(node), compressedNodeUnwrapper(otherNode));
}
},
identityProvider: options.identityProvider && {
getId(node: ICompressedTreeNode<T>): { toString(): string; } {
return options.identityProvider!.getId(compressedNodeUnwrapper(node));
}
},
filter: options.filter && {
filter(node: ICompressedTreeNode<T>, parentVisibility: TreeVisibility): TreeFilterResult<TFilterData> {
return options.filter!.filter(compressedNodeUnwrapper(node), parentVisibility);
}
}
};
}
export interface ICompressedObjectTreeModelOptions<T, TFilterData> extends ICompressedTreeModelOptions<T, TFilterData> {
export interface ICompressibleObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<T, TFilterData> {
readonly elementMapper?: ElementMapper<T>;
}
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements IObjectTreeModel<T, TFilterData> {
export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements IObjectTreeModel<T, TFilterData> {
readonly rootRef = null;
get onDidSplice(): Event<ITreeModelSpliceEvent<T | null, TFilterData>> {
return Event.map(this.model.onDidSplice, ({ insertedNodes, deletedNodes }) => ({
insertedNodes: insertedNodes.map(this.mapNode),
deletedNodes: deletedNodes.map(this.mapNode),
insertedNodes: insertedNodes.map(node => this.nodeMapper.map(node)),
deletedNodes: deletedNodes.map(node => this.nodeMapper.map(node)),
}));
}
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T | null, TFilterData>> {
return Event.map(this.model.onDidChangeCollapseState, ({ node, deep }) => ({
node: this.mapNode(node),
node: this.nodeMapper.map(node),
deep
}));
}
get onDidChangeRenderNodeCount(): Event<ITreeNode<T | null, TFilterData>> {
return Event.map(this.model.onDidChangeRenderNodeCount, this.mapNode);
return Event.map(this.model.onDidChangeRenderNodeCount, node => this.nodeMapper.map(node));
}
private mapElement: ElementMapper<T | null>;
private mapNode: NodeMapper<T | null, TFilterData>;
private model: CompressedTreeModel<T, TFilterData>;
private elementMapper: ElementMapper<T>;
private nodeMapper: CompressedNodeWeakMapper<T, TFilterData>;
private model: CompressedObjectTreeModel<T, TFilterData>;
constructor(
user: string,
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
list: ISpliceable<ITreeNode<T, TFilterData>>,
options: ICompressibleObjectTreeModelOptions<T, TFilterData> = {}
) {
this.mapElement = options.elementMapper || DefaultElementMapper;
this.mapNode = createNodeMapper(this.mapElement);
this.model = new CompressedTreeModel(user, list, options);
this.elementMapper = options.elementMapper || DefaultElementMapper;
const compressedNodeUnwrapper: CompressedNodeUnwrapper<T> = node => this.elementMapper(node.elements);
this.nodeMapper = new WeakMapper(node => new CompressedTreeNodeWrapper(compressedNodeUnwrapper, node));
this.model = new CompressedObjectTreeModel(user, mapList(this.nodeMapper, list), mapOptions(compressedNodeUnwrapper, options));
}
setChildren(
element: T | null,
children: ISequence<ITreeElement<T>> | undefined
): Iterator<ITreeElement<T>> {
setChildren(element: T | null, children?: ISequence<ICompressedTreeElement<T>>): void {
this.model.setChildren(element, children);
}
// TODO
return Iterator.empty();
isCompressionEnabled(): boolean {
return this.model.isCompressionEnabled();
}
setCompressionEnabled(enabled: boolean): void {
this.model.setCompressionEnabled(enabled);
}
getListIndex(location: T | null): number {
......@@ -357,7 +415,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
}
getNode(location?: T | null | undefined): ITreeNode<T | null, any> {
return this.mapNode(this.model.getNode(location));
return this.nodeMapper.map(this.model.getNode(location));
}
getNodeLocation(node: ITreeNode<T | null, any>): T | null {
......@@ -368,16 +426,6 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
return this.model.getParentNodeLocation(location);
}
getParentElement(location: T | null): T | null {
const result = this.model.getParentElement(location);
if (result === null) {
return result;
}
return this.mapElement(result.elements);
}
getFirstElementChild(location: T | null): T | null | undefined {
const result = this.model.getFirstElementChild(location);
......@@ -385,7 +433,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
return result;
}
return this.mapElement(result.elements);
return this.elementMapper(result.elements);
}
getLastElementAncestor(location?: T | null | undefined): T | null | undefined {
......@@ -395,7 +443,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
return result;
}
return this.mapElement(result.elements);
return this.elementMapper(result.elements);
}
isCollapsible(location: T | null): boolean {
......@@ -429,4 +477,8 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
resort(element: T | null = null, recursive = true): void {
return this.model.resort(element, recursive);
}
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
return this.model.getNode(element) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
}
}
......@@ -28,8 +28,8 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
super(user, container, delegate, renderers, options);
}
splice(location: number[], deleteCount: number, toInsert: ISequence<ITreeElement<T>> = Iterator.empty()): Iterator<ITreeElement<T>> {
return this.model.splice(location, deleteCount, toInsert);
splice(location: number[], deleteCount: number, toInsert: ISequence<ITreeElement<T>> = Iterator.empty()): void {
this.model.splice(location, deleteCount, toInsert);
}
rerender(location?: number[]): void {
......
......@@ -9,9 +9,10 @@ import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
import { ISequence, Iterator } from 'vs/base/common/iterator';
import { ISpliceable } from 'vs/base/common/sequence';
interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
readonly parent: IMutableTreeNode<T, TFilterData> | undefined;
readonly children: IMutableTreeNode<T, TFilterData>[];
// Exported for tests
export interface IIndexTreeNode<T, TFilterData = void> extends ITreeNode<T, TFilterData> {
readonly parent: IIndexTreeNode<T, TFilterData> | undefined;
readonly children: IIndexTreeNode<T, TFilterData>[];
visibleChildrenCount: number;
visibleChildIndex: number;
collapsible: boolean;
......@@ -33,13 +34,6 @@ export function getVisibleState(visibility: boolean | TreeVisibility): TreeVisib
}
}
function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
const { element, collapsed } = node;
const children = Iterator.map(Iterator.fromArray(node.children), treeNodeToElement);
return { element, children, collapsed };
}
export interface IIndexTreeModelOptions<T, TFilterData> {
readonly collapseByDefault?: boolean; // defaults to false
readonly filter?: ITreeFilter<T, TFilterData>;
......@@ -65,7 +59,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
readonly rootRef = [];
private root: IMutableTreeNode<T, TFilterData>;
private root: IIndexTreeNode<T, TFilterData>;
private eventBufferer = new EventBufferer();
private _onDidChangeCollapseState = new Emitter<ICollapseStateChangeEvent<T, TFilterData>>();
......@@ -112,7 +106,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
toInsert?: ISequence<ITreeElement<T>>,
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
): Iterator<ITreeElement<T>> {
): void {
if (location.length === 0) {
throw new TreeError(this.user, 'Invalid tree location');
}
......@@ -136,7 +130,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
}
const nodesToInsert: IMutableTreeNode<T, TFilterData>[] = [];
const nodesToInsert: IIndexTreeNode<T, TFilterData>[] = [];
let insertedVisibleChildrenCount = 0;
let renderNodeCount = 0;
......@@ -190,9 +184,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
deletedNodes.forEach(visit);
}
const result = Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes });
return result;
}
rerender(location: number[]): void {
......@@ -275,7 +267,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return result;
}
private _setListNodeCollapseState(node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, update: CollapseStateUpdate): boolean {
private _setListNodeCollapseState(node: IIndexTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, update: CollapseStateUpdate): boolean {
const result = this._setNodeCollapseState(node, update, false);
if (!revealed || !node.visible || !result) {
......@@ -290,7 +282,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return result;
}
private _setNodeCollapseState(node: IMutableTreeNode<T, TFilterData>, update: CollapseStateUpdate, deep: boolean): boolean {
private _setNodeCollapseState(node: IIndexTreeNode<T, TFilterData>, update: CollapseStateUpdate, deep: boolean): boolean {
let result: boolean;
if (node === this.root) {
......@@ -341,13 +333,13 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
private createTreeNode(
treeElement: ITreeElement<T>,
parent: IMutableTreeNode<T, TFilterData>,
parent: IIndexTreeNode<T, TFilterData>,
parentVisibility: TreeVisibility,
revealed: boolean,
treeListElements: ITreeNode<T, TFilterData>[],
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void
): IMutableTreeNode<T, TFilterData> {
const node: IMutableTreeNode<T, TFilterData> = {
): IIndexTreeNode<T, TFilterData> {
const node: IIndexTreeNode<T, TFilterData> = {
parent,
element: treeElement.element,
children: [],
......@@ -404,7 +396,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return node;
}
private updateNodeAfterCollapseChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
private updateNodeAfterCollapseChange(node: IIndexTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
const previousRenderNodeCount = node.renderNodeCount;
const result: ITreeNode<T, TFilterData>[] = [];
......@@ -414,7 +406,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return result;
}
private _updateNodeAfterCollapseChange(node: IMutableTreeNode<T, TFilterData>, result: ITreeNode<T, TFilterData>[]): number {
private _updateNodeAfterCollapseChange(node: IIndexTreeNode<T, TFilterData>, result: ITreeNode<T, TFilterData>[]): number {
if (node.visible === false) {
return 0;
}
......@@ -432,7 +424,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return node.renderNodeCount;
}
private updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
private updateNodeAfterFilterChange(node: IIndexTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
const previousRenderNodeCount = node.renderNodeCount;
const result: ITreeNode<T, TFilterData>[] = [];
......@@ -442,7 +434,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return result;
}
private _updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>, parentVisibility: TreeVisibility, result: ITreeNode<T, TFilterData>[], revealed = true): boolean {
private _updateNodeAfterFilterChange(node: IIndexTreeNode<T, TFilterData>, parentVisibility: TreeVisibility, result: ITreeNode<T, TFilterData>[], revealed = true): boolean {
let visibility: TreeVisibility;
if (node !== this.root) {
......@@ -496,7 +488,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return node.visible;
}
private _updateAncestorsRenderNodeCount(node: IMutableTreeNode<T, TFilterData> | undefined, diff: number): void {
private _updateAncestorsRenderNodeCount(node: IIndexTreeNode<T, TFilterData> | undefined, diff: number): void {
if (diff === 0) {
return;
}
......@@ -508,7 +500,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
}
private _filterNode(node: IMutableTreeNode<T, TFilterData>, parentVisibility: TreeVisibility): TreeVisibility {
private _filterNode(node: IIndexTreeNode<T, TFilterData>, parentVisibility: TreeVisibility): TreeVisibility {
const result = this.filter ? this.filter.filter(node.element, parentVisibility) : TreeVisibility.Visible;
if (typeof result === 'boolean') {
......@@ -524,7 +516,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
// cheap
private getTreeNode(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root): IMutableTreeNode<T, TFilterData> {
private getTreeNode(location: number[], node: IIndexTreeNode<T, TFilterData> = this.root): IIndexTreeNode<T, TFilterData> {
if (!location || location.length === 0) {
return node;
}
......@@ -539,7 +531,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
// expensive
private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, visible: boolean } {
private getTreeNodeWithListIndex(location: number[]): { node: IIndexTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, visible: boolean } {
if (location.length === 0) {
return { node: this.root, listIndex: -1, revealed: true, visible: false };
}
......@@ -556,7 +548,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return { node, listIndex, revealed, visible: visible && node.visible };
}
private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true, visible = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; visible: boolean; } {
private getParentNodeWithListIndex(location: number[], node: IIndexTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true, visible = true): { parentNode: IIndexTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; visible: boolean; } {
const [index, ...rest] = location;
if (index < 0 || index > node.children.length) {
......@@ -585,27 +577,24 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
// TODO@joao perf!
getNodeLocation(node: ITreeNode<T, TFilterData>): number[] {
const location: number[] = [];
let indexTreeNode = node as IIndexTreeNode<T, TFilterData>; // typing woes
while (node.parent) {
location.push(node.parent.children.indexOf(node));
node = node.parent;
while (indexTreeNode.parent) {
location.push(indexTreeNode.parent.children.indexOf(indexTreeNode));
indexTreeNode = indexTreeNode.parent;
}
return location.reverse();
}
getParentNodeLocation(location: number[]): number[] {
if (location.length <= 1) {
getParentNodeLocation(location: number[]): number[] | undefined {
if (location.length === 0) {
return undefined;
} else if (location.length === 1) {
return [];
} else {
return tail2(location)[0];
}
return tail2(location)[0];
}
getParentElement(location: number[]): T {
const parentLocation = this.getParentNodeLocation(location);
const node = this.getTreeNode(parentLocation);
return node.element;
}
getFirstElementChild(location: number[]): T | undefined {
......
......@@ -3,13 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Iterator, ISequence } from 'vs/base/common/iterator';
import { ISequence } from 'vs/base/common/iterator';
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ISpliceable } from 'vs/base/common/sequence';
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { Event } from 'vs/base/common/event';
import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
sorter?: ITreeSorter<T>;
......@@ -31,11 +32,8 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
super(user, container, delegate, renderers, options);
}
setChildren(
element: T | null,
children?: ISequence<ITreeElement<T>>
): Iterator<ITreeElement<T | null>> {
return this.model.setChildren(element, children);
setChildren(element: T | null, children?: ISequence<ITreeElement<T>>): void {
this.model.setChildren(element, children);
}
rerender(element?: T): void {
......@@ -55,3 +53,113 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
return new ObjectTreeModel(user, view, options);
}
}
interface ICompressedTreeNodeProvider<T, TFilterData> {
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData>;
}
export interface ICompressibleObjectTreeOptions<T, TFilterData = void> extends IObjectTreeOptions<T, TFilterData> {
readonly elementMapper?: ElementMapper<T>;
}
export interface ICompressibleTreeRenderer<T, TFilterData = void, TTemplateData = void> extends ITreeRenderer<T, TFilterData, TTemplateData> {
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<T>, TFilterData>, index: number, templateData: TTemplateData, height: number | undefined): void;
disposeCompressedElements?(node: ITreeNode<ICompressedTreeNode<T>, TFilterData>, index: number, templateData: TTemplateData, height: number | undefined): void;
}
interface CompressibleTemplateData<T, TFilterData, TTemplateData> {
compressedTreeNode: ITreeNode<ICompressedTreeNode<T>, TFilterData> | undefined;
readonly data: TTemplateData;
}
class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<T, TFilterData, CompressibleTemplateData<T, TFilterData, TTemplateData>> {
readonly templateId: string;
readonly onDidChangeTwistieState: Event<T> | undefined;
compressedTreeNodeProvider: ICompressedTreeNodeProvider<T, TFilterData>;
constructor(private renderer: ICompressibleTreeRenderer<T, TFilterData, TTemplateData>) {
this.templateId = renderer.templateId;
if (renderer.onDidChangeTwistieState) {
this.onDidChangeTwistieState = renderer.onDidChangeTwistieState;
}
}
renderTemplate(container: HTMLElement): CompressibleTemplateData<T, TFilterData, TTemplateData> {
const data = this.renderer.renderTemplate(container);
return { compressedTreeNode: undefined, data };
}
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element);
if (compressedTreeNode.element.elements.length === 1) {
templateData.compressedTreeNode = undefined;
this.renderer.renderElement(node, index, templateData.data, height);
} else {
templateData.compressedTreeNode = compressedTreeNode;
this.renderer.renderCompressedElements(compressedTreeNode, index, templateData.data, height);
}
}
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
if (templateData.compressedTreeNode) {
if (this.renderer.disposeCompressedElements) {
this.renderer.disposeCompressedElements(templateData.compressedTreeNode, index, templateData.data, height);
}
} else {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(node, index, templateData.data, height);
}
}
}
disposeTemplate(templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>): void {
this.renderer.disposeTemplate(templateData.data);
}
renderTwistie?(element: T, twistieElement: HTMLElement): void {
if (this.renderer.renderTwistie) {
this.renderer.renderTwistie(element, twistieElement);
}
}
}
export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> {
protected model: CompressibleObjectTreeModel<T, TFilterData>;
constructor(
user: string,
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
options: IObjectTreeOptions<T, TFilterData> = {}
) {
const compressibleRenderers = renderers.map(r => new CompressibleRenderer(r));
super(user, container, delegate, compressibleRenderers, options);
compressibleRenderers.forEach(r => r.compressedTreeNodeProvider = this);
}
setChildren(element: T | null, children?: ISequence<ICompressedTreeElement<T>>): void {
this.model.setChildren(element, children);
}
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: ICompressibleObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
return new CompressibleObjectTreeModel(user, view, options);
}
isCompressionEnabled(): boolean {
return this.model.isCompressionEnabled();
}
setCompressionEnabled(enabled: boolean): void {
this.model.setCompressionEnabled(enabled);
}
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
return this.model.getCompressedTreeNode(element)!;
}
}
......@@ -13,7 +13,7 @@ import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void;
export interface IObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> extends ITreeModel<T | null, TFilterData, T | null> {
setChildren(element: T | null, children: ISequence<ITreeElement<T>> | undefined): Iterator<ITreeElement<T>>;
setChildren(element: T | null, children: ISequence<ITreeElement<T>> | undefined): void;
resort(element?: T | null, recursive?: boolean): void;
}
......@@ -64,9 +64,9 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
children: ISequence<ITreeElement<T>> | undefined,
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
): Iterator<ITreeElement<T>> {
): void {
const location = this.getElementLocation(element);
return this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
}
private _setChildren(
......@@ -74,7 +74,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
children: ISequence<ITreeElement<T>> | undefined,
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
): Iterator<ITreeElement<T>> {
): void {
const insertedElements = new Set<T | null>();
const insertedElementIds = new Set<string>();
......@@ -110,15 +110,13 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
}
};
const result = this.model.splice(
this.model.splice(
[...location, 0],
Number.MAX_VALUE,
children,
_onDidCreateNode,
_onDidDeleteNode
);
return result as Iterator<ITreeElement<T>>;
}
private preserveCollapseState(elements: ISequence<ITreeElement<T>> | undefined): ISequence<ITreeElement<T>> {
......@@ -186,11 +184,6 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
}));
}
getParentElement(ref: T | null = null): T | null {
const location = this.getElementLocation(ref);
return this.model.getParentElement(location);
}
getFirstElementChild(ref: T | null = null): T | null | undefined {
const location = this.getElementLocation(ref);
return this.model.getFirstElementChild(location);
......@@ -263,13 +256,12 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
throw new TreeError(this.user, `Invalid getParentNodeLocation call`);
}
const node = this.nodes.get(element);
if (!node) {
throw new TreeError(this.user, `Tree element not found: ${element}`);
}
const node = this.nodes.get(element)!;
const location = this.model.getNodeLocation(node);
const parentLocation = this.model.getParentNodeLocation(location);
const parent = this.model.getNode(parentLocation);
return node.parent!.element;
return parent.element;
}
private getElementLocation(element: T | null): number[] {
......
......@@ -81,7 +81,6 @@ export interface ITreeElement<T> {
export interface ITreeNode<T, TFilterData = void> {
readonly element: T;
readonly parent: ITreeNode<T, TFilterData> | undefined;
readonly children: ITreeNode<T, TFilterData>[];
readonly depth: number;
readonly visibleChildrenCount: number;
......@@ -113,9 +112,8 @@ export interface ITreeModel<T, TFilterData, TRef> {
getListRenderCount(location: TRef): number;
getNode(location?: TRef): ITreeNode<T, any>;
getNodeLocation(node: ITreeNode<T, any>): TRef;
getParentNodeLocation(location: TRef): TRef;
getParentNodeLocation(location: TRef): TRef | undefined;
getParentElement(location: TRef): T;
getFirstElementChild(location: TRef): T | undefined;
getLastElementAncestor(location?: TRef): T | undefined;
......@@ -160,7 +158,6 @@ export interface ITreeContextMenuEvent<T> {
export interface ITreeNavigator<T> {
current(): T | null;
previous(): T | null;
parent(): T | null;
first(): T | null;
last(): T | null;
next(): T | null;
......@@ -202,3 +199,21 @@ export class TreeError extends Error {
super(`TreeError [${user}] ${message}`);
}
}
export class WeakMapper<K extends object, V> {
constructor(private fn: (k: K) => V) { }
private _map = new WeakMap<K, V>();
map(key: K): V {
let result = this._map.get(key);
if (!result) {
result = this.fn(key);
this._map.set(key, result);
}
return result;
}
}
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedObjectTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { Iterator } from 'vs/base/common/iterator';
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { ISpliceable } from 'vs/base/common/sequence';
......@@ -305,7 +305,7 @@ suite('CompressedObjectTree', function () {
test('ctor', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedTreeModel<number>('test', toSpliceable(list));
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
assert(model);
assert.equal(list.length, 0);
assert.equal(model.size, 0);
......@@ -313,7 +313,7 @@ suite('CompressedObjectTree', function () {
test('flat', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedTreeModel<number>('test', toSpliceable(list));
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
model.setChildren(null, Iterator.fromArray([
{ element: 0 },
......@@ -340,7 +340,7 @@ suite('CompressedObjectTree', function () {
test('nested', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedTreeModel<number>('test', toSpliceable(list));
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
model.setChildren(null, Iterator.fromArray([
{
......@@ -376,7 +376,7 @@ suite('CompressedObjectTree', function () {
test('compressed', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedTreeModel<number>('test', toSpliceable(list));
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
model.setChildren(null, Iterator.fromArray([
{
......
......@@ -7,7 +7,7 @@ import * as assert from 'assert';
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';
import { IndexTreeModel, IIndexTreeNode } from 'vs/base/browser/ui/tree/indexTreeModel';
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
return {
......@@ -637,7 +637,7 @@ suite('IndexTreeModel', function () {
suite('getNodeLocation', function () {
test('simple', function () {
const list: ITreeNode<number>[] = [];
const list: IIndexTreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
model.splice([0], 0, Iterator.fromArray([
......@@ -661,7 +661,7 @@ suite('IndexTreeModel', function () {
});
test('with filter', function () {
const list: ITreeNode<number>[] = [];
const list: IIndexTreeNode<number>[] = [];
const filter = new class implements ITreeFilter<number> {
filter(element: number): TreeVisibility {
return element % 2 === 0 ? TreeVisibility.Visible : TreeVisibility.Hidden;
......
......@@ -6,8 +6,9 @@
import * as assert from 'assert';
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ObjectTree, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { Iterator } from 'vs/base/common/iterator';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
suite('ObjectTree', function () {
suite('TreeNavigator', function () {
......@@ -81,8 +82,6 @@ suite('ObjectTree', function () {
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.parent(), 0);
assert.equal(navigator.parent(), null);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
......@@ -112,7 +111,6 @@ suite('ObjectTree', function () {
assert.equal(navigator.previous(), 0);
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.parent(), null);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
......@@ -147,8 +145,6 @@ suite('ObjectTree', function () {
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.parent(), 0);
assert.equal(navigator.parent(), null);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
......@@ -180,8 +176,6 @@ suite('ObjectTree', function () {
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.parent(), 0);
assert.equal(navigator.parent(), null);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
......@@ -225,3 +219,160 @@ suite('ObjectTree', function () {
assert.deepStrictEqual(tree.getFocus(), [101]);
});
});
function toArray(list: NodeList): Node[] {
const result: Node[] = [];
list.forEach(node => result.push(node));
return result;
}
suite('CompressibleObjectTree', function () {
class Delegate implements IListVirtualDelegate<number> {
getHeight() { return 20; }
getTemplateId(): string { return 'default'; }
}
class Renderer implements ICompressibleTreeRenderer<number, void, HTMLElement> {
readonly templateId = 'default';
renderTemplate(container: HTMLElement): HTMLElement {
return container;
}
renderElement(node: ITreeNode<number, void>, _: number, templateData: HTMLElement): void {
templateData.textContent = `${node.element}`;
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<number>, void>, _: number, templateData: HTMLElement): void {
templateData.textContent = `${node.element.elements.join('/')}`;
}
disposeTemplate(): void { }
}
test('empty', function () {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
const rows = toArray(container.querySelectorAll('.monaco-tl-contents'));
assert.equal(rows.length, 0);
});
test('simple', function () {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
tree.setChildren(null, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
const rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['0', '10', '11', '12', '1', '2']);
});
test('compressed', () => {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
tree.setChildren(null, Iterator.fromArray([
{
element: 1, children: Iterator.fromArray([{
element: 11, children: Iterator.fromArray([{
element: 111, children: Iterator.fromArray([
{ element: 1111 },
{ element: 1112 },
{ element: 1113 },
])
}])
}])
}
]));
let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
tree.setChildren(11, Iterator.fromArray([
{ element: 111 },
{ element: 112 },
{ element: 113 },
]));
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113']);
tree.setChildren(113, Iterator.fromArray([
{ element: 1131 }
]));
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131']);
tree.setChildren(1131, Iterator.fromArray([
{ element: 1132 }
]));
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131/1132']);
tree.setChildren(1131, Iterator.fromArray([
{ element: 1132 },
{ element: 1133 },
]));
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131', '1132', '1133']);
});
test('enableCompression', () => {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
assert.equal(tree.isCompressionEnabled(), true);
tree.setChildren(null, Iterator.fromArray([
{
element: 1, children: Iterator.fromArray([{
element: 11, children: Iterator.fromArray([{
element: 111, children: Iterator.fromArray([
{ element: 1111 },
{ element: 1112 },
{ element: 1113 },
])
}])
}])
}
]));
let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
tree.setCompressionEnabled(false);
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1', '11', '111', '1111', '1112', '1113']);
tree.setCompressionEnabled(true);
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
});
});
......@@ -25,9 +25,9 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree';
import { AsyncDataTree, IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree';
import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree';
import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
......@@ -681,7 +681,7 @@ export class TreeResourceNavigator2<T, TFilterData> extends Disposable {
readonly onDidOpenResource: Event<IOpenEvent<T | null>> = this._onDidOpenResource.event;
constructor(
private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchDataTree<any, T, TFilterData> | WorkbenchAsyncDataTree<any, T, TFilterData>,
private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchDataTree<any, T, TFilterData> | WorkbenchAsyncDataTree<any, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<any, T, TFilterData>,
options?: IResourceResultsNavigationOptions2
) {
super();
......@@ -863,6 +863,35 @@ export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends Async
}
}
export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> extends CompressibleAsyncDataTree<TInput, T, TFilterData> {
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
constructor(
user: string,
container: HTMLElement,
virtualDelegate: IListVirtualDelegate<T>,
compressionDelegate: ITreeCompressionDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
dataSource: IAsyncDataSource<TInput, T>,
options: IAsyncDataTreeOptions<T, TFilterData>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService
) {
const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService);
super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions);
this.disposables.push(disposable);
this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService);
this.disposables.push(this.internals);
}
}
function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTreeOptions<T, TFilterData> | IAsyncDataTreeOptions<T, TFilterData>>(
container: HTMLElement,
options: TOptions,
......@@ -928,7 +957,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
private disposables: IDisposable[] = [];
constructor(
tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData>,
tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData>,
options: IAbstractTreeOptions<T, TFilterData> | IAsyncDataTreeOptions<T, TFilterData>,
getAutomaticKeyboardNavigation: () => boolean | undefined,
@IContextKeyService contextKeyService: IContextKeyService,
......
......@@ -701,14 +701,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
if (!start) {
scope = undefined;
} else {
const selectedNode = tree.getNode(start);
const parentNode = selectedNode.parent;
if (!parentNode || !parentNode.parent) { // root
scope = undefined;
} else {
scope = parentNode.element;
}
scope = tree.getParentElement(start);
}
const newSelection: unknown[] = [];
......
......@@ -5,7 +5,6 @@
import * as DOM from 'vs/base/browser/dom';
import { Action } from 'vs/base/common/actions';
import { INavigator } from 'vs/base/common/iterator';
import { createKeybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { isWindows, OS } from 'vs/base/common/platform';
import { repeat } from 'vs/base/common/strings';
......@@ -29,6 +28,7 @@ import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/search
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet';
import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel';
import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree';
export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean {
const searchView = getSearchView(viewletService, panelService);
......@@ -447,7 +447,7 @@ export abstract class AbstractSearchAndReplaceAction extends Action {
}
getNextElementAfterRemoved(viewer: WorkbenchObjectTree<RenderableMatch>, element: RenderableMatch): RenderableMatch {
const navigator: INavigator<any> = viewer.navigate(element);
const navigator: ITreeNavigator<any> = viewer.navigate(element);
if (element instanceof FolderMatch) {
while (!!navigator.next() && !(navigator.current() instanceof FolderMatch)) { }
} else if (element instanceof FileMatch) {
......@@ -461,7 +461,7 @@ export abstract class AbstractSearchAndReplaceAction extends Action {
}
getPreviousElementAfterRemoved(viewer: WorkbenchObjectTree<RenderableMatch>, element: RenderableMatch): RenderableMatch {
const navigator: INavigator<any> = viewer.navigate(element);
const navigator: ITreeNavigator<any> = viewer.navigate(element);
let previousElement = navigator.previous();
// Hence take the previous element.
......@@ -613,7 +613,7 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction {
}
private getElementToFocusAfterReplace(): Match {
const navigator: INavigator<any> = this.viewer.navigate();
const navigator: ITreeNavigator<any> = this.viewer.navigate();
let fileMatched = false;
let elementToFocus: any = null;
do {
......
......@@ -68,10 +68,6 @@ class ArrayNavigator<T> implements ITreeNavigator<T> {
return this.elements[--this.index];
}
parent(): T | null {
throw new Error('not implemented');
}
first(): T | null {
this.index = 0;
return this.elements[this.index];
......
......@@ -44,7 +44,7 @@
require.config({ baseUrl: '/static' });
require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/compressedObjectTree', 'vs/base/browser/ui/tree/asyncDataTree', 'vs/base/browser/ui/tree/dataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { CompressedObjectTree }, { AsyncDataTree }, { DataTree }, { TreeVisibility }, { iter }) => {
require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/objectTree', 'vs/base/browser/ui/tree/asyncDataTree', 'vs/base/browser/ui/tree/dataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { CompressibleObjectTree }, { AsyncDataTree }, { DataTree }, { TreeVisibility }, { iter }) => {
function createIndexTree(opts) {
opts = opts || {};
......@@ -95,7 +95,7 @@
}
};
const tree = new IndexTree(container, delegate, [renderer], null, { ...opts, filter: treeFilter, setRowLineHeight: false });
const tree = new IndexTree('test', container, delegate, [renderer], null, { ...opts, filter: treeFilter, setRowLineHeight: false });
return { tree, treeFilter };
}
......@@ -113,11 +113,10 @@
templateId: 'template',
renderTemplate(container) { return container; },
renderElement(element, index, container) {
if (element.element.elements.length > 1) {
container.innerHTML = `🙈 ${element.element.elements.map(el => el.name).join('/')}`;
} else {
container.innerHTML = element.element.elements[0].name;
}
container.innerHTML = element.element.name;
},
renderCompressedElements(node, index, container, height) {
container.innerHTML = `🙈 ${node.element.elements.map(el => el.name).join('/')}`;
},
disposeElement() { },
disposeTemplate() { }
......@@ -146,7 +145,7 @@
}
};
const tree = new CompressedObjectTree(container, delegate, [renderer], { ...opts, filter: treeFilter, setRowLineHeight: false, collapseByDefault: true, setRowLineHeight: true });
const tree = new CompressibleObjectTree('test', container, delegate, [renderer], { ...opts, filter: treeFilter, setRowLineHeight: false, collapseByDefault: true, setRowLineHeight: true });
return { tree, treeFilter };
}
......@@ -206,7 +205,7 @@
getChildren(element) {
return new Promise((c, e) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', element ? `/ api / readdir ? path = ${element.element.path} ` : '/api/readdir');
xhr.open('GET', element ? `/api/readdir?path=${element.element.path}` : '/api/readdir');
xhr.send();
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
......@@ -228,7 +227,7 @@
}
};
const tree = new AsyncDataTree(container, delegate, [renderer], dataSource, { filter: treeFilter, sorter, identityProvider });
const tree = new AsyncDataTree('test', container, delegate, [renderer], dataSource, { filter: treeFilter, sorter, identityProvider });
return { tree, treeFilter };
}
......@@ -283,15 +282,15 @@
}
};
const tree = new DataTree(container, delegate, [renderer], dataSource, { filter: treeFilter, identityProvider });
const tree = new DataTree('test', container, delegate, [renderer], dataSource, { filter: treeFilter, identityProvider });
tree.input = {
tree.setInput({
children: [
{ name: 'A', children: [{ name: 'AA' }, { name: 'AB' }] },
{ name: 'B', children: [{ name: 'BA', children: [{ name: 'BAA' }] }, { name: 'BB' }] },
{ name: 'C' }
]
};
});
return { tree, treeFilter };
}
......@@ -324,9 +323,9 @@
expandall.onclick = () => perf('expand all', () => tree.expandAll());
collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));
refresh.onclick = () => perf('refresh', () => tree.refresh(null, true));
refresh.onclick = () => perf('refresh', () => tree.updateChildren());
tree.refresh(null);
tree.setInput(null);
break;
}
......@@ -336,7 +335,7 @@
expandall.onclick = () => perf('expand all', () => tree.expandAll());
collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));
refresh.onclick = () => perf('refresh', () => tree.refresh(null, true));
refresh.onclick = () => perf('refresh', () => tree.updateChildren());
break;
}
......@@ -401,4 +400,4 @@
</script>
</body>
</html>
\ No newline at end of file
</html>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册