提交 33fc7752 编写于 作者: J Joao Moreno

objectTreeModel preserves collapse state on strict identity

上级 d88dd690
...@@ -8,20 +8,14 @@ import { Iterator, ISequence, getSequenceIterator } from 'vs/base/common/iterato ...@@ -8,20 +8,14 @@ import { Iterator, ISequence, getSequenceIterator } from 'vs/base/common/iterato
import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel'; import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> { export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> { }
identityProvider?: IIdentityProvider<T>;
}
export class ObjectTreeModel<T extends NonNullable<any>, TFilterData = void> implements ITreeModel<T, TFilterData, T> { export class ObjectTreeModel<T extends NonNullable<any>, TFilterData = void> implements ITreeModel<T, TFilterData, T> {
private model: IndexTreeModel<T, TFilterData>; private model: IndexTreeModel<T, TFilterData>;
private nodes = new Map<T, ITreeNode<T, TFilterData>>(); private nodes = new Map<T, ITreeNode<T, TFilterData>>();
private identityProvider: IIdentityProvider<T> | undefined = undefined;
private nodesByIdentity: Map<string, ITreeNode<T, TFilterData>> | undefined = undefined;
readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>; readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>;
readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>>; readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>>;
...@@ -29,39 +23,23 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData = void> imp ...@@ -29,39 +23,23 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData = void> imp
constructor(list: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeModelOptions<T, TFilterData> = {}) { constructor(list: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeModelOptions<T, TFilterData> = {}) {
this.model = new IndexTreeModel(list, options); this.model = new IndexTreeModel(list, options);
this.identityProvider = options.identityProvider;
if (this.identityProvider) {
this.nodesByIdentity = new Map();
}
this.onDidChangeCollapseState = this.model.onDidChangeCollapseState; this.onDidChangeCollapseState = this.model.onDidChangeCollapseState;
this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount; this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount;
} }
setChildren( setChildren(
element: T | null, element: T | null,
// TODO@joao also use identity provider to preserve children when `children` is undefined!!!
children: ISequence<ITreeElement<T>> | undefined, children: ISequence<ITreeElement<T>> | undefined,
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void, onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
): Iterator<ITreeElement<T>> { ): Iterator<ITreeElement<T>> {
const location = this.getElementLocation(element); const location = this.getElementLocation(element);
const insertedElements = new Set<T>(); const insertedElements = new Set<T>();
let createdNodesByIdentity: Map<string, ITreeNode<T, TFilterData>> | undefined;
if (this.identityProvider) {
createdNodesByIdentity = new Map();
}
const _onDidCreateNode = (node: ITreeNode<T, TFilterData>) => { const _onDidCreateNode = (node: ITreeNode<T, TFilterData>) => {
insertedElements.add(node.element); insertedElements.add(node.element);
this.nodes.set(node.element, node); this.nodes.set(node.element, node);
if (createdNodesByIdentity) {
createdNodesByIdentity.set(this.identityProvider!.getId(node.element).toString(), node);
}
if (onDidCreateNode) { if (onDidCreateNode) {
onDidCreateNode(node); onDidCreateNode(node);
} }
...@@ -72,43 +50,40 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData = void> imp ...@@ -72,43 +50,40 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData = void> imp
this.nodes.delete(node.element); this.nodes.delete(node.element);
} }
if (this.nodesByIdentity) {
this.nodesByIdentity.delete(this.identityProvider!.getId(node.element).toString());
}
if (onDidDeleteNode) { if (onDidDeleteNode) {
onDidDeleteNode(node); onDidDeleteNode(node);
} }
}; };
const preserveCollapseState = (elements: ISequence<ITreeElement<T>>): ISequence<ITreeElement<T>> => { return this.model.splice(
const iterator = getSequenceIterator(elements); [...location, 0],
Number.MAX_VALUE,
return Iterator.map(iterator, treeElement => { this.preserveCollapseState(children),
const identity = this.identityProvider!.getId(treeElement.element).toString(); _onDidCreateNode,
const node = this.nodesByIdentity!.get(identity); _onDidDeleteNode
const collapsible = typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : (typeof treeElement.collapsed !== 'undefined'); );
}
return {
...treeElement,
collapsible,
collapsed: typeof treeElement.collapsed === 'undefined' ? (node ? (collapsible && node.collapsed) : undefined) : treeElement.collapsed,
children: treeElement.children && preserveCollapseState(treeElement.children)
};
});
};
if (children && this.identityProvider) { private preserveCollapseState(elements: ISequence<ITreeElement<T>> | undefined): ISequence<ITreeElement<T>> {
children = preserveCollapseState(children); const iterator = elements ? getSequenceIterator(elements) : Iterator.empty<ITreeElement<T>>();
}
const result = this.model.splice([...location, 0], Number.MAX_VALUE, children, _onDidCreateNode, _onDidDeleteNode); return Iterator.map(iterator, treeElement => {
const node = this.nodes.get(treeElement.element);
if (createdNodesByIdentity) { if (!node) {
createdNodesByIdentity.forEach((node, identity) => this.nodesByIdentity!.set(identity, node)); return treeElement;
} }
const collapsible = typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : node.collapsible;
const collapsed = typeof treeElement.collapsed !== 'undefined' ? treeElement.collapsed : (collapsible && node.collapsed);
return result; return {
...treeElement,
collapsible,
collapsed,
children: this.preserveCollapseState(treeElement.children)
};
});
} }
getParentElement(ref: T | null = null): T | null { getParentElement(ref: T | null = null): T | null {
......
...@@ -141,7 +141,7 @@ suite('ObjectTreeModel', function () { ...@@ -141,7 +141,7 @@ suite('ObjectTreeModel', function () {
assert.deepEqual(toArray(list), [1, 11, 111, 112, 2]); assert.deepEqual(toArray(list), [1, 11, 111, 112, 2]);
}); });
test('collapse state is lost without an identity provider', () => { test('collapse state is preserved with strict identity', () => {
const list: ITreeNode<string>[] = []; const list: ITreeNode<string>[] = [];
const model = new ObjectTreeModel<string>(toSpliceable(list), { collapseByDefault: true }); const model = new ObjectTreeModel<string>(toSpliceable(list), { collapseByDefault: true });
const data = [{ element: 'father', children: [{ element: 'child' }] }]; const data = [{ element: 'father', children: [{ element: 'child' }] }];
...@@ -153,22 +153,19 @@ suite('ObjectTreeModel', function () { ...@@ -153,22 +153,19 @@ suite('ObjectTreeModel', function () {
assert.deepEqual(toArray(list), ['father', 'child']); assert.deepEqual(toArray(list), ['father', 'child']);
model.setChildren(null, data); model.setChildren(null, data);
assert.deepEqual(toArray(list), ['father']); assert.deepEqual(toArray(list), ['father', 'child']);
});
test('collapse state is preserved with an identity provider', () => { const data2 = [{ element: 'father', children: [{ element: 'child' }] }, { element: 'uncle' }];
const list: ITreeNode<string>[] = []; model.setChildren(null, data2);
const identityProvider = { getId: (name: string) => name }; assert.deepEqual(toArray(list), ['father', 'child', 'uncle']);
const model = new ObjectTreeModel<string>(toSpliceable(list), { collapseByDefault: true, identityProvider });
const data = [{ element: 'father', children: [{ element: 'child' }] }];
model.setChildren(null, data); model.setChildren(null, [{ element: 'uncle' }]);
assert.deepEqual(toArray(list), ['father']); assert.deepEqual(toArray(list), ['uncle']);
model.setCollapsed('father', false); model.setChildren(null, data2);
assert.deepEqual(toArray(list), ['father', 'child']); assert.deepEqual(toArray(list), ['father', 'uncle']);
model.setChildren(null, data); model.setChildren(null, data);
assert.deepEqual(toArray(list), ['father', 'child']); assert.deepEqual(toArray(list), ['father']);
}); });
}); });
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册