提交 842fdc7d 编写于 作者: J Joao Moreno

unify tree models

上级 a8e7ed08
......@@ -6,105 +6,91 @@
'use strict';
import { ISpliceable } from 'vs/base/common/sequence';
import { IIterator, map, collect, forEach, iter, empty } from 'vs/base/common/iterator';
import { IIterator, map, collect, iter } from 'vs/base/common/iterator';
import { last } from 'vs/base/common/arrays';
import { Tree } from 'vs/base/common/tree';
/**
* TODO:
* remove trie
* remote base tree
* remove tree
*/
export interface ITreeElement<T> {
readonly element: T;
readonly children: IIterator<ITreeElement<T>>;
readonly collapsed: boolean;
}
export interface ITreeListElement<T> {
readonly element: T;
readonly collapsed: boolean;
readonly depth: number;
}
class TreeNode<T> implements ITreeListElement<T> {
static createRoot<T>(element: T): TreeNode<T> {
const node = new TreeNode<T>(element);
node.children = [];
node.count = 1;
node.depth = 0;
return node;
}
static createNode<T>(treeElement: ITreeElement<T>, depth: number, list: ITreeListElement<T>[]): TreeNode<T> {
const node = new TreeNode<T>(treeElement.element);
list.push(node);
let count = 1;
const children: TreeNode<T>[] = [];
forEach(treeElement.children, child => {
const node = TreeNode.createNode<T>(child, depth + 1, list);
children.push(node);
count += node.count;
});
interface ITreeNode<T> {
readonly element: T;
readonly children: ITreeNode<T>[];
readonly depth: number;
collapsed: boolean;
visibleCount: number;
}
node.children = children;
node.count = count;
node.depth = depth;
function treeElementToNode<T>(treeElement: ITreeElement<T>, depth: number, visible: boolean, visibleElements: ITreeListElement<T>[]): ITreeNode<T> {
const { element, collapsed } = treeElement;
return node;
if (visible) {
visibleElements.push({ element, collapsed, depth });
}
children: TreeNode<T>[];
count: number;
depth: number;
private constructor(public element: T) { }
const children = collect(map(treeElement.children, el => treeElementToNode(el, depth + 1, visible && !treeElement.collapsed, visibleElements)));
const visibleCount = children.reduce((r, c) => r + (c.collapsed ? 1 : c.visibleCount), 1);
splice(index: number, deleteCount: number, toInsert: IIterator<ITreeElement<T>>): { listDeleteCount: number; listElements: ITreeListElement<T>[]; deletedNodes: TreeNode<T>[]; } {
const listElements = [] as ITreeListElement<T>[];
const nodesToInsert = collect(map(toInsert, e => TreeNode.createNode<T>(e, this.depth + 1, listElements)));
const listAddCount = nodesToInsert.reduce((r, n) => r + n.count, 0);
const deletedNodes = this.children.splice(index, deleteCount, ...nodesToInsert);
const listDeleteCount = deletedNodes.reduce((r, n) => r + n.count, 0);
this.count += listAddCount - listDeleteCount;
return { listDeleteCount, listElements, deletedNodes };
}
return { element, children, depth, collapsed, visibleCount };
}
function createTreeElementFromTreeNode<T>(node: TreeNode<T>): ITreeElement<T> {
return {
element: node.element,
children: map(iter(node.children), createTreeElementFromTreeNode)
};
function treeNodeToElement<T>(node: ITreeNode<T>): ITreeElement<T> {
const { element, collapsed } = node;
const children = map(iter(node.children), treeNodeToElement);
return { element, children, collapsed };
}
export class TreeModel<T> {
private root = TreeNode.createRoot<T>(undefined);
private root: ITreeNode<T> = {
element: undefined,
children: [],
depth: 0,
collapsed: false,
visibleCount: 1
};
constructor(private spliceable: ISpliceable<ITreeListElement<T>>) { }
constructor(private list: ISpliceable<ITreeListElement<T>>) { }
splice(location: number[], deleteCount: number, toInsert: IIterator<ITreeElement<T>>): IIterator<ITreeElement<T>> {
if (location.length === 0) {
throw new Error('Invalid tree location');
}
const { parentNode, parentListIndex } = this.findParentNode(location);
const { parentNode, parentListIndex, visible } = this.findParentNode(location);
const listToInsert: ITreeListElement<T>[] = [];
const nodesToInsert = collect(map(toInsert, el => treeElementToNode(el, parentNode.depth + 1, visible, listToInsert)));
const index = last(location);
const { listDeleteCount, listElements, deletedNodes } = parentNode.splice(index, deleteCount, toInsert);
const deletedNodes = parentNode.children.splice(index, deleteCount, ...nodesToInsert);
parentNode.visibleCount += nodesToInsert.reduce((r, c) => r + (c.collapsed ? 1 : c.visibleCount), 1) - deletedNodes.reduce((r, c) => r + (c.collapsed ? 1 : c.visibleCount), 1);
this.spliceable.splice(parentListIndex + index, listDeleteCount, listElements);
if (visible) {
const listDeleteCount = deletedNodes.reduce((r, c) => r + (c.collapsed ? 1 : c.visibleCount), 1);
this.list.splice(parentListIndex + index, listDeleteCount, listToInsert);
}
return map(iter(deletedNodes), createTreeElementFromTreeNode);
return map(iter(deletedNodes), treeNodeToElement);
}
private findParentNode(location: number[], node: TreeNode<T> = this.root, listIndex: number = 0): { parentNode: TreeNode<T>; parentListIndex: number } {
private findParentNode(location: number[], node: ITreeNode<T> = this.root, listIndex: number = 0, visible = true): { parentNode: ITreeNode<T>; parentListIndex: number; visible: boolean; } {
if (location.length === 1) {
return { parentNode: node, parentListIndex: listIndex };
return { parentNode: node, parentListIndex: listIndex, visible };
}
const [i, ...rest] = location;
......@@ -112,53 +98,9 @@ export class TreeModel<T> {
// TODO@joao perf!
for (let j = 0; j < limit; j++) {
listIndex += node.children[j].count;
listIndex += node.children[j].visibleCount;
}
return this.findParentNode(rest, node.children[i], listIndex + 1);
return this.findParentNode(rest, node.children[i], listIndex + 1, visible && !node.collapsed);
}
}
interface ICollapsibleElement<T> {
collapsed: boolean;
element: T;
}
export type ICollapsibleTreeElement<T> = ITreeElement<ICollapsibleElement<T>>;
export type ICollapsibleTreeListElement<T> = ITreeListElement<ICollapsibleElement<T>>;
function asVisibleElement<T>(element: ICollapsibleTreeElement<T>): ICollapsibleTreeElement<T> {
if (element.element.collapsed) {
return { element: element.element, children: empty() };
}
return {
element: element.element,
children: map(element.children, asVisibleElement)
};
}
export class CollapsibleTreeModel<T> {
private model = new Tree<ICollapsibleElement<T>>();
private viewModel: TreeModel<ICollapsibleElement<T>>;
constructor(spliceable: ISpliceable<ICollapsibleTreeListElement<T>>) {
this.viewModel = new TreeModel(spliceable);
}
splice(location: number[], deleteCount: number, toInsert: IIterator<ICollapsibleTreeElement<T>>): IIterator<ICollapsibleTreeElement<T>> {
let length = 0;
toInsert = map(toInsert, el => { length++; return el; });
const result = this.model.splice(location, deleteCount, toInsert);
const [ancestors, elementsToInsert] = this.model.getElementRange(location, length);
const isVisible = ancestors.every(el => !el.collapsed);
if (isVisible) {
this.viewModel.splice(location, deleteCount, map(elementsToInsert, asVisibleElement));
}
return result;
}
}
}
\ No newline at end of file
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { TreeModel, ITreeListElement, CollapsibleTreeModel, ICollapsibleTreeListElement } from 'vs/base/browser/ui/list/treeModel';
import { ITreeListElement, TreeModel } from 'vs/base/browser/ui/list/treeModel';
import { ISpliceable } from 'vs/base/browser/ui/list/splice';
import { iter } from 'vs/base/common/iterator';
......@@ -30,17 +30,20 @@ suite('TreeModel2', () => {
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{ element: 0, children: iter([]) },
{ element: 1, children: iter([]) },
{ element: 2, children: iter([]) }
{ element: 0, collapsed: false, children: iter([]) },
{ element: 1, collapsed: false, children: iter([]) },
{ element: 2, collapsed: false, children: iter([]) }
]));
assert.deepEqual(list.length, 3);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 1);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
assert.deepEqual(list[2].element, 2);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 1);
});
......@@ -50,184 +53,62 @@ suite('TreeModel2', () => {
model.splice([0], 0, iter([
{
element: 0, children: iter([
{ element: 10, children: iter([]) },
{ element: 11, children: iter([]) },
{ element: 12, children: iter([]) },
element: 0, collapsed: false, children: iter([
{ element: 10, collapsed: false, children: iter([]) },
{ element: 11, collapsed: false, children: iter([]) },
{ element: 12, collapsed: false, children: iter([]) },
])
},
{ element: 1, children: iter([]) },
{ element: 2, children: iter([]) }
{ element: 1, collapsed: false, children: iter([]) },
{ element: 2, collapsed: false, children: iter([]) }
]));
assert.deepEqual(list.length, 6);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 2);
assert.deepEqual(list[2].element, 11);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 2);
assert.deepEqual(list[3].element, 12);
assert.deepEqual(list[3].collapsed, false);
assert.deepEqual(list[3].depth, 2);
assert.deepEqual(list[4].element, 1);
assert.deepEqual(list[4].collapsed, false);
assert.deepEqual(list[4].depth, 1);
assert.deepEqual(list[5].element, 2);
assert.deepEqual(list[5].collapsed, false);
assert.deepEqual(list[5].depth, 1);
});
test('delete', () => {
const list = [] as ITreeListElement<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{ element: 0, children: iter([]) },
{ element: 1, children: iter([]) },
{ element: 2, children: iter([]) }
]));
model.splice([0], 3, iter([]));
assert.equal(list.length, 0);
});
test('nested delete', () => {
const list = [] as ITreeListElement<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: 0, children: iter([
{ element: 10, children: iter([]) },
{ element: 11, children: iter([]) },
{ element: 12, children: iter([]) },
])
},
{ element: 1, children: iter([]) },
{ element: 2, children: iter([]) }
]));
model.splice([0, 1], 1, iter([]));
assert.deepEqual(list.length, 5, 'list has 5 elements');
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].depth, 2);
assert.deepEqual(list[2].element, 12);
assert.deepEqual(list[2].depth, 2);
assert.deepEqual(list[3].element, 1);
assert.deepEqual(list[3].depth, 1);
assert.deepEqual(list[4].element, 2);
assert.deepEqual(list[4].depth, 1);
});
test('deep delete', () => {
test('deep insert collapsed', () => {
const list = [] as ITreeListElement<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: 0, children: iter([
{ element: 10, children: iter([]) },
{ element: 11, children: iter([]) },
{ element: 12, children: iter([]) },
element: 0, collapsed: true, children: iter([
{ element: 10, collapsed: false, children: iter([]) },
{ element: 11, collapsed: false, children: iter([]) },
{ element: 12, collapsed: false, children: iter([]) },
])
},
{ element: 1, children: iter([]) },
{ element: 2, children: iter([]) }
]));
model.splice([0], 1, iter([]));
assert.deepEqual(list.length, 2, 'list has 2 elements only');
assert.deepEqual(list[0].element, 1);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 2);
assert.deepEqual(list[1].depth, 1);
});
});
suite('CollapsibleTreeModel', () => {
test('ctor', () => {
const list = [] as ICollapsibleTreeListElement<number>[];
const model = new CollapsibleTreeModel<number>(toSpliceable(list));
assert(model);
assert.equal(list.length, 0);
});
test('insert', () => {
const list = [] as ICollapsibleTreeListElement<number>[];
const model = new CollapsibleTreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{ element: { element: 0, collapsed: false }, children: iter([]) },
{ element: { element: 1, collapsed: false }, children: iter([]) },
{ element: { element: 2, collapsed: false }, children: iter([]) }
{ element: 1, collapsed: false, children: iter([]) },
{ element: 2, collapsed: false, children: iter([]) }
]));
assert.deepEqual(list.length, 3);
assert.deepEqual(list[0].element.element, 0);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element.element, 1);
assert.deepEqual(list[1].depth, 1);
assert.deepEqual(list[2].element.element, 2);
assert.deepEqual(list[2].depth, 1);
});
test('deep insert', () => {
const list = [] as ICollapsibleTreeListElement<number>[];
const model = new CollapsibleTreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: { element: 0, collapsed: false }, children: iter([
{ element: { element: 10, collapsed: false }, children: iter([]) },
{ element: { element: 11, collapsed: false }, children: iter([]) },
{ element: { element: 12, collapsed: false }, children: iter([]) },
])
},
{ element: { element: 1, collapsed: false }, children: iter([]) },
{ element: { element: 2, collapsed: false }, children: iter([]) }
]));
assert.deepEqual(list.length, 6);
assert.deepEqual(list[0].element.element, 0);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element.element, 10);
assert.deepEqual(list[1].depth, 2);
assert.deepEqual(list[2].element.element, 11);
assert.deepEqual(list[2].depth, 2);
assert.deepEqual(list[3].element.element, 12);
assert.deepEqual(list[3].depth, 2);
assert.deepEqual(list[4].element.element, 1);
assert.deepEqual(list[4].depth, 1);
assert.deepEqual(list[5].element.element, 2);
assert.deepEqual(list[5].depth, 1);
});
test('deep insert collapsed', () => {
const list = [] as ICollapsibleTreeListElement<number>[];
const model = new CollapsibleTreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: { element: 0, collapsed: true }, children: iter([
{ element: { element: 10, collapsed: false }, children: iter([]) },
{ element: { element: 11, collapsed: false }, children: iter([]) },
{ element: { element: 12, collapsed: false }, children: iter([]) },
])
},
{ element: { element: 1, collapsed: false }, children: iter([]) },
{ element: { element: 2, collapsed: false }, children: iter([]) }
]));
assert.deepEqual(list.length, 3);
assert.deepEqual(list[0].element.element, 0);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, true);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element.element, 1);
assert.deepEqual(list[1].element, 1);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
assert.deepEqual(list[2].element.element, 2);
assert.deepEqual(list[2].element, 2);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 1);
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册