提交 419d5bba 编写于 作者: J Joao Moreno

AsyncDataTree<TInput...

related to #65091
上级 8c09d4f4
......@@ -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);
}
}
};
......
......@@ -17,17 +17,7 @@ export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOp
export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | null, TFilterData, TInput | T> {
protected model: ObjectTreeModel<T | null, TFilterData>;
private _input: TInput | undefined;
get input(): TInput | undefined {
return this._input;
}
set input(input: TInput | undefined) {
this._input = input;
this.refresh(input);
}
private input: TInput | undefined;
constructor(
container: HTMLElement,
......@@ -39,8 +29,22 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
super(container, delegate, renderers, options);
}
refresh(element: TInput | T): void {
if (!this._input) {
getInput(): TInput | undefined {
return this.input;
}
setInput(input: TInput | undefined): void {
this.input = input;
if (typeof input === 'undefined') {
return;
}
this.refresh(input);
}
refresh(element: TInput | T = this.input): void {
if (typeof this.input === 'undefined') {
throw new Error('Tree input not set');
}
......
......@@ -149,9 +149,9 @@ export interface IDataSource<TInput, T> {
getChildren(element: TInput | T): T[];
}
export interface IAsyncDataSource<T extends NonNullable<any>> {
hasChildren(element: T | null): boolean;
getChildren(element: T | null): T[] | Promise<T[]>;
export interface IAsyncDataSource<TInput, T> {
hasChildren(element: TInput | T): boolean;
getChildren(element: TInput | T): T[] | Promise<T[]>;
}
/**
......
......@@ -51,15 +51,11 @@ suite('AsyncDataTree', function () {
}
};
const dataSource = new class implements IAsyncDataSource<Element> {
hasChildren(element: Element | null): boolean {
return !element || (element.children && element.children.length > 0);
const dataSource = new class implements IAsyncDataSource<Element, Element> {
hasChildren(element: Element): boolean {
return element.children && element.children.length > 0;
}
getChildren(element: Element | null): Promise<Element[]> {
if (!element) {
return Promise.resolve(root.children);
}
getChildren(element: Element): Promise<Element[]> {
return Promise.resolve(element.children || []);
}
};
......@@ -79,11 +75,11 @@ suite('AsyncDataTree', function () {
const _: (id: string) => Element = find.bind(null, root.children);
const tree = new AsyncDataTree(container, delegate, [renderer], dataSource, { identityProvider });
const tree = new AsyncDataTree<Element, Element>(container, delegate, [renderer], dataSource, { identityProvider });
tree.layout(200);
assert.equal(container.querySelectorAll('.monaco-list-row').length, 0);
await tree.refresh(null);
await tree.setInput(root);
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(!hasClass(twistie, 'collapsible'));
......
......@@ -26,18 +26,12 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
export type TreeElement = FileReferences | OneReference;
export class DataSource implements IAsyncDataSource<TreeElement> {
export class DataSource implements IAsyncDataSource<ReferencesModel | FileReferences, TreeElement> {
root: ReferencesModel | FileReferences;
constructor(@ITextModelService private readonly _resolverService: ITextModelService) { }
constructor(
@ITextModelService private readonly _resolverService: ITextModelService,
) {
//
}
hasChildren(element: TreeElement): boolean {
if (!element) {
hasChildren(element: ReferencesModel | FileReferences | TreeElement): boolean {
if (element instanceof ReferencesModel) {
return true;
}
if (element instanceof FileReferences && !element.failure) {
......@@ -46,10 +40,11 @@ export class DataSource implements IAsyncDataSource<TreeElement> {
return false;
}
getChildren(element: TreeElement): Promise<TreeElement[]> {
if (!element && this.root instanceof FileReferences) {
element = this.root;
getChildren(element: ReferencesModel | FileReferences | TreeElement): TreeElement[] | Promise<TreeElement[]> {
if (element instanceof ReferencesModel) {
return element.groups;
}
if (element instanceof FileReferences) {
return element.resolve(this._resolverService).then(val => {
// if (element.failure) {
......@@ -60,9 +55,7 @@ export class DataSource implements IAsyncDataSource<TreeElement> {
return val.children;
});
}
if (this.root instanceof ReferencesModel) {
return Promise.resolve(this.root.groups);
}
throw new Error('bad tree');
}
}
......
......@@ -237,8 +237,7 @@ export class ReferenceWidget extends PeekViewWidget {
private _callOnDispose: IDisposable[] = [];
private _onDidSelectReference = new Emitter<SelectionEvent>();
private _treeDataSource: DataSource;
private _tree: WorkbenchAsyncDataTree<TreeElement>;
private _tree: WorkbenchAsyncDataTree<ReferencesModel | FileReferences, TreeElement>;
private _treeContainer: HTMLElement;
private _sash: VSash;
private _preview: ICodeEditor;
......@@ -357,14 +356,14 @@ export class ReferenceWidget extends PeekViewWidget {
accessibilityProvider: new AriaProvider()
};
this._treeDataSource = this._instantiationService.createInstance(DataSource);
const treeDataSource = this._instantiationService.createInstance(DataSource);
this._tree = this._instantiationService.createInstance<HTMLElement, IListVirtualDelegate<TreeElement>, ITreeRenderer<any, void, any>[], IAsyncDataSource<TreeElement>, IAsyncDataTreeOptions<TreeElement, void>, WorkbenchAsyncDataTree<TreeElement, void>>(
this._tree = this._instantiationService.createInstance<HTMLElement, IListVirtualDelegate<TreeElement>, ITreeRenderer<any, void, any>[], IAsyncDataSource<ReferencesModel | FileReferences, TreeElement>, IAsyncDataTreeOptions<TreeElement, void>, WorkbenchAsyncDataTree<ReferencesModel | FileReferences, TreeElement, void>>(
WorkbenchAsyncDataTree,
this._treeContainer,
new Delegate(),
renderers,
this._treeDataSource,
treeDataSource,
treeOptions
);
......@@ -498,8 +497,7 @@ export class ReferenceWidget extends PeekViewWidget {
this.focus();
// pick input and a reference to begin with
this._treeDataSource.root = this._model.groups.length === 1 ? this._model.groups[0] : this._model;
return this._tree.refresh(null);
return this._tree.setInput(this._model.groups.length === 1 ? this._model.groups[0] : this._model);
}
private _getFocusedReference(): OneReference {
......@@ -533,7 +531,7 @@ export class ReferenceWidget extends PeekViewWidget {
const promise = this._textModelResolverService.createModelReference(reference.uri);
if (this._treeDataSource.root === reference.parent) {
if (this._tree.getInput() === reference.parent) {
this._tree.reveal(reference);
} else {
if (revealParent) {
......
......@@ -35,7 +35,7 @@ import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTr
import { ITreeEvent, ITreeRenderer, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { AsyncDataTree, IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
export type ListWidget = List<any> | PagedList<any> | ITree | ObjectTree<any, any> | AsyncDataTree<any, any>;
export type ListWidget = List<any> | PagedList<any> | ITree | ObjectTree<any, any> | AsyncDataTree<any, any, any>;
export const IListService = createDecorator<IListService>('listService');
......@@ -590,7 +590,7 @@ export class TreeResourceNavigator2<T, TFilterData> extends Disposable {
private readonly _openResource: Emitter<IOpenEvent<T>> = new Emitter<IOpenEvent<T>>();
readonly openResource: Event<IOpenEvent<T>> = this._openResource.event;
constructor(private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchAsyncDataTree<T, TFilterData>, private options?: IResourceResultsNavigationOptions) {
constructor(private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchAsyncDataTree<any, T, TFilterData>, private options?: IResourceResultsNavigationOptions) {
super();
this.registerListeners();
......@@ -955,7 +955,7 @@ export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void>
}
}
export class WorkbenchAsyncDataTree<T extends NonNullable<any>, TFilterData = void> extends AsyncDataTree<T, TFilterData> {
export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
readonly contextKeyService: IContextKeyService;
......@@ -969,7 +969,7 @@ export class WorkbenchAsyncDataTree<T extends NonNullable<any>, TFilterData = vo
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
dataSource: IAsyncDataSource<T>,
dataSource: IAsyncDataSource<TInput, T>,
options: IAsyncDataTreeOptions<T, TFilterData>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
......
......@@ -186,7 +186,7 @@ export class CollapseAction extends Action {
// Collapse All action for the new tree
export class CollapseAction2 extends Action {
constructor(tree: AsyncDataTree<any>, enabled: boolean, clazz: string) {
constructor(tree: AsyncDataTree<any, any>, enabled: boolean, clazz: string) {
super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, () => {
tree.collapseAll();
return Promise.resolve(undefined);
......
......@@ -804,7 +804,7 @@ export class ReverseContinueAction extends AbstractDebugAction {
}
export class ReplCollapseAllAction extends CollapseAction2 {
constructor(tree: AsyncDataTree<any>, private toFocus: { focus(): void; }) {
constructor(tree: AsyncDataTree<any, any>, private toFocus: { focus(): void; }) {
super(tree, true, undefined);
}
......
......@@ -362,7 +362,7 @@ export class LoadedScriptsView extends ViewletPanel {
private treeContainer: HTMLElement;
private loadedScriptsItemType: IContextKey<string>;
private tree: WorkbenchAsyncDataTree<any>;
private tree: WorkbenchAsyncDataTree<LoadedScriptsItem, LoadedScriptsItem>;
private changeScheduler: RunOnceScheduler;
private treeNeedsRefreshOnVisible: boolean;
private filter: LoadedScriptsFilter;
......@@ -399,7 +399,7 @@ export class LoadedScriptsView extends ViewletPanel {
[
this.instantiationService.createInstance(LoadedScriptsRenderer)
],
new LoadedScriptsDataSource(root),
new LoadedScriptsDataSource(),
{
identityProvider: {
getId: element => element.getId()
......@@ -414,10 +414,12 @@ export class LoadedScriptsView extends ViewletPanel {
this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService
);
this.tree.setInput(root);
this.changeScheduler = new RunOnceScheduler(() => {
this.treeNeedsRefreshOnVisible = false;
if (this.tree) {
this.tree.refresh(null);
this.tree.refresh();
}
}, 300);
this.disposables.push(this.changeScheduler);
......@@ -535,19 +537,13 @@ class LoadedScriptsDelegate implements IListVirtualDelegate<LoadedScriptsItem> {
}
}
class LoadedScriptsDataSource implements IAsyncDataSource<LoadedScriptsItem> {
constructor(private root: LoadedScriptsItem) {
}
class LoadedScriptsDataSource implements IAsyncDataSource<LoadedScriptsItem, LoadedScriptsItem> {
hasChildren(element: LoadedScriptsItem | null): boolean {
return element === null || element.hasChildren();
hasChildren(element: LoadedScriptsItem): boolean {
return element.hasChildren();
}
getChildren(element: LoadedScriptsItem | null): Promise<LoadedScriptsItem[]> {
if (element === null) {
element = this.root;
}
getChildren(element: LoadedScriptsItem): Promise<LoadedScriptsItem[]> {
return element.getChildren();
}
}
......
......@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { RunOnceScheduler, ignoreErrors } from 'vs/base/common/async';
import * as dom from 'vs/base/browser/dom';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE } from 'vs/workbench/parts/debug/common/debug';
import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE, IDebugModel } from 'vs/workbench/parts/debug/common/debug';
import { Thread, StackFrame, ThreadAndSessionIds } from 'vs/workbench/parts/debug/common/debugModel';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......@@ -45,7 +45,7 @@ export class CallStackView extends ViewletPanel {
private ignoreFocusStackFrameEvent: boolean;
private callStackItemType: IContextKey<string>;
private dataSource: CallStackDataSource;
private tree: WorkbenchAsyncDataTree<CallStackItem>;
private tree: WorkbenchAsyncDataTree<IDebugModel, CallStackItem>;
private contributedContextMenu: IMenu;
constructor(
......@@ -84,7 +84,7 @@ export class CallStackView extends ViewletPanel {
this.needsRefresh = false;
this.dataSource.deemphasizedStackFramesToShow = [];
this.tree.refresh(null).then(() => this.updateTreeSelection());
this.tree.refresh().then(() => this.updateTreeSelection());
}, 50);
}
......@@ -101,7 +101,7 @@ export class CallStackView extends ViewletPanel {
dom.addClass(container, 'debug-call-stack');
const treeContainer = renderViewTree(container);
this.dataSource = new CallStackDataSource(this.debugService);
this.dataSource = new CallStackDataSource();
this.tree = new WorkbenchAsyncDataTree(treeContainer, new CallStackDelegate(), [
new SessionsRenderer(),
new ThreadsRenderer(),
......@@ -144,6 +144,9 @@ export class CallStackView extends ViewletPanel {
}
}, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService);
// TODO@isidor this is a promise
this.tree.setInput(this.debugService.getModel());
const callstackNavigator = new TreeResourceNavigator2(this.tree);
this.disposables.push(callstackNavigator);
this.disposables.push(callstackNavigator.openResource(e => {
......@@ -176,12 +179,12 @@ export class CallStackView extends ViewletPanel {
const thread = session && session.getThread(element.threadId);
if (thread) {
(<Thread>thread).fetchCallStack()
.then(() => this.tree.refresh(null));
.then(() => this.tree.refresh());
}
}
if (element instanceof Array) {
this.dataSource.deemphasizedStackFramesToShow.push(...element);
this.tree.refresh(null);
this.tree.refresh();
}
}));
......@@ -571,19 +574,20 @@ class CallStackDelegate implements IListVirtualDelegate<CallStackItem> {
}
}
class CallStackDataSource implements IAsyncDataSource<CallStackItem> {
deemphasizedStackFramesToShow: IStackFrame[];
function isDebugModel(obj: any): obj is IDebugModel {
return typeof obj.getSessions === 'function';
}
constructor(private debugService: IDebugService) { }
class CallStackDataSource implements IAsyncDataSource<IDebugModel, CallStackItem> {
deemphasizedStackFramesToShow: IStackFrame[];
hasChildren(element: CallStackItem | null): boolean {
return element === null || element instanceof DebugSession || (element instanceof Thread && element.stopped);
hasChildren(element: IDebugModel | CallStackItem): boolean {
return isDebugModel(element) || element instanceof DebugSession || (element instanceof Thread && element.stopped);
}
getChildren(element: CallStackItem | null): Promise<CallStackItem[]> {
if (element === null) {
const model = this.debugService.getModel();
const sessions = model.getSessions();
getChildren(element: IDebugModel | CallStackItem): Promise<CallStackItem[]> {
if (isDebugModel(element)) {
const sessions = element.getSessions();
if (sessions.length === 0) {
return Promise.resolve([]);
}
......@@ -594,12 +598,11 @@ class CallStackDataSource implements IAsyncDataSource<CallStackItem> {
const threads = sessions[0].getAllThreads();
// Only show the threads in the call stack if there is more than 1 thread.
return threads.length === 1 ? this.getThreadChildren(<Thread>threads[0]) : Promise.resolve(threads);
}
if (element instanceof DebugSession) {
} else if (element instanceof DebugSession) {
return Promise.resolve(element.getAllThreads());
} else {
return this.getThreadChildren(<Thread>element);
}
return this.getThreadChildren(<Thread>element);
}
private getThreadChildren(thread: Thread): Promise<Array<IStackFrame | string | ThreadAndSessionIds>> {
......
......@@ -45,7 +45,7 @@ export class DebugHoverWidget implements IContentWidget {
private _isVisible: boolean;
private domNode: HTMLElement;
private tree: AsyncDataTree<IExpression>;
private tree: AsyncDataTree<IExpression, IExpression>;
private showAtPosition: Position;
private highlightDecorations: string[];
private complexValueContainer: HTMLElement;
......@@ -237,9 +237,8 @@ export class DebugHoverWidget implements IContentWidget {
this.valueContainer.hidden = true;
this.complexValueContainer.hidden = false;
this.dataSource.expression = expression;
return this.tree.refresh(null).then(() => {
return this.tree.setInput(expression).then(() => {
this.complexValueTitle.textContent = expression.value;
this.complexValueTitle.title = expression.value;
this.layoutTreeAndContainer();
......@@ -291,19 +290,13 @@ class DebugHoverAccessibilityProvider implements IAccessibilityProvider<IExpress
}
}
class DebugHoverDataSource implements IAsyncDataSource<IExpression> {
class DebugHoverDataSource implements IAsyncDataSource<IExpression, IExpression> {
expression: IExpression;
hasChildren(element: IExpression | null): boolean {
return element === null || element.hasChildren;
hasChildren(element: IExpression): boolean {
return element.hasChildren;
}
getChildren(element: IExpression | null): Promise<IExpression[]> {
if (element === null) {
element = this.expression;
}
getChildren(element: IExpression): Promise<IExpression[]> {
return element.getChildren();
}
}
......
......@@ -81,7 +81,7 @@ interface IPrivateReplService {
clearRepl(): void;
}
function revealLastElement<T>(tree: WorkbenchAsyncDataTree<T>) {
function revealLastElement(tree: WorkbenchAsyncDataTree<any, any>) {
tree.scrollTop = tree.scrollHeight - tree.renderHeight;
}
......@@ -95,7 +95,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
private static readonly REPL_INPUT_MAX_HEIGHT = 170;
private history: HistoryNavigator<string>;
private tree: WorkbenchAsyncDataTree<IReplElement>;
private tree: WorkbenchAsyncDataTree<null, IReplElement>;
private dataSource: ReplDataSource;
private replDelegate: ReplDelegate;
private container: HTMLElement;
......@@ -213,7 +213,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
if (this.tree && this.dataSource.input !== session) {
this.dataSource.input = session;
this.tree.refresh(null).then(() => revealLastElement(this.tree));
this.tree.refresh().then(() => revealLastElement(this.tree));
}
}
......@@ -331,7 +331,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
private get refreshScheduler(): RunOnceScheduler {
return new RunOnceScheduler(() => {
const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight;
this.tree.refresh(null).then(() => {
this.tree.refresh().then(() => {
if (lastElementVisible) {
// Only scroll if we were scrolled all the way down before tree refreshed #10486
revealLastElement(this.tree);
......@@ -363,6 +363,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e }
}, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService);
this.tree.setInput(null);
this.toDispose.push(this.tree.onContextMenu(e => this.onContextMenu(e)));
// Make sure to select the session if debugging is already active
this.selectSession();
......@@ -750,7 +752,7 @@ class ReplDelegate implements IListVirtualDelegate<IReplElement> {
}
class ReplDataSource implements IAsyncDataSource<IReplElement> {
class ReplDataSource implements IAsyncDataSource<null, IReplElement> {
input: IDebugSession;
hasChildren(element: IReplElement | null): boolean {
......
......@@ -8,7 +8,7 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import * as dom from 'vs/base/browser/dom';
import { CollapseAction2 } from 'vs/workbench/browser/viewlet';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED } from 'vs/workbench/parts/debug/common/debug';
import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewModel } from 'vs/workbench/parts/debug/common/debug';
import { Variable, Scope } from 'vs/workbench/parts/debug/common/debugModel';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
......@@ -37,7 +37,7 @@ export class VariablesView extends ViewletPanel {
private onFocusStackFrameScheduler: RunOnceScheduler;
private needsRefresh: boolean;
private tree: WorkbenchAsyncDataTree<IExpression | IScope>;
private tree: WorkbenchAsyncDataTree<IViewModel, IExpression | IScope>;
constructor(
options: IViewletViewOptions,
......@@ -55,7 +55,7 @@ export class VariablesView extends ViewletPanel {
// Use scheduler to prevent unnecessary flashing
this.onFocusStackFrameScheduler = new RunOnceScheduler(() => {
this.needsRefresh = false;
this.tree.refresh(null).then(() => {
this.tree.refresh().then(() => {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
if (stackFrame) {
stackFrame.getScopes().then(scopes => {
......@@ -75,18 +75,21 @@ export class VariablesView extends ViewletPanel {
this.tree = new WorkbenchAsyncDataTree(treeContainer, new VariablesDelegate(),
[this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer()],
new VariablesDataSource(this.debugService), {
new VariablesDataSource(), {
ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"),
accessibilityProvider: new VariablesAccessibilityProvider(),
identityProvider: { getId: element => element.getId() },
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e }
}, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService);
// TODO@isidor this is a promise
this.tree.setInput(this.debugService.getViewModel());
CONTEXT_VARIABLES_FOCUSED.bindTo(this.contextKeyService.createScoped(treeContainer));
const collapseAction = new CollapseAction2(this.tree, true, 'explorer-action collapse-explorer');
this.toolbar.setActions([collapseAction])();
this.tree.refresh(null);
this.tree.refresh();
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(sf => {
if (!this.isVisible() || !this.isExpanded()) {
......@@ -99,7 +102,7 @@ export class VariablesView extends ViewletPanel {
const timeout = sf.explicit ? 0 : undefined;
this.onFocusStackFrameScheduler.schedule(timeout);
}));
this.disposables.push(variableSetEmitter.event(() => this.tree.refresh(null)));
this.disposables.push(variableSetEmitter.event(() => this.tree.refresh()));
this.disposables.push(this.tree.onMouseDblClick(e => this.onMouseDblClick(e)));
this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e)));
}
......@@ -149,21 +152,23 @@ export class VariablesView extends ViewletPanel {
}
}
export class VariablesDataSource implements IAsyncDataSource<IExpression | IScope> {
function isViewModel(obj: any): obj is IViewModel {
return typeof obj.getSelectedExpression === 'function';
}
constructor(private debugService: IDebugService) { }
export class VariablesDataSource implements IAsyncDataSource<IViewModel, IExpression | IScope> {
hasChildren(element: IExpression | IScope | null): boolean {
if (element === null || element instanceof Scope) {
hasChildren(element: IViewModel | IExpression | IScope): boolean {
if (isViewModel(element) || element instanceof Scope) {
return true;
}
return element.hasChildren;
}
getChildren(element: IExpression | IScope | null): Promise<Array<IExpression | IScope>> {
if (element === null) {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
getChildren(element: IViewModel | IExpression | IScope): Promise<(IExpression | IScope)[]> {
if (isViewModel(element)) {
const stackFrame = element.focusedStackFrame;
return stackFrame ? stackFrame.getScopes() : Promise.resolve([]);
}
......
......@@ -34,7 +34,7 @@ export class WatchExpressionsView extends ViewletPanel {
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
private needsRefresh: boolean;
private tree: WorkbenchAsyncDataTree<IExpression>;
private tree: WorkbenchAsyncDataTree<IDebugService, IExpression>;
constructor(
options: IViewletViewOptions,
......@@ -51,7 +51,7 @@ export class WatchExpressionsView extends ViewletPanel {
this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
this.needsRefresh = false;
this.tree.refresh(null);
this.tree.refresh();
}, 50);
}
......@@ -63,14 +63,15 @@ export class WatchExpressionsView extends ViewletPanel {
const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer);
this.disposables.push(expressionsRenderer);
this.tree = new WorkbenchAsyncDataTree(treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)],
new WatchExpressionsDataSource(this.debugService), {
new WatchExpressionsDataSource(), {
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"),
accessibilityProvider: new WatchExpressionsAccessibilityProvider(),
identityProvider: { getId: element => element.getId() },
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e }
}, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService);
this.tree.refresh(null);
// TODO@isidor this is a promise
this.tree.setInput(this.debugService);
const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService);
const collapseAction = new CollapseAction2(this.tree, true, 'explorer-action collapse-explorer');
......@@ -83,7 +84,7 @@ export class WatchExpressionsView extends ViewletPanel {
if (!this.isExpanded() || !this.isVisible()) {
this.needsRefresh = true;
} else {
this.tree.refresh(null);
this.tree.refresh();
}
}));
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
......@@ -96,7 +97,7 @@ export class WatchExpressionsView extends ViewletPanel {
this.onWatchExpressionsUpdatedScheduler.schedule();
}
}));
this.disposables.push(variableSetEmitter.event(() => this.tree.refresh(null)));
this.disposables.push(variableSetEmitter.event(() => this.tree.refresh()));
}
layoutBody(size: number): void {
......@@ -186,22 +187,21 @@ class WatchExpressionsDelegate implements IListVirtualDelegate<IExpression> {
}
}
class WatchExpressionsDataSource implements IAsyncDataSource<IExpression> {
function isDebugService(element: any): element is IDebugService {
return typeof element.getConfigurationManager === 'function';
}
constructor(private debugService: IDebugService) { }
class WatchExpressionsDataSource implements IAsyncDataSource<IDebugService, IExpression> {
hasChildren(element: IExpression | null): boolean {
if (element === null) {
return true;
}
return element.hasChildren;
return isDebugService(element) || element.hasChildren;
}
getChildren(element: IExpression | null): Promise<Array<IExpression>> {
if (element === null) {
const watchExpressions = this.debugService.getModel().getWatchExpressions();
const viewModel = this.debugService.getViewModel();
getChildren(element: IDebugService | IExpression): Promise<Array<IExpression>> {
if (isDebugService(element)) {
const debugService = element as IDebugService;
const watchExpressions = debugService.getModel().getWatchExpressions();
const viewModel = debugService.getViewModel();
return Promise.all(watchExpressions.map(we => !!we.name
? we.evaluate(viewModel.focusedSession, viewModel.focusedStackFrame, 'watch').then(() => we)
: Promise.resolve(we)));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册