From 419d5bbaedebef63cff9bc248f8407cda5d40562 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 17 Dec 2018 15:42:26 +0100 Subject: [PATCH] AsyncDataTree> { - element: T | null; - readonly parent: IAsyncDataTreeNode | null; +interface IAsyncDataTreeNode { + element: TInput | T; + readonly parent: IAsyncDataTreeNode | null; readonly id?: string | null; - readonly children?: IAsyncDataTreeNode[]; + readonly children?: IAsyncDataTreeNode[]; state: AsyncDataTreeNodeState; } @@ -33,9 +33,9 @@ interface IDataTreeListTemplateData { templateData: T; } -class AsyncDataTreeNodeWrapper implements ITreeNode { +class AsyncDataTreeNodeWrapper implements ITreeNode { - get element(): T { return this.node.element!.element!; } + get element(): T { return this.node.element!.element as T; } get parent(): ITreeNode | undefined { return this.node.parent && new AsyncDataTreeNodeWrapper(this.node.parent); } get children(): ITreeNode[] { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); } get depth(): number { return this.node.depth; } @@ -44,18 +44,18 @@ class AsyncDataTreeNodeWrapper implements ITreeNode | null, TFilterData>) { } + constructor(private node: ITreeNode | null, TFilterData>) { } } -class DataTreeRenderer implements ITreeRenderer, TFilterData, IDataTreeListTemplateData> { +class DataTreeRenderer implements ITreeRenderer, TFilterData, IDataTreeListTemplateData> { readonly templateId: string; - private renderedNodes = new Map, IDataTreeListTemplateData>(); + private renderedNodes = new Map, IDataTreeListTemplateData>(); private disposables: IDisposable[] = []; constructor( private renderer: ITreeRenderer, - readonly onDidChangeTwistieState: Event> + readonly onDidChangeTwistieState: Event> ) { this.templateId = renderer.templateId; } @@ -65,16 +65,16 @@ class DataTreeRenderer implements ITreeRenderer, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { + renderElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData); } - renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { + renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { toggleClass(twistieElement, 'loading', element.state === AsyncDataTreeNodeState.Slow); return false; } - disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { + disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { if (this.renderer.disposeElement) { this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData); } @@ -90,24 +90,24 @@ class DataTreeRenderer implements ITreeRenderer(e: ITreeEvent>): ITreeEvent { +function asTreeEvent(e: ITreeEvent>): ITreeEvent { return { browserEvent: e.browserEvent, - elements: e.elements.map(e => e.element!) + elements: e.elements.map(e => e.element as T) }; } -function asTreeMouseEvent(e: ITreeMouseEvent>): ITreeMouseEvent { +function asTreeMouseEvent(e: ITreeMouseEvent>): ITreeMouseEvent { return { browserEvent: e.browserEvent, - element: e.element && e.element.element! + element: e.element && e.element.element as T }; } -function asTreeContextMenuEvent(e: ITreeContextMenuEvent>): ITreeContextMenuEvent { +function asTreeContextMenuEvent(e: ITreeContextMenuEvent>): ITreeContextMenuEvent { 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 { readonly reason: ChildrenResolutionReason; } -function asObjectTreeOptions(options?: IAsyncDataTreeOptions): IObjectTreeOptions, TFilterData> | undefined { +function asObjectTreeOptions(options?: IAsyncDataTreeOptions): IObjectTreeOptions, 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(options?: IAsyncDataTreeOptions(node: IAsyncDataTreeNode): ITreeElement> { +function asTreeElement(node: IAsyncDataTreeNode): ITreeElement> { return { element: node, children: Iterator.map(Iterator.fromArray(node.children!), asTreeElement) @@ -173,15 +173,15 @@ export interface IAsyncDataTreeOptions extends IAbstractT sorter?: ITreeSorter; } -export class AsyncDataTree, TFilterData = void> implements IDisposable { +export class AsyncDataTree implements IDisposable { - private readonly tree: ObjectTree, TFilterData>; - private readonly root: IAsyncDataTreeNode; - private readonly nodes = new Map>(); - private readonly refreshPromises = new Map, Promise>(); + private readonly tree: ObjectTree, TFilterData>; + private readonly root: IAsyncDataTreeNode; + private readonly nodes = new Map>(); + private readonly refreshPromises = new Map, Promise>(); private readonly identityProvider?: IIdentityProvider; - private readonly _onDidChangeNodeState = new Emitter>(); + private readonly _onDidChangeNodeState = new Emitter>(); protected readonly disposables: IDisposable[] = []; @@ -204,20 +204,20 @@ export class AsyncDataTree, TFilterData = void> imple container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], - private dataSource: IAsyncDataSource, + private dataSource: IAsyncDataSource, options?: IAsyncDataTreeOptions ) { this.identityProvider = options && options.identityProvider; - const objectTreeDelegate = new ComposedTreeDelegate>(delegate); + const objectTreeDelegate = new ComposedTreeDelegate>(delegate); const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event)); - const objectTreeOptions = asObjectTreeOptions(options) || {}; + const objectTreeOptions = asObjectTreeOptions(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, TFilterData = void> imple // Data Tree - refresh(element: T | null, recursive = true): Promise { + getInput(): TInput | undefined { + return this.root.element as TInput; + } + + setInput(input: TInput | undefined): Promise { + 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 { + 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 { + getNode(element: TInput | T): ITreeNode { const dataNode = this.getDataNode(element); const node = this.tree.getNode(dataNode === this.root ? null : dataNode); - return new AsyncDataTreeNodeWrapper(node); + return new AsyncDataTreeNodeWrapper(node); } collapse(element: T, recursive: boolean = false): boolean { @@ -342,7 +360,7 @@ export class AsyncDataTree, 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, 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, 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, TFilterData = void> imple // Implementation - private getDataNode(element: T | null): IAsyncDataTreeNode { - const node: IAsyncDataTreeNode = this.nodes.get(element); + private getDataNode(element: TInput | T): IAsyncDataTreeNode { + const node: IAsyncDataTreeNode = 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, TFilterData = void> imple return node; } - private async refreshNode(node: IAsyncDataTreeNode, recursive: boolean, reason: ChildrenResolutionReason): Promise { + private async refreshNode(node: IAsyncDataTreeNode, recursive: boolean, reason: ChildrenResolutionReason): Promise { await this._refreshNode(node, recursive, reason); if (recursive && node.children) { @@ -437,7 +455,7 @@ export class AsyncDataTree, TFilterData = void> imple } } - private _refreshNode(node: IAsyncDataTreeNode, recursive: boolean, reason: ChildrenResolutionReason): Promise { + private _refreshNode(node: IAsyncDataTreeNode, recursive: boolean, reason: ChildrenResolutionReason): Promise { let result = this.refreshPromises.get(node); if (result) { @@ -449,8 +467,8 @@ export class AsyncDataTree, TFilterData = void> imple return always(result, () => this.refreshPromises.delete(node)); } - private doRefresh(node: IAsyncDataTreeNode, recursive: boolean, reason: ChildrenResolutionReason): Promise { - const hasChildren = !!this.dataSource.hasChildren(node.element); + private doRefresh(node: IAsyncDataTreeNode, recursive: boolean, reason: ChildrenResolutionReason): Promise { + const hasChildren = !!this.dataSource.hasChildren(node.element!); if (!hasChildren) { this.setChildren(node, [], recursive); @@ -468,14 +486,14 @@ export class AsyncDataTree, 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, TFilterData = void> imple } } - private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent, any>): void { + private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent, 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, childrenElements: T[], recursive: boolean): void { - const children = childrenElements.map>>(element => { + private setChildren(node: IAsyncDataTreeNode, childrenElements: T[], recursive: boolean): void { + const children = childrenElements.map>>(element => { if (!this.identityProvider) { return { element: { @@ -514,7 +532,7 @@ export class AsyncDataTree, TFilterData = void> imple }; } - const nodeChildren = new Map>(); + const nodeChildren = new Map>(); for (const child of node.children!) { nodeChildren.set(child.id!, child); @@ -556,7 +574,7 @@ export class AsyncDataTree, TFilterData = void> imple } } - let children: Iterator>> | undefined = undefined; + let children: Iterator>> | undefined = undefined; if (collapsible) { children = Iterator.map(Iterator.fromArray(asyncDataTreeNode.children!), asTreeElement); @@ -572,17 +590,17 @@ export class AsyncDataTree, TFilterData = void> imple const insertedElements = new Set(); - const onDidCreateNode = (treeNode: ITreeNode, TFilterData>) => { + const onDidCreateNode = (treeNode: ITreeNode, 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, TFilterData>) => { + const onDidDeleteNode = (treeNode: ITreeNode, 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); } } }; diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index 6684ef46bba..4a3b4a43a28 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -17,17 +17,7 @@ export interface IDataTreeOptions extends IAbstractTreeOp export class DataTree extends AbstractTree { protected model: ObjectTreeModel; - - 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 extends AbstractTree { getChildren(element: TInput | T): T[]; } -export interface IAsyncDataSource> { - hasChildren(element: T | null): boolean; - getChildren(element: T | null): T[] | Promise; +export interface IAsyncDataSource { + hasChildren(element: TInput | T): boolean; + getChildren(element: TInput | T): T[] | Promise; } /** diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index 3c3a6119a16..670254527a5 100644 --- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -51,15 +51,11 @@ suite('AsyncDataTree', function () { } }; - const dataSource = new class implements IAsyncDataSource { - hasChildren(element: Element | null): boolean { - return !element || (element.children && element.children.length > 0); + const dataSource = new class implements IAsyncDataSource { + hasChildren(element: Element): boolean { + return element.children && element.children.length > 0; } - getChildren(element: Element | null): Promise { - if (!element) { - return Promise.resolve(root.children); - } - + getChildren(element: Element): Promise { 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(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')); diff --git a/src/vs/editor/contrib/referenceSearch/referencesTree.ts b/src/vs/editor/contrib/referenceSearch/referencesTree.ts index b52a1640090..73508ceaaa4 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesTree.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesTree.ts @@ -26,18 +26,12 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; export type TreeElement = FileReferences | OneReference; -export class DataSource implements IAsyncDataSource { +export class DataSource implements IAsyncDataSource { - 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 { return false; } - getChildren(element: TreeElement): Promise { - if (!element && this.root instanceof FileReferences) { - element = this.root; + getChildren(element: ReferencesModel | FileReferences | TreeElement): TreeElement[] | Promise { + 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 { return val.children; }); } - if (this.root instanceof ReferencesModel) { - return Promise.resolve(this.root.groups); - } + throw new Error('bad tree'); } } diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index fbc07d6ae7f..403567a3094 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -237,8 +237,7 @@ export class ReferenceWidget extends PeekViewWidget { private _callOnDispose: IDisposable[] = []; private _onDidSelectReference = new Emitter(); - private _treeDataSource: DataSource; - private _tree: WorkbenchAsyncDataTree; + private _tree: WorkbenchAsyncDataTree; 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, ITreeRenderer[], IAsyncDataSource, IAsyncDataTreeOptions, WorkbenchAsyncDataTree>( + this._tree = this._instantiationService.createInstance, ITreeRenderer[], IAsyncDataSource, IAsyncDataTreeOptions, WorkbenchAsyncDataTree>( 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) { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 7ba2538bf83..cfca8cc68dd 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -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 | PagedList | ITree | ObjectTree | AsyncDataTree; +export type ListWidget = List | PagedList | ITree | ObjectTree | AsyncDataTree; export const IListService = createDecorator('listService'); @@ -590,7 +590,7 @@ export class TreeResourceNavigator2 extends Disposable { private readonly _openResource: Emitter> = new Emitter>(); readonly openResource: Event> = this._openResource.event; - constructor(private tree: WorkbenchObjectTree | WorkbenchAsyncDataTree, private options?: IResourceResultsNavigationOptions) { + constructor(private tree: WorkbenchObjectTree | WorkbenchAsyncDataTree, private options?: IResourceResultsNavigationOptions) { super(); this.registerListeners(); @@ -955,7 +955,7 @@ export class WorkbenchObjectTree, TFilterData = void> } } -export class WorkbenchAsyncDataTree, TFilterData = void> extends AsyncDataTree { +export class WorkbenchAsyncDataTree extends AsyncDataTree { readonly contextKeyService: IContextKeyService; @@ -969,7 +969,7 @@ export class WorkbenchAsyncDataTree, TFilterData = vo container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], - dataSource: IAsyncDataSource, + dataSource: IAsyncDataSource, options: IAsyncDataTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index e61e215069d..b6a5f34ec75 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -186,7 +186,7 @@ export class CollapseAction extends Action { // Collapse All action for the new tree export class CollapseAction2 extends Action { - constructor(tree: AsyncDataTree, enabled: boolean, clazz: string) { + constructor(tree: AsyncDataTree, enabled: boolean, clazz: string) { super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, () => { tree.collapseAll(); return Promise.resolve(undefined); diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index cf5a6fb1cf9..26f3536fe9c 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -804,7 +804,7 @@ export class ReverseContinueAction extends AbstractDebugAction { } export class ReplCollapseAllAction extends CollapseAction2 { - constructor(tree: AsyncDataTree, private toFocus: { focus(): void; }) { + constructor(tree: AsyncDataTree, private toFocus: { focus(): void; }) { super(tree, true, undefined); } diff --git a/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts b/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts index 96f50cdecc8..7d9a3c90613 100644 --- a/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts @@ -362,7 +362,7 @@ export class LoadedScriptsView extends ViewletPanel { private treeContainer: HTMLElement; private loadedScriptsItemType: IContextKey; - private tree: WorkbenchAsyncDataTree; + private tree: WorkbenchAsyncDataTree; 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 { } } -class LoadedScriptsDataSource implements IAsyncDataSource { +class LoadedScriptsDataSource implements IAsyncDataSource { - constructor(private root: LoadedScriptsItem) { + hasChildren(element: LoadedScriptsItem): boolean { + return element.hasChildren(); } - hasChildren(element: LoadedScriptsItem | null): boolean { - return element === null || element.hasChildren(); - } - - getChildren(element: LoadedScriptsItem | null): Promise { - if (element === null) { - element = this.root; - } + getChildren(element: LoadedScriptsItem): Promise { return element.getChildren(); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/callStackView.ts b/src/vs/workbench/parts/debug/electron-browser/callStackView.ts index abdac7a42be..204c995986e 100644 --- a/src/vs/workbench/parts/debug/electron-browser/callStackView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/callStackView.ts @@ -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; private dataSource: CallStackDataSource; - private tree: WorkbenchAsyncDataTree; + private tree: WorkbenchAsyncDataTree; 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).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 { } } -class CallStackDataSource implements IAsyncDataSource { +function isDebugModel(obj: any): obj is IDebugModel { + return typeof obj.getSessions === 'function'; +} + +class CallStackDataSource implements IAsyncDataSource { deemphasizedStackFramesToShow: IStackFrame[]; - constructor(private debugService: IDebugService) { } - - 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 { - if (element === null) { - const model = this.debugService.getModel(); - const sessions = model.getSessions(); + getChildren(element: IDebugModel | CallStackItem): Promise { + if (isDebugModel(element)) { + const sessions = element.getSessions(); if (sessions.length === 0) { return Promise.resolve([]); } @@ -594,12 +598,11 @@ class CallStackDataSource implements IAsyncDataSource { 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(threads[0]) : Promise.resolve(threads); - } - if (element instanceof DebugSession) { + } else if (element instanceof DebugSession) { return Promise.resolve(element.getAllThreads()); + } else { + return this.getThreadChildren(element); } - - return this.getThreadChildren(element); } private getThreadChildren(thread: Thread): Promise> { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts index 116b865ead2..43c08cc4965 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts @@ -45,7 +45,7 @@ export class DebugHoverWidget implements IContentWidget { private _isVisible: boolean; private domNode: HTMLElement; - private tree: AsyncDataTree; + private tree: AsyncDataTree; 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 { +class DebugHoverDataSource implements IAsyncDataSource { - expression: IExpression; - - hasChildren(element: IExpression | null): boolean { - return element === null || element.hasChildren; + hasChildren(element: IExpression): boolean { + return element.hasChildren; } - getChildren(element: IExpression | null): Promise { - if (element === null) { - element = this.expression; - } - + getChildren(element: IExpression): Promise { return element.getChildren(); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index 43e043e0929..c32885af6ad 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -81,7 +81,7 @@ interface IPrivateReplService { clearRepl(): void; } -function revealLastElement(tree: WorkbenchAsyncDataTree) { +function revealLastElement(tree: WorkbenchAsyncDataTree) { 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; - private tree: WorkbenchAsyncDataTree; + private tree: WorkbenchAsyncDataTree; 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 { } -class ReplDataSource implements IAsyncDataSource { +class ReplDataSource implements IAsyncDataSource { input: IDebugSession; hasChildren(element: IReplElement | null): boolean { diff --git a/src/vs/workbench/parts/debug/electron-browser/variablesView.ts b/src/vs/workbench/parts/debug/electron-browser/variablesView.ts index 1c234db02b5..28f567f0c9f 100644 --- a/src/vs/workbench/parts/debug/electron-browser/variablesView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/variablesView.ts @@ -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; + private tree: WorkbenchAsyncDataTree; 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 { +function isViewModel(obj: any): obj is IViewModel { + return typeof obj.getSelectedExpression === 'function'; +} - constructor(private debugService: IDebugService) { } +export class VariablesDataSource implements IAsyncDataSource { - 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> { - 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([]); } diff --git a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts index 759fa41a422..feaa138016e 100644 --- a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts @@ -34,7 +34,7 @@ export class WatchExpressionsView extends ViewletPanel { private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh: boolean; - private tree: WorkbenchAsyncDataTree; + private tree: WorkbenchAsyncDataTree; 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 { } } -class WatchExpressionsDataSource implements IAsyncDataSource { +function isDebugService(element: any): element is IDebugService { + return typeof element.getConfigurationManager === 'function'; +} - constructor(private debugService: IDebugService) { } +class WatchExpressionsDataSource implements IAsyncDataSource { hasChildren(element: IExpression | null): boolean { - if (element === null) { - return true; - } - - return element.hasChildren; + return isDebugService(element) || element.hasChildren; } - getChildren(element: IExpression | null): Promise> { - if (element === null) { - const watchExpressions = this.debugService.getModel().getWatchExpressions(); - const viewModel = this.debugService.getViewModel(); + getChildren(element: IDebugService | IExpression): Promise> { + 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)));