Merge branch 'master' into isidorn/debugTrees

This commit is contained in:
isidor
2018-11-29 15:46:52 +01:00
64 changed files with 3373 additions and 2974 deletions

View File

@@ -6,13 +6,13 @@
import { ComposedTreeDelegate, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter } from 'vs/base/browser/ui/tree/tree';
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Emitter, Event, mapEvent } from 'vs/base/common/event';
import { timeout, always } from 'vs/base/common/async';
import { ISequence } from 'vs/base/common/iterator';
import { IListStyles, IMultipleSelectionController, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { toggleClass } from 'vs/base/browser/dom';
import { Iterator } from 'vs/base/common/iterator';
export interface IDataSource<T extends NonNullable<any>> {
hasChildren(element: T | null): boolean;
@@ -29,6 +29,8 @@ enum AsyncDataTreeNodeState {
interface IAsyncDataTreeNode<T extends NonNullable<any>> {
readonly element: T | null;
readonly parent: IAsyncDataTreeNode<T> | null;
readonly id?: string | null;
readonly children?: IAsyncDataTreeNode<T>[];
state: AsyncDataTreeNodeState;
}
@@ -123,86 +125,63 @@ export interface IChildrenResolutionEvent<T> {
readonly reason: ChildrenResolutionReason;
}
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> { }
function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<T>, TFilterData> | undefined {
if (!options) {
return undefined;
}
let identityProvider: IIdentityProvider<IAsyncDataTreeNode<T>> | undefined = undefined;
if (options.identityProvider) {
const ip = options.identityProvider;
identityProvider = {
return options && {
...options,
identityProvider: options.identityProvider && {
getId(el) {
return ip.getId(el.element!);
return options.identityProvider!.getId(el.element!);
}
};
}
let multipleSelectionController: IMultipleSelectionController<IAsyncDataTreeNode<T>> | undefined = undefined;
if (options.multipleSelectionController) {
const msc = options.multipleSelectionController;
multipleSelectionController = {
},
multipleSelectionController: options.multipleSelectionController && {
isSelectionSingleChangeEvent(e) {
return msc.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
},
isSelectionRangeChangeEvent(e) {
return msc.isSelectionRangeChangeEvent({ ...e, element: e.element } as any);
return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element } as any);
}
};
}
let accessibilityProvider: IAccessibilityProvider<IAsyncDataTreeNode<T>> | undefined = undefined;
if (options.accessibilityProvider) {
const ap = options.accessibilityProvider;
accessibilityProvider = {
},
accessibilityProvider: options.accessibilityProvider && {
getAriaLabel(e) {
return ap.getAriaLabel(e.element!);
return options.accessibilityProvider!.getAriaLabel(e.element!);
}
};
}
let filter: ITreeFilter<IAsyncDataTreeNode<T>, TFilterData> | undefined = undefined;
if (options.filter) {
const f = options.filter;
filter = {
},
filter: options.filter && {
filter(element, parentVisibility) {
return f.filter(element.element!, parentVisibility);
return options.filter!.filter(element.element!, parentVisibility);
}
};
}
return {
...options,
identityProvider,
multipleSelectionController,
accessibilityProvider,
filter
}
};
}
function asTreeElement<T>(node: IAsyncDataTreeNode<T>): ITreeElement<IAsyncDataTreeNode<T>> {
return {
element: node,
children: Iterator.map(Iterator.fromArray(node.children!), asTreeElement)
};
}
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
identityProvider?: IIdentityProvider<T>;
}
export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> implements IDisposable {
private tree: ObjectTree<IAsyncDataTreeNode<T>, TFilterData>;
private root: IAsyncDataTreeNode<T>;
private nodes = new Map<T | null, IAsyncDataTreeNode<T>>();
private refreshPromises = new Map<IAsyncDataTreeNode<T>, Thenable<void>>();
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>, Thenable<void>>();
private readonly identityProvider?: IIdentityProvider<T>;
private _onDidChangeNodeState = new Emitter<IAsyncDataTreeNode<T>>();
private readonly _onDidChangeNodeState = new Emitter<IAsyncDataTreeNode<T>>();
protected disposables: IDisposable[] = [];
protected readonly disposables: IDisposable[] = [];
get onDidChangeFocus(): Event<ITreeEvent<T>> { return mapEvent(this.tree.onDidChangeFocus, asTreeEvent); }
get onDidChangeSelection(): Event<ITreeEvent<T>> { return mapEvent(this.tree.onDidChangeSelection, asTreeEvent); }
get onDidChangeCollapseState(): Event<T> { return mapEvent(this.tree.onDidChangeCollapseState, e => e.element!.element!); }
private _onDidResolveChildren = new Emitter<IChildrenResolutionEvent<T>>();
private readonly _onDidResolveChildren = new Emitter<IChildrenResolutionEvent<T>>();
readonly onDidResolveChildren: Event<IChildrenResolutionEvent<T>> = this._onDidResolveChildren.event;
get onMouseClick(): Event<ITreeMouseEvent<T>> { return mapEvent(this.tree.onMouseClick, asTreeMouseEvent); }
@@ -220,18 +199,29 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
private dataSource: IDataSource<T>,
options?: IAsyncDataTreeOptions<T, TFilterData>
) {
this.identityProvider = options && options.identityProvider;
const objectTreeDelegate = new ComposedTreeDelegate<T | null, IAsyncDataTreeNode<T>>(delegate);
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event));
const objectTreeOptions = asObjectTreeOptions(options) || {};
objectTreeOptions.collapseByDefault = true;
this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
this.root = {
element: null,
parent: null,
state: AsyncDataTreeNodeState.Uninitialized,
};
if (this.identityProvider) {
this.root = {
...this.root,
id: null,
children: [],
};
}
this.nodes.set(null, this.root);
this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables);
@@ -273,8 +263,8 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
// Data Tree
refresh(element: T | null): Thenable<void> {
return this.refreshNode(this.getDataNode(element), ChildrenResolutionReason.Refresh);
refresh(element: T | null, recursive = false): Thenable<void> {
return this.refreshNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh);
}
// Tree
@@ -289,21 +279,20 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
return this.tree.collapse(this.getDataNode(element));
}
expand(element: T): Thenable<boolean> {
async expand(element: T): Promise<boolean> {
const node = this.getDataNode(element);
if (!this.tree.isCollapsed(node)) {
return Promise.resolve(false);
}
if (node.element!.state === AsyncDataTreeNodeState.Uninitialized) {
const result = this.refreshNode(node, ChildrenResolutionReason.Expand);
this.tree.expand(node);
return result.then(() => true);
return false;
}
this.tree.expand(node);
return Promise.resolve(true);
if (node.element!.state === AsyncDataTreeNodeState.Uninitialized) {
await this.refreshNode(node, false, ChildrenResolutionReason.Expand);
}
return true;
}
toggleCollapsed(element: T): void {
@@ -390,13 +379,13 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
return node && node.element;
}
getFirstElementChild(element: T | null = null): T | null {
getFirstElementChild(element: T | null = null): T | null | undefined {
const dataNode = this.getDataNode(element);
const node = this.tree.getFirstElementChild(dataNode === this.root ? null : dataNode);
return node && node.element;
}
getLastElementAncestor(element: T | null = null): T | null {
getLastElementAncestor(element: T | null = null): T | null | undefined {
const dataNode = this.getDataNode(element);
const node = this.tree.getLastElementAncestor(dataNode === this.root ? null : dataNode);
return node && node.element;
@@ -420,23 +409,23 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
return node;
}
private refreshNode(node: IAsyncDataTreeNode<T>, reason: ChildrenResolutionReason): Thenable<void> {
private refreshNode(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Thenable<void> {
let result = this.refreshPromises.get(node);
if (result) {
return result;
}
result = this.doRefresh(node, reason);
result = this.doRefresh(node, recursive, reason);
this.refreshPromises.set(node, result);
return always(result, () => this.refreshPromises.delete(node));
}
private doRefresh(node: IAsyncDataTreeNode<T>, reason: ChildrenResolutionReason): Thenable<void> {
private doRefresh(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Thenable<void> {
const hasChildren = this.dataSource.hasChildren(node.element);
if (!hasChildren) {
this.setChildren(node === this.root ? null : node);
this.setChildren(node, [], recursive);
return Promise.resolve();
} else {
node.state = AsyncDataTreeNodeState.Loading;
@@ -455,21 +444,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
node.state = AsyncDataTreeNodeState.Loaded;
this._onDidChangeNodeState.fire(node);
const createTreeElement = (element: T): ITreeElement<IAsyncDataTreeNode<T>> => {
const collapsible = this.dataSource.hasChildren(element);
return {
element: {
element: element,
state: AsyncDataTreeNodeState.Uninitialized,
parent: node
},
collapsible
};
};
const nodeChildren = children.map<ITreeElement<IAsyncDataTreeNode<T>>>(createTreeElement);
this.setChildren(node === this.root ? null : node, nodeChildren);
this.setChildren(node, children, recursive);
this._onDidResolveChildren.fire({ element: node.element, reason });
}, err => {
slowTimeout.cancel();
@@ -487,32 +462,92 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
private _onDidChangeCollapseState(treeNode: ITreeNode<IAsyncDataTreeNode<T>, any>): void {
if (!treeNode.collapsed && treeNode.element.state === AsyncDataTreeNodeState.Uninitialized) {
this.refreshNode(treeNode.element, ChildrenResolutionReason.Expand);
this.refreshNode(treeNode.element, false, ChildrenResolutionReason.Expand);
}
}
private setChildren(element: IAsyncDataTreeNode<T> | null, children?: ISequence<ITreeElement<IAsyncDataTreeNode<T>>>): void {
private setChildren(node: IAsyncDataTreeNode<T>, childrenElements: T[], recursive: boolean): void {
const children = childrenElements.map<ITreeElement<IAsyncDataTreeNode<T>>>(element => {
if (!this.identityProvider) {
return {
element: {
element,
parent: node,
state: AsyncDataTreeNodeState.Uninitialized
},
collapsible: this.dataSource.hasChildren(element),
collapsed: true
};
}
const nodeChildren = new Map<string, IAsyncDataTreeNode<T>>();
for (const child of node.children!) {
nodeChildren.set(child.id!, child);
}
const id = this.identityProvider.getId(element).toString();
const asyncDataTreeNode = nodeChildren.get(id);
if (!asyncDataTreeNode) {
return {
element: {
element,
parent: node,
id,
children: [],
state: AsyncDataTreeNodeState.Uninitialized
},
collapsible: this.dataSource.hasChildren(element),
collapsed: true
};
}
// TODO
// if (recursive) {
// asyncDataTreeNode.state = AsyncDataTreeNodeState.Uninitialized;
// if (this.tree.isCollapsed(asyncDataTreeNode)) {
// asyncDataTreeNode.children!.length = 0;
// return {
// element: asyncDataTreeNode,
// collapsible: this.dataSource.hasChildren(element),
// };
// }
// }
return {
element: asyncDataTreeNode,
children: Iterator.map(Iterator.fromArray(asyncDataTreeNode.children!), asTreeElement)
};
});
const insertedElements = new Set<T>();
const onDidCreateNode = (node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
if (node.element.element) {
insertedElements.add(node.element.element);
this.nodes.set(node.element.element, node.element);
const onDidCreateNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
if (treeNode.element.element) {
insertedElements.add(treeNode.element.element);
this.nodes.set(treeNode.element.element, treeNode.element);
}
};
const onDidDeleteNode = (node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
if (node.element.element) {
if (!insertedElements.has(node.element.element)) {
this.nodes.delete(node.element.element);
const onDidDeleteNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
if (treeNode.element.element) {
if (!insertedElements.has(treeNode.element.element)) {
this.nodes.delete(treeNode.element.element);
}
}
};
this.tree.setChildren(element, children, onDidCreateNode, onDidDeleteNode);
this.tree.setChildren(node === this.root ? null : node, children, onDidCreateNode, onDidDeleteNode);
if (this.identityProvider) {
node.children!.splice(0, node.children!.length, ...children.map(c => c.element));
}
}
dispose(): void {
this.disposables = dispose(this.disposables);
dispose(this.disposables);
}
}