mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 12:04:04 +01:00
@@ -21,11 +21,11 @@ enum AsyncDataTreeNodeState {
|
||||
Slow
|
||||
}
|
||||
|
||||
interface IAsyncDataTreeNode<T extends NonNullable<any>> {
|
||||
element: T | null;
|
||||
readonly parent: IAsyncDataTreeNode<T> | null;
|
||||
interface IAsyncDataTreeNode<TInput, T> {
|
||||
element: TInput | T;
|
||||
readonly parent: IAsyncDataTreeNode<TInput, T> | null;
|
||||
readonly id?: string | null;
|
||||
readonly children?: IAsyncDataTreeNode<T>[];
|
||||
readonly children?: IAsyncDataTreeNode<TInput, T>[];
|
||||
state: AsyncDataTreeNodeState;
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ interface IDataTreeListTemplateData<T> {
|
||||
templateData: T;
|
||||
}
|
||||
|
||||
class AsyncDataTreeNodeWrapper<T, TFilterData> implements ITreeNode<T, TFilterData> {
|
||||
class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInput | T, TFilterData> {
|
||||
|
||||
get element(): T { return this.node.element!.element!; }
|
||||
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; }
|
||||
@@ -44,18 +44,18 @@ class AsyncDataTreeNodeWrapper<T, TFilterData> implements ITreeNode<T, TFilterDa
|
||||
get visible(): boolean { return this.node.visible; }
|
||||
get filterData(): TFilterData | undefined { return this.node.filterData; }
|
||||
|
||||
constructor(private node: ITreeNode<IAsyncDataTreeNode<T> | null, TFilterData>) { }
|
||||
constructor(private node: ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>) { }
|
||||
}
|
||||
|
||||
class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
|
||||
class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
|
||||
|
||||
readonly templateId: string;
|
||||
private renderedNodes = new Map<IAsyncDataTreeNode<T>, IDataTreeListTemplateData<TTemplateData>>();
|
||||
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
|
||||
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<T>>
|
||||
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
|
||||
) {
|
||||
this.templateId = renderer.templateId;
|
||||
}
|
||||
@@ -65,16 +65,16 @@ class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<I
|
||||
return { templateData };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
|
||||
}
|
||||
|
||||
renderTwistie(element: IAsyncDataTreeNode<T>, twistieElement: HTMLElement): boolean {
|
||||
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
|
||||
toggleClass(twistieElement, 'loading', element.state === AsyncDataTreeNodeState.Slow);
|
||||
return false;
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
if (this.renderer.disposeElement) {
|
||||
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
|
||||
}
|
||||
@@ -90,24 +90,24 @@ class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<I
|
||||
}
|
||||
}
|
||||
|
||||
function asTreeEvent<T>(e: ITreeEvent<IAsyncDataTreeNode<T>>): ITreeEvent<T> {
|
||||
function asTreeEvent<TInput, T>(e: ITreeEvent<IAsyncDataTreeNode<TInput, T>>): ITreeEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
elements: e.elements.map(e => e.element!)
|
||||
elements: e.elements.map(e => e.element as T)
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeMouseEvent<T>(e: ITreeMouseEvent<IAsyncDataTreeNode<T>>): ITreeMouseEvent<T> {
|
||||
function asTreeMouseEvent<TInput, T>(e: ITreeMouseEvent<IAsyncDataTreeNode<TInput, T>>): ITreeMouseEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
element: e.element && e.element.element!
|
||||
element: e.element && e.element.element as T
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeContextMenuEvent<T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<T>>): ITreeContextMenuEvent<T> {
|
||||
function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<TInput, T>>): ITreeContextMenuEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
element: e.element && e.element.element!,
|
||||
element: e.element && e.element.element as T,
|
||||
anchor: e.anchor
|
||||
};
|
||||
}
|
||||
@@ -122,12 +122,12 @@ export interface IChildrenResolutionEvent<T> {
|
||||
readonly reason: ChildrenResolutionReason;
|
||||
}
|
||||
|
||||
function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<T>, TFilterData> | undefined {
|
||||
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
|
||||
return options && {
|
||||
...options,
|
||||
identityProvider: options.identityProvider && {
|
||||
getId(el) {
|
||||
return options.identityProvider!.getId(el.element!);
|
||||
return options.identityProvider!.getId(el.element as T);
|
||||
}
|
||||
},
|
||||
multipleSelectionController: options.multipleSelectionController && {
|
||||
@@ -140,28 +140,28 @@ function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T,
|
||||
},
|
||||
accessibilityProvider: options.accessibilityProvider && {
|
||||
getAriaLabel(e) {
|
||||
return options.accessibilityProvider!.getAriaLabel(e.element!);
|
||||
return options.accessibilityProvider!.getAriaLabel(e.element as T);
|
||||
}
|
||||
},
|
||||
filter: options.filter && {
|
||||
filter(e, parentVisibility) {
|
||||
return options.filter!.filter(e.element!, parentVisibility);
|
||||
return options.filter!.filter(e.element as T, parentVisibility);
|
||||
}
|
||||
},
|
||||
keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
|
||||
getKeyboardNavigationLabel(e) {
|
||||
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element!);
|
||||
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element as T);
|
||||
}
|
||||
},
|
||||
sorter: options.sorter && {
|
||||
compare(a, b) {
|
||||
return options.sorter!.compare(a.element!, b.element!);
|
||||
return options.sorter!.compare(a.element as T, b.element as T);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeElement<T>(node: IAsyncDataTreeNode<T>): ITreeElement<IAsyncDataTreeNode<T>> {
|
||||
function asTreeElement<TInput, T>(node: IAsyncDataTreeNode<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
return {
|
||||
element: node,
|
||||
children: Iterator.map(Iterator.fromArray(node.children!), asTreeElement)
|
||||
@@ -173,15 +173,15 @@ export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAbstractT
|
||||
sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> implements IDisposable {
|
||||
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable {
|
||||
|
||||
private readonly tree: ObjectTree<IAsyncDataTreeNode<T>, TFilterData>;
|
||||
private readonly root: IAsyncDataTreeNode<T>;
|
||||
private readonly nodes = new Map<T | null, IAsyncDataTreeNode<T>>();
|
||||
private readonly refreshPromises = new Map<IAsyncDataTreeNode<T>, Promise<void>>();
|
||||
private readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
|
||||
private readonly root: IAsyncDataTreeNode<TInput, T>;
|
||||
private readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
|
||||
private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
|
||||
private readonly identityProvider?: IIdentityProvider<T>;
|
||||
|
||||
private readonly _onDidChangeNodeState = new Emitter<IAsyncDataTreeNode<T>>();
|
||||
private readonly _onDidChangeNodeState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
|
||||
|
||||
protected readonly disposables: IDisposable[] = [];
|
||||
|
||||
@@ -204,20 +204,20 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
private dataSource: IAsyncDataSource<T>,
|
||||
private dataSource: IAsyncDataSource<TInput, T>,
|
||||
options?: IAsyncDataTreeOptions<T, TFilterData>
|
||||
) {
|
||||
this.identityProvider = options && options.identityProvider;
|
||||
|
||||
const objectTreeDelegate = new ComposedTreeDelegate<T | null, IAsyncDataTreeNode<T>>(delegate);
|
||||
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
|
||||
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event));
|
||||
const objectTreeOptions = asObjectTreeOptions(options) || {};
|
||||
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
|
||||
objectTreeOptions.collapseByDefault = true;
|
||||
|
||||
this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
|
||||
|
||||
this.root = {
|
||||
element: null,
|
||||
element: undefined!,
|
||||
parent: null,
|
||||
state: AsyncDataTreeNodeState.Uninitialized,
|
||||
};
|
||||
@@ -279,16 +279,34 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
// Data Tree
|
||||
|
||||
refresh(element: T | null, recursive = true): Promise<void> {
|
||||
getInput(): TInput | undefined {
|
||||
return this.root.element as TInput;
|
||||
}
|
||||
|
||||
setInput(input: TInput | undefined): Promise<void> {
|
||||
this.root.element = input!;
|
||||
|
||||
if (typeof input === 'undefined') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.refresh(input);
|
||||
}
|
||||
|
||||
refresh(element: TInput | T = this.root.element, recursive = true): Promise<void> {
|
||||
if (typeof this.root.element === 'undefined') {
|
||||
throw new Error('Tree input not set');
|
||||
}
|
||||
|
||||
return this.refreshNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh);
|
||||
}
|
||||
|
||||
// Tree
|
||||
|
||||
getNode(element: T | null): ITreeNode<T | null, TFilterData> {
|
||||
getNode(element: TInput | T): ITreeNode<TInput | T, TFilterData> {
|
||||
const dataNode = this.getDataNode(element);
|
||||
const node = this.tree.getNode(dataNode === this.root ? null : dataNode);
|
||||
return new AsyncDataTreeNodeWrapper<T | null, TFilterData>(node);
|
||||
return new AsyncDataTreeNodeWrapper<TInput, T, TFilterData>(node);
|
||||
}
|
||||
|
||||
collapse(element: T, recursive: boolean = false): boolean {
|
||||
@@ -342,7 +360,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
getSelection(): T[] {
|
||||
const nodes = this.tree.getSelection();
|
||||
return nodes.map(n => n!.element!);
|
||||
return nodes.map(n => n!.element as T);
|
||||
}
|
||||
|
||||
setFocus(elements: T[], browserEvent?: UIEvent): void {
|
||||
@@ -376,7 +394,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
getFocus(): T[] {
|
||||
const nodes = this.tree.getFocus();
|
||||
return nodes.map(n => n!.element!);
|
||||
return nodes.map(n => n!.element as T);
|
||||
}
|
||||
|
||||
open(elements: T[]): void {
|
||||
@@ -394,21 +412,21 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
// Tree navigation
|
||||
|
||||
getParentElement(element: T): T | null {
|
||||
getParentElement(element: T): TInput | T {
|
||||
const node = this.tree.getParentElement(this.getDataNode(element));
|
||||
return node && node.element;
|
||||
return (node && node.element)!;
|
||||
}
|
||||
|
||||
getFirstElementChild(element: T | null = null): T | null | undefined {
|
||||
getFirstElementChild(element: TInput | T = this.root.element): TInput | T | undefined {
|
||||
const dataNode = this.getDataNode(element);
|
||||
const node = this.tree.getFirstElementChild(dataNode === this.root ? null : dataNode);
|
||||
return node && node.element;
|
||||
return (node && node.element)!;
|
||||
}
|
||||
|
||||
getLastElementAncestor(element: T | null = null): T | null | undefined {
|
||||
getLastElementAncestor(element: TInput | T = this.root.element): TInput | T | undefined {
|
||||
const dataNode = this.getDataNode(element);
|
||||
const node = this.tree.getLastElementAncestor(dataNode === this.root ? null : dataNode);
|
||||
return node && node.element;
|
||||
return (node && node.element)!;
|
||||
}
|
||||
|
||||
// List
|
||||
@@ -419,8 +437,8 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
// Implementation
|
||||
|
||||
private getDataNode(element: T | null): IAsyncDataTreeNode<T> {
|
||||
const node: IAsyncDataTreeNode<T> = this.nodes.get(element);
|
||||
private getDataNode(element: TInput | T): IAsyncDataTreeNode<TInput, T> {
|
||||
const node: IAsyncDataTreeNode<TInput, T> = this.nodes.get((element === this.root.element ? null : element) as T);
|
||||
|
||||
if (typeof node === 'undefined') {
|
||||
throw new Error(`Data tree node not found: ${element}`);
|
||||
@@ -429,7 +447,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
return node;
|
||||
}
|
||||
|
||||
private async refreshNode(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
|
||||
private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
|
||||
await this._refreshNode(node, recursive, reason);
|
||||
|
||||
if (recursive && node.children) {
|
||||
@@ -437,7 +455,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshNode(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
|
||||
private _refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
|
||||
let result = this.refreshPromises.get(node);
|
||||
|
||||
if (result) {
|
||||
@@ -449,8 +467,8 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
return always(result, () => this.refreshPromises.delete(node));
|
||||
}
|
||||
|
||||
private doRefresh(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
|
||||
const hasChildren = !!this.dataSource.hasChildren(node.element);
|
||||
private doRefresh(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
|
||||
const hasChildren = !!this.dataSource.hasChildren(node.element!);
|
||||
|
||||
if (!hasChildren) {
|
||||
this.setChildren(node, [], recursive);
|
||||
@@ -468,14 +486,14 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
this._onDidChangeNodeState.fire(node);
|
||||
}, _ => null);
|
||||
|
||||
return Promise.resolve(this.dataSource.getChildren(node.element))
|
||||
return Promise.resolve(this.dataSource.getChildren(node.element!))
|
||||
.then(children => {
|
||||
slowTimeout.cancel();
|
||||
node.state = AsyncDataTreeNodeState.Loaded;
|
||||
this._onDidChangeNodeState.fire(node);
|
||||
|
||||
this.setChildren(node, children, recursive);
|
||||
this._onDidResolveChildren.fire({ element: node.element, reason });
|
||||
this._onDidResolveChildren.fire({ element: node.element as T, reason });
|
||||
}, err => {
|
||||
slowTimeout.cancel();
|
||||
node.state = AsyncDataTreeNodeState.Uninitialized;
|
||||
@@ -490,18 +508,18 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
}
|
||||
}
|
||||
|
||||
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<T>, any>): void {
|
||||
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T>, any>): void {
|
||||
if (!node.collapsed && node.element.state === AsyncDataTreeNodeState.Uninitialized) {
|
||||
if (deep) {
|
||||
this.collapse(node.element.element!);
|
||||
this.collapse(node.element.element as T);
|
||||
} else {
|
||||
this.refreshNode(node.element, false, ChildrenResolutionReason.Expand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setChildren(node: IAsyncDataTreeNode<T>, childrenElements: T[], recursive: boolean): void {
|
||||
const children = childrenElements.map<ITreeElement<IAsyncDataTreeNode<T>>>(element => {
|
||||
private setChildren(node: IAsyncDataTreeNode<TInput, T>, childrenElements: T[], recursive: boolean): void {
|
||||
const children = childrenElements.map<ITreeElement<IAsyncDataTreeNode<TInput, T>>>(element => {
|
||||
if (!this.identityProvider) {
|
||||
return {
|
||||
element: {
|
||||
@@ -514,7 +532,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
};
|
||||
}
|
||||
|
||||
const nodeChildren = new Map<string, IAsyncDataTreeNode<T>>();
|
||||
const nodeChildren = new Map<string, IAsyncDataTreeNode<TInput, T>>();
|
||||
|
||||
for (const child of node.children!) {
|
||||
nodeChildren.set(child.id!, child);
|
||||
@@ -556,7 +574,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
}
|
||||
}
|
||||
|
||||
let children: Iterator<ITreeElement<IAsyncDataTreeNode<T>>> | undefined = undefined;
|
||||
let children: Iterator<ITreeElement<IAsyncDataTreeNode<TInput, T>>> | undefined = undefined;
|
||||
|
||||
if (collapsible) {
|
||||
children = Iterator.map(Iterator.fromArray(asyncDataTreeNode.children!), asTreeElement);
|
||||
@@ -572,17 +590,17 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
const insertedElements = new Set<T>();
|
||||
|
||||
const onDidCreateNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
|
||||
const onDidCreateNode = (treeNode: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>) => {
|
||||
if (treeNode.element.element) {
|
||||
insertedElements.add(treeNode.element.element);
|
||||
this.nodes.set(treeNode.element.element, treeNode.element);
|
||||
insertedElements.add(treeNode.element.element as T);
|
||||
this.nodes.set(treeNode.element.element as T, treeNode.element);
|
||||
}
|
||||
};
|
||||
|
||||
const onDidDeleteNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
|
||||
const onDidDeleteNode = (treeNode: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>) => {
|
||||
if (treeNode.element.element) {
|
||||
if (!insertedElements.has(treeNode.element.element)) {
|
||||
this.nodes.delete(treeNode.element.element);
|
||||
if (!insertedElements.has(treeNode.element.element as T)) {
|
||||
this.nodes.delete(treeNode.element.element as T);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user