提交 707bb36d 编写于 作者: J Joao Moreno

ObjectTree.navigate

fixes #64793
上级 89a77514
......@@ -1096,6 +1096,10 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.eventBufferer.bufferEvents(() => this.spliceable.splice(start, deleteCount, elements));
}
element(index: number): T {
return this.view.element(index);
}
get length(): number {
return this.view.length;
}
......
......@@ -11,7 +11,7 @@ import { append, $, toggleClass } from 'vs/base/browser/dom';
import { Event, Relay } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter } from 'vs/base/browser/ui/tree/tree';
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator } from 'vs/base/browser/ui/tree/tree';
import { ISpliceable } from 'vs/base/common/sequence';
function asListOptions<T, TFilterData>(options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {
......@@ -491,8 +491,68 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
protected abstract createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IAbstractTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, TRef>;
navigate(): ITreeNavigator<T> {
return new TreeNavigator(this.view, this.model);
}
dispose(): void {
this.disposables = dispose(this.disposables);
this.view.dispose();
}
}
interface ITreeNavigatorView<T extends NonNullable<any>, TFilterData> {
readonly length: number;
element(index: number): ITreeNode<T, TFilterData>;
}
class TreeNavigator<T extends NonNullable<any>, TFilterData, TRef> implements ITreeNavigator<T> {
private index: number = -1;
constructor(private view: ITreeNavigatorView<T, TFilterData>, private model: ITreeModel<T, TFilterData, TRef>) { }
current(): T | null {
if (this.index < 0 || this.index >= this.view.length) {
return null;
}
return this.view.element(this.index).element;
}
previous(): T | null {
this.index--;
return this.current();
}
next(): T | null {
this.index++;
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();
}
last(): T | null {
this.index = this.view.length - 1;
return this.current();
}
}
\ No newline at end of file
......@@ -120,7 +120,8 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
getListIndex(location: number[]): number {
return this.getTreeNodeWithListIndex(location).listIndex;
const { node, listIndex } = this.getTreeNodeWithListIndex(location);
return node ? listIndex : -1;
}
setCollapsed(location: number[], collapsed: boolean): boolean {
......
......@@ -128,6 +128,15 @@ export interface ITreeContextMenuEvent<T> {
anchor: HTMLElement | { x: number; y: number; } | undefined;
}
export interface ITreeNavigator<T> {
current(): T | null;
previous(): T | null;
parent(): T | null;
first(): T | null;
last(): T | null;
next(): T | null;
}
/**
* Use this renderer when you want to re-render elements on account of
* an event firing.
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { Iterator } from 'vs/base/common/iterator';
suite('ObjectTree', function () {
suite('TreeNavigator', function () {
let tree: ObjectTree<number>;
let filter = (_: number) => true;
setup(() => {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const delegate = new class implements IListVirtualDelegate<number> {
getHeight() { return 20; }
getTemplateId(): string { return 'default'; }
};
const renderer = new class implements ITreeRenderer<number, void, HTMLElement> {
readonly templateId = 'default';
renderTemplate(container: HTMLElement): HTMLElement {
return container;
}
renderElement(element: ITreeNode<number, void>, index: number, templateData: HTMLElement): void {
templateData.textContent = `${element.element}`;
}
disposeTemplate(): void { }
};
tree = new ObjectTree<number>(container, delegate, [renderer], { filter: { filter: (el) => filter(el) } });
tree.layout(200);
});
teardown(() => {
tree.dispose();
});
test('should be able to navigate', () => {
tree.setChildren(null, Iterator.fromArray([
{
element: 0, children: Iterator.fromArray([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
const navigator = tree.navigate();
assert.equal(navigator.current(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.current(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.current(), 10);
assert.equal(navigator.next(), 11);
assert.equal(navigator.current(), 11);
assert.equal(navigator.next(), 12);
assert.equal(navigator.current(), 12);
assert.equal(navigator.next(), 1);
assert.equal(navigator.current(), 1);
assert.equal(navigator.next(), 2);
assert.equal(navigator.current(), 2);
assert.equal(navigator.previous(), 1);
assert.equal(navigator.current(), 1);
assert.equal(navigator.previous(), 12);
assert.equal(navigator.previous(), 11);
assert.equal(navigator.previous(), 10);
assert.equal(navigator.previous(), 0);
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);
});
test('should skip collapsed nodes', () => {
tree.setChildren(null, Iterator.fromArray([
{
element: 0, collapsed: true, children: Iterator.fromArray([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
const navigator = tree.navigate();
assert.equal(navigator.current(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 1);
assert.equal(navigator.next(), 2);
assert.equal(navigator.next(), null);
assert.equal(navigator.previous(), 2);
assert.equal(navigator.previous(), 1);
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);
});
test('should skip filtered elements', () => {
filter = el => el % 2 === 0;
tree.setChildren(null, Iterator.fromArray([
{
element: 0, children: Iterator.fromArray([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
const navigator = tree.navigate();
assert.equal(navigator.current(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.next(), 12);
assert.equal(navigator.next(), 2);
assert.equal(navigator.next(), null);
assert.equal(navigator.previous(), 2);
assert.equal(navigator.previous(), 12);
assert.equal(navigator.previous(), 10);
assert.equal(navigator.previous(), 0);
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);
});
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册