diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 84b213d6831..9324e3efd47 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6499,10 +6499,11 @@ declare module 'vscode' { * By default revealed element is selected and not focused. * In order to not to select, set the option `select` to `false`. * In order to focus, set the option `focus` to `true`. + * In order to expand the revealed element, set the option `expand` to `true` or `1`. To expand recursively set `expand` to the number of levels to expand. * * **NOTE:** [TreeDataProvider](#TreeDataProvider) is required to implement [getParent](#TreeDataProvider.getParent) method to access this API. */ - reveal(element: T, options?: { select?: boolean, focus?: boolean }): Thenable; + reveal(element: T, options?: { select?: boolean, focus?: boolean, expand?: boolean | number }): Thenable; } /** diff --git a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts index 9ed8aff7cd0..e686cf33662 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts @@ -6,10 +6,11 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Disposable } from 'vs/base/common/lifecycle'; import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../node/extHost.protocol'; -import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeViewer, ViewsRegistry, ICustomViewDescriptor } from 'vs/workbench/common/views'; +import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeViewer, ViewsRegistry, ICustomViewDescriptor, IRevealOptions } from 'vs/workbench/common/views'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { isUndefinedOrNull, isNumber } from 'vs/base/common/types'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape { @@ -40,11 +41,11 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } } - $reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: { select: boolean, focus: boolean }): Thenable { + $reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Thenable { return this.viewsService.openView(treeViewId, options.focus) .then(() => { const viewer = this.getTreeViewer(treeViewId); - return viewer ? viewer.reveal(item, parentChain, options) : null; + return this.reveal(viewer, this._dataProviders.get(treeViewId), item, parentChain, options); }); } @@ -58,6 +59,42 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie return TPromise.as(null); } + private async reveal(treeViewer: ITreeViewer, dataProvider: TreeViewDataProvider, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): TPromise { + options = options ? options : { select: false, focus: false }; + const select = isUndefinedOrNull(options.select) ? false : options.select; + const focus = isUndefinedOrNull(options.focus) ? false : options.focus; + let expand = isNumber(options.expand) ? options.expand : options.expand === true ? 1 : 0; + + if (dataProvider.isEmpty()) { + // Refresh if empty + await treeViewer.refresh(); + } + for (const parent of parentChain) { + await treeViewer.expand(parent); + } + item = dataProvider.getItem(item.handle); + if (item) { + await treeViewer.reveal(item); + if (select) { + treeViewer.setSelection([item]); + } + if (focus) { + treeViewer.setFocus(item); + } + let itemsToExpand = [item]; + for (; itemsToExpand.length > 0 && expand > 0; expand--) { + await treeViewer.expand(itemsToExpand); + itemsToExpand = itemsToExpand.reduce((result, item) => { + item = dataProvider.getItem(item.handle); + if (item && item.children && item.children.length) { + result.push(...item.children); + } + return result; + }, []); + } + } + } + private registerListeners(treeViewId: string, treeViewer: ITreeViewer): void { this._register(treeViewer.onDidExpandItem(item => this._proxy.$setExpanded(treeViewId, item.handle, true))); this._register(treeViewer.onDidCollapseItem(item => this._proxy.$setExpanded(treeViewId, item.handle, false))); @@ -111,7 +148,7 @@ class TreeViewDataProvider implements ITreeViewDataProvider { const itemsToRefresh: ITreeItem[] = []; if (itemsToRefreshByHandle) { for (const treeItemHandle of Object.keys(itemsToRefreshByHandle)) { - const currentTreeItem = this.itemsMap.get(treeItemHandle); + const currentTreeItem = this.getItem(treeItemHandle); if (currentTreeItem) { // Refresh only if the item exists const treeItem = itemsToRefreshByHandle[treeItemHandle]; // Update the current item with refreshed item @@ -133,6 +170,14 @@ class TreeViewDataProvider implements ITreeViewDataProvider { return itemsToRefresh; } + getItem(treeItemHandle: string): ITreeItem { + return this.itemsMap.get(treeItemHandle); + } + + isEmpty(): boolean { + return this.itemsMap.size === 0; + } + private postGetChildren(elements: ITreeItem[]): ITreeItem[] { const result: ITreeItem[] = []; if (elements) { diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 78d9bd8fb9f..bc33b0928c6 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -32,7 +32,7 @@ import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { EndOfLine, IFileOperationOptions, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes'; import { EditorViewColumn } from 'vs/workbench/api/shared/editor'; import { TaskDTO, TaskExecutionDTO, TaskFilterDTO, TaskHandleDTO, TaskProcessEndedDTO, TaskProcessStartedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks'; -import { ITreeItem } from 'vs/workbench/common/views'; +import { ITreeItem, IRevealOptions } from 'vs/workbench/common/views'; import { IAdapterDescriptor, IConfig, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug'; import { ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks'; @@ -212,7 +212,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { export interface MainThreadTreeViewsShape extends IDisposable { $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Thenable; - $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: { select: boolean, focus: boolean }): Thenable; + $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Thenable; } export interface MainThreadErrorsShape extends IDisposable { diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index cd21bb1bb31..f84369ccaeb 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol'; -import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel } from 'vs/workbench/common/views'; +import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { asThenable } from 'vs/base/common/async'; import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/node/extHostTypes'; @@ -81,7 +81,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { get onDidChangeSelection() { return treeView.onDidChangeSelection; }, get visible() { return treeView.visible; }, get onDidChangeVisibility() { return treeView.onDidChangeVisibility; }, - reveal: (element: T, options?: { select?: boolean, focus?: boolean }): Thenable => { + reveal: (element: T, options?: IRevealOptions): Thenable => { return treeView.reveal(element, options); }, dispose: () => { @@ -209,10 +209,11 @@ class ExtHostTreeView extends Disposable { return this.elements.get(treeItemHandle); } - reveal(element: T, options?: { select?: boolean, focus?: boolean }): Promise { + reveal(element: T, options?: IRevealOptions): Promise { options = options ? options : { select: true, focus: false }; const select = isUndefinedOrNull(options.select) ? true : options.select; const focus = isUndefinedOrNull(options.focus) ? false : options.focus; + const expand = isUndefinedOrNull(options.expand) ? false : options.expand; if (typeof this.dataProvider.getParent !== 'function') { return Promise.reject(new Error(`Required registered TreeDataProvider to implement 'getParent' method to access 'reveal' method`)); @@ -220,7 +221,7 @@ class ExtHostTreeView extends Disposable { return this.refreshPromise .then(() => this.resolveUnknownParentChain(element)) .then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1]) - .then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus })), error => this.logService.error(error)); + .then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus, expand })), error => this.logService.error(error)); } setExpanded(treeItemHandle: TreeItemHandle, expanded: boolean): void { diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index abaa1b7eb73..68d83bf2f06 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -23,7 +23,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as DOM from 'vs/base/browser/dom'; -import { isUndefinedOrNull } from 'vs/base/common/types'; import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -409,32 +408,22 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer { return Promise.resolve(null); } - reveal(item: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean, focus?: boolean }): TPromise { - if (this.dataProvider && this.tree && this.isVisible) { - options = options ? options : { select: false, focus: false }; - const select = isUndefinedOrNull(options.select) ? false : options.select; - const focus = isUndefinedOrNull(options.focus) ? false : options.focus; + expand(itemOrItems: ITreeItem | ITreeItem[]): TPromise { + itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems]; + return this.tree.expandAll(itemOrItems); + } - const root: Root = this.tree.getInput(); - const promise: Thenable = root.children ? Promise.resolve(null) : this.refresh(); // Refresh if root is not populated - return promise.then(() => { - var result = Promise.resolve(null); - parentChain.forEach((e) => { - result = result.then(() => this.tree.expand(e)); - }); - return result.then(() => this.tree.reveal(item)) - .then(() => { - if (select) { - this.tree.setSelection([item], { source: 'api' }); - } - if (focus) { - this.focus(); - this.tree.setFocus(item); - } - }); - }); - } - return Promise.resolve(null); + setSelection(items: ITreeItem[]): void { + this.tree.setSelection(items, { source: 'api' }); + } + + setFocus(item: ITreeItem): void { + this.focus(); + this.tree.setFocus(item); + } + + reveal(item: ITreeItem): TPromise { + return this.tree.reveal(item); } private activate() { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 3ee072af53b..c51a17c5c3a 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -265,13 +265,29 @@ export interface ITreeViewer extends IDisposable { getOptimalWidth(): number; - reveal(item: ITreeItem, parentChain: ITreeItem[], options: { select?: boolean }): TPromise; + reveal(item: ITreeItem): TPromise; + + expand(itemOrItems: ITreeItem | ITreeItem[]): TPromise; + + setSelection(items: ITreeItem[]): void; + + setFocus(item: ITreeItem): void; getPrimaryActions(): IAction[]; getSecondaryActions(): IAction[]; } +export interface IRevealOptions { + + select?: boolean; + + focus?: boolean; + + expand?: boolean | number; + +} + export interface ICustomViewDescriptor extends IViewDescriptor { readonly treeViewer: ITreeViewer;