diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 100276698ea..be22ec02e24 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -63,8 +63,9 @@ export class Progress implements IProgress { } export enum ProgressLocation { - Scm = 1, - Extensions = 2, + Explorer = 1, + Scm = 3, + Extensions = 5, Window = 10 } diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index dbe24cc426e..9d4f94df9c1 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -9,7 +9,7 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry'; import { ViewLocation, ViewsRegistry, ICustomViewDescriptor } from 'vs/workbench/common/views'; -import { CustomTreeViewPanel } from 'vs/workbench/browser/parts/views/customView'; +import { CustomTreeViewPanel } from 'vs/workbench/browser/parts/views/customViewPanel'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; @@ -110,7 +110,7 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyV when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, collapsed: true, - treeItemView: true + treeView: true }; // validate diff --git a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts index 1a3901f13ab..50f205d1be5 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts @@ -11,12 +11,13 @@ import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainCo import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { ITreeViewDataProvider, ITreeItem, ICustomViewsService } from 'vs/workbench/common/views'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { assign } from 'vs/base/common/objects'; +import { distinct } from 'vs/base/common/arrays'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape { private _proxy: ExtHostTreeViewsShape; + private _dataProviders: Map = new Map(); constructor( extHostContext: IExtHostContext, @@ -29,15 +30,21 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie $registerTreeViewDataProvider(treeViewId: string): void { const dataProvider = this._register(new TreeViewDataProvider(treeViewId, this._proxy, this.messageService)); + this._dataProviders.set(treeViewId, dataProvider); this.viewsService.registerTreeViewDataProvider(treeViewId, dataProvider); } $refresh(treeViewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): void { - const treeViewer = this.viewsService.getTreeItemViewer(treeViewId); - if (treeViewer && treeViewer.dataProvider) { - (treeViewer.dataProvider).refresh(itemsToRefresh); + const dataProvider = this._dataProviders.get(treeViewId); + if (dataProvider) { + dataProvider.refresh(itemsToRefresh); } } + + dispose(): void { + this._dataProviders.clear(); + super.dispose(); + } } type TreeItemHandle = string; @@ -58,23 +65,13 @@ class TreeViewDataProvider implements ITreeViewDataProvider { ) { } - getElements(): TPromise { - return this._proxy.$getElements(this.treeViewId) - .then(elements => { - return this.postGetElements(elements); - }, err => { - this.messageService.show(Severity.Error, err); - return []; - }); - } - - getChildren(treeItem: ITreeItem): TPromise { - if (treeItem.children) { + getChildren(treeItem?: ITreeItem): TPromise { + if (treeItem && treeItem.children) { return TPromise.as(treeItem.children); } - return this._proxy.$getChildren(this.treeViewId, treeItem.handle) + return this._proxy.$getChildren(this.treeViewId, treeItem ? treeItem.handle : void 0) .then(children => { - return this.postGetElements(children); + return this.postGetChildren(children); }, err => { this.messageService.show(Severity.Error, err); return []; @@ -108,19 +105,12 @@ class TreeViewDataProvider implements ITreeViewDataProvider { } } - private postGetElements(elements: ITreeItem[]): ITreeItem[] { + private postGetChildren(elements: ITreeItem[]): ITreeItem[] { const result = []; if (elements) { for (const element of elements) { - const currentTreeItem = this.itemsMap.get(element.handle); - if (currentTreeItem) { - // Update the current item with new item - this.updateTreeItem(currentTreeItem, element); - } else { - this.itemsMap.set(element.handle, element); - } - // Always use the existing items - result.push(this.itemsMap.get(element.handle)); + this.itemsMap.set(element.handle, element); + result.push(element); } } return result; @@ -129,7 +119,10 @@ class TreeViewDataProvider implements ITreeViewDataProvider { private updateTreeItem(current: ITreeItem, treeItem: ITreeItem): void { treeItem.children = treeItem.children ? treeItem.children : null; if (current) { - assign(current, treeItem); + const properties = distinct([...Object.keys(current), ...Object.keys(treeItem)]); + for (const property of properties) { + current[property] = treeItem[property]; + } } } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 2462095db3b..07a6fc237ad 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -518,8 +518,7 @@ export interface ExtHostDocumentsAndEditorsShape { } export interface ExtHostTreeViewsShape { - $getElements(treeViewId: string): TPromise; - $getChildren(treeViewId: string, treeItemHandle: string): TPromise; + $getChildren(treeViewId: string, treeItemHandle?: string): TPromise; } export interface ExtHostWorkspaceShape { diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 9cd4cc8687d..cbf0e1a75f1 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -48,14 +48,6 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { }; } - $getElements(treeViewId: string): TPromise { - const treeView = this.treeViews.get(treeViewId); - if (!treeView) { - return TPromise.wrapError(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId))); - } - return treeView.getChildren(); - } - $getChildren(treeViewId: string, treeItemHandle?: string): TPromise { const treeView = this.treeViews.get(treeViewId); if (!treeView) { diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 8650a23e259..f276597e0e2 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -6,126 +6,159 @@ import 'vs/css!./media/views'; import Event, { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; -import { IDisposable, dispose, empty as EmptyDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TPromise } from 'vs/base/common/winjs.base'; import * as DOM from 'vs/base/browser/dom'; import { $ } from 'vs/base/browser/builder'; -import { IAction, IActionItem, ActionRunner } from 'vs/base/common/actions'; -import { IMessageService } from 'vs/platform/message/common/message'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; -import { fillInActions, ContextAwareMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { LIGHT } from 'vs/platform/theme/common/themeService'; import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ActionItem, ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; -import { TreeItemCollapsibleState, ITreeItem, TreeViewItemHandleArg, ITreeItemViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, ICustomViewDescriptor } from 'vs/workbench/common/views'; -import { IViewletViewOptions, IViewOptions, FileIconThemableWorkbenchTree, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; +import { TreeItemCollapsibleState, ITreeItem, ITreeViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, IViewDescriptor, TreeViewItemHandleArg, ICustomViewDescriptor } from 'vs/workbench/common/views'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IExtensionService } from 'vs/platform/extensions/common/extensions'; +import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import URI from 'vs/base/common/uri'; import { basename } from 'vs/base/common/paths'; -import { FileKind } from 'vs/platform/files/common/files'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IExtensionService } from 'vs/platform/extensions/common/extensions'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { fillInActions, ContextAwareMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; +import { FileKind } from 'vs/platform/files/common/files'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet'; -export class CustomViewsService implements ICustomViewsService { +export class CustomViewsService extends Disposable implements ICustomViewsService { _serviceBrand: any; - private viewers: Map = new Map(); + private viewers: Map = new Map(); constructor( @IInstantiationService private instantiationService: IInstantiationService ) { + super(); + this.createViewers(ViewsRegistry.getAllViews()); + this._register(ViewsRegistry.onViewsRegistered(viewDescriptors => this.createViewers(viewDescriptors))); + this._register(ViewsRegistry.onViewsDeregistered(viewDescriptors => this.removeViewers(viewDescriptors))); } - getTreeItemViewer(id: string): ITreeItemViewer { - let viewer = this.viewers.get(id); - if (!viewer) { - viewer = this.createViewer(id); - if (viewer) { - this.viewers.set(id, viewer); - } - } - return viewer; + getTreeViewer(id: string): ITreeViewer { + return this.viewers.get(id); } registerTreeViewDataProvider(id: string, dataProvider: ITreeViewDataProvider): void { - const treeViewer = this.getTreeItemViewer(id); + const treeViewer = this.getTreeViewer(id); if (treeViewer) { - treeViewer.dataProvider = dataProvider; - dataProvider.onDispose(() => treeViewer.dataProvider = null); + treeViewer.setDataProvider(dataProvider); + dataProvider.onDispose(() => treeViewer.setDataProvider(null)); } } - private createViewer(id: string): ITreeItemViewer { - const viewDeescriptor = ViewsRegistry.getView(id); - if (viewDeescriptor && viewDeescriptor.treeItemView) { - return this.instantiationService.createInstance(TreeItemViewer, id); + private createViewers(viewDescriptors: IViewDescriptor[]): void { + for (const viewDescriptor of viewDescriptors) { + if ((viewDescriptor).treeView) { + this.viewers.set(viewDescriptor.id, this.instantiationService.createInstance(CustomTreeViewer, viewDescriptor.id)); + } + } + } + + private removeViewers(viewDescriptors: IViewDescriptor[]): void { + for (const { id } of viewDescriptors) { + const viewer = this.getTreeViewer(id); + if (viewer) { + viewer.dispose(); + this.viewers.delete(id); + } } - return null; } } -export class TreeItemViewer extends Disposable implements ITreeItemViewer { +class Root implements ITreeItem { + label = 'root'; + handle = '0'; + parentHandle = null; + collapsibleState = TreeItemCollapsibleState.Expanded; + children = void 0; +} + +class CustomTreeViewer extends Disposable implements ITreeViewer { private isVisible: boolean = false; private activated: boolean = false; + private _hasIconForParentNode = false; + private _hasIconForLeafNode = false; - private tree: ITree; - private treeInputPromise: TPromise; + private _onDidIconsChange: Emitter = this._register(new Emitter()); + readonly onDidIconsChange: Event = this._onDidIconsChange.event; + + private treeContainer: HTMLElement; + private tree: FileIconThemableWorkbenchTree; + private root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; + private refreshing = 0; private _dataProvider: ITreeViewDataProvider; private dataProviderElementChangeListener: IDisposable; constructor( private id: string, - @IExtensionService private extensionService: IExtensionService + @IExtensionService private extensionService: IExtensionService, + @IWorkbenchThemeService private themeService: IWorkbenchThemeService, + @IInstantiationService private instantiationService: IInstantiationService, + @ICommandService private commandService: ICommandService ) { super(); + this.root = new Root(); + this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); + this._register(this.themeService.onThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); } get dataProvider(): ITreeViewDataProvider { return this._dataProvider; } - set dataProvider(dataProvider: ITreeViewDataProvider) { - this._dataProvider = dataProvider; + setDataProvider(dataProvider: ITreeViewDataProvider) { if (this.dataProviderElementChangeListener) { this.dataProviderElementChangeListener.dispose(); } - if (this.dataProvider) { - this.dataProviderElementChangeListener = this._register(this.dataProvider.onDidChange(element => this.refresh(element))); - this.refresh(null); + if (dataProvider) { + const customTreeView: CustomTreeViewer = this; + this._dataProvider = new class implements ITreeViewDataProvider { + onDidChange = dataProvider.onDidChange; + onDispose = dataProvider.onDispose; + getChildren(node?: ITreeItem): TPromise { + if (node.children) { + return TPromise.as(node.children); + } + const promise = node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node); + return promise.then(children => { + node.children = children; + if (!customTreeView.refreshing) { + customTreeView.updateIconsAvailability(node); + } + return children; + }); + } + }; + this.dataProviderElementChangeListener = this._register(dataProvider.onDidChange(elements => this.refresh(elements))); + } else { + this._dataProvider = null; } + this.refresh(); } - refresh(elements: ITreeItem[]): TPromise { - if (this.tree) { - if (!elements) { - const root: ITreeItem = this.tree.getInput(); - root.children = null; // reset children - elements = [root]; - } - if (this.isVisible) { - return this.doRefresh(elements); - } else { - this.elementsToRefresh.push(...elements); - } - } - return TPromise.as(null); + get hasIconForParentNode(): boolean { + return this._hasIconForParentNode; } - setTree(tree: ITree): void { - this.tree = tree; - this.setInput(); + get hasIconForLeafNode(): boolean { + return this._hasIconForLeafNode; } setVisibility(isVisible: boolean): void { @@ -169,79 +202,16 @@ export class TreeItemViewer extends Disposable implements ITreeItemViewer { // Pass Focus to Viewer this.tree.DOMFocus(); } - } - layout(size: number) { - if (this.tree) { - this.tree.layout(size); - } - } - - private activate() { - if (!this.activated) { - this.extensionService.activateByEvent(`onView:${this.id}`); - this.activated = true; - this.setInput(); - } - } - - private setInput(): TPromise { - if (this.tree) { - if (!this.treeInputPromise) { - this.treeInputPromise = this.tree.setInput(new Root()); - } - return this.treeInputPromise; - } - return TPromise.as(null); - } - - private doRefresh(elements: ITreeItem[]): TPromise { - return TPromise.join(elements.map(e => this.tree.refresh(e))) as TPromise; - } -} - -export class CustomTreeViewPanel extends ViewsViewletPanel { - - private menus: Menus; - private treeContainer: HTMLElement; - private tree: WorkbenchTree; - private treeViewer: TreeItemViewer; - - constructor( - options: IViewletViewOptions, - @IMessageService private messageService: IMessageService, - @IKeybindingService keybindingService: IKeybindingService, - @IContextMenuService contextMenuService: IContextMenuService, - @IInstantiationService private instantiationService: IInstantiationService, - @IThemeService private themeService: IWorkbenchThemeService, - @ICommandService private commandService: ICommandService, - @IConfigurationService configurationService: IConfigurationService, - @ICustomViewsService customViewsService: ICustomViewsService, - ) { - super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService, configurationService); - this.menus = this.instantiationService.createInstance(Menus, this.id); - this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables); - this.treeViewer = customViewsService.getTreeItemViewer(this.id); - this.disposables.push(this.treeViewer); - this.updateTreeVisibility(); - } - - setVisible(visible: boolean): TPromise { - return super.setVisible(visible).then(() => this.updateTreeVisibility()); - } - - focus(): void { - super.focus(); - this.treeViewer.focus(); - } - - renderBody(container: HTMLElement): void { + render(container: HTMLElement): void { this.treeContainer = DOM.append(container, DOM.$('.tree-explorer-viewlet-tree-view')); - const actionItemProvider = (action: IAction) => this.getActionItem(action); - const dataSource = this.instantiationService.createInstance(TreeDataSource, this.treeViewer); - const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, this.menus, actionItemProvider); - const controller = this.instantiationService.createInstance(TreeController, this.id, this.menus); + + const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuItemActionItem, action) : undefined; + const menus = this.instantiationService.createInstance(Menus, this.id); + const dataSource = this.instantiationService.createInstance(TreeDataSource, this); + const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, this, menus, actionItemProvider); + const controller = this.instantiationService.createInstance(TreeController, this.id, menus); this.tree = this.instantiationService.createInstance(FileIconThemableWorkbenchTree, this.treeContainer, { dataSource, renderer, controller }, @@ -249,38 +219,17 @@ export class CustomTreeViewPanel extends ViewsViewletPanel { ); this.tree.contextKeyService.createKey(this.id, true); - this.disposables.push(this.tree); - this.disposables.push(this.tree.onDidChangeSelection(e => this.onSelection(e, this.tree))); - this.themeService.onThemeChange(() => this.tree.refresh() /* soft refresh */, this, this.disposables); + this._register(this.tree); + this._register(this.tree.onDidChangeSelection(e => this.onSelection(e))); - this.treeViewer.setTree(this.tree); + this.tree.setInput(this.root); } - setExpanded(expanded: boolean): void { - this.treeViewer.setVisibility(this.isVisible() && expanded); - super.setExpanded(expanded); - } - - layoutBody(size: number): void { - if (this.treeContainer) { + layout(size: number) { + if (this.tree) { this.treeContainer.style.height = size + 'px'; + this.tree.layout(size); } - this.treeViewer.layout(size); - } - - getActions(): IAction[] { - return [...this.menus.getTitleActions()]; - } - - getSecondaryActions(): IAction[] { - return this.menus.getTitleSecondaryActions(); - } - - getActionItem(action: IAction): IActionItem { - if (!(action instanceof MenuItemAction)) { - return undefined; - } - return new ContextAwareMenuItemActionItem(action, this.keybindingService, this.messageService, this.contextMenuService); } getOptimalWidth(): number { @@ -289,46 +238,107 @@ export class CustomTreeViewPanel extends ViewsViewletPanel { const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a')); return DOM.getLargestChildWidth(parentNode, childNodes); } - return super.getOptimalWidth(); + return 0; } - private updateTreeVisibility(): void { - this.treeViewer.setVisibility(this.isVisible() && this.isExpanded()); + refresh(elements?: ITreeItem[]): TPromise { + if (this.tree) { + elements = elements || [this.root]; + for (const element of elements) { + element.children = null; // reset children + } + if (this.isVisible) { + return this.doRefresh(elements); + } else { + this.elementsToRefresh.push(...elements); + } + } + return TPromise.as(null); } - private onSelection({ payload }: any, tree: WorkbenchTree): void { - const selection: ITreeItem = tree.getSelection()[0]; + private activate() { + if (!this.activated) { + this.extensionService.activateByEvent(`onView:${this.id}`); + this.activated = true; + } + } + + private doRefresh(elements: ITreeItem[]): TPromise { + if (this.tree) { + return TPromise.join(elements.map(e => { + this.refreshing++; + return this.tree.refresh(e).then(() => this.refreshing--, () => this.refreshing--); + })).then(() => this.updateIconsAvailability(this.root)); + } + return TPromise.as(null); + } + + private updateIconsAvailability(parent: ITreeItem): void { + if (this.activated && this.tree) { + const initialResult = parent instanceof Root ? { hasIconForParentNode: false, hasIconForLeafNode: false } : { hasIconForParentNode: this.hasIconForParentNode, hasIconForLeafNode: this.hasIconForLeafNode }; + const { hasIconForParentNode, hasIconForLeafNode } = this.computeIconsAvailability(parent.children || [], initialResult); + const changed = this.hasIconForParentNode !== hasIconForParentNode || this.hasIconForLeafNode !== hasIconForLeafNode; + this._hasIconForParentNode = hasIconForParentNode; + this._hasIconForLeafNode = hasIconForLeafNode; + if (changed) { + this._onDidIconsChange.fire(); + } + DOM.toggleClass(this.treeContainer, 'custom-view-align-icons-and-twisties', this.hasIconForLeafNode && !this.hasIconForParentNode); + } + } + + private computeIconsAvailability(nodes: ITreeItem[], result: { hasIconForParentNode: boolean, hasIconForLeafNode: boolean }): { hasIconForParentNode: boolean, hasIconForLeafNode: boolean } { + if (!result.hasIconForLeafNode || !result.hasIconForParentNode) { + for (const node of nodes) { + if (this.hasIcon(node)) { + result.hasIconForParentNode = result.hasIconForParentNode || node.collapsibleState !== TreeItemCollapsibleState.None; + result.hasIconForLeafNode = result.hasIconForLeafNode || node.collapsibleState === TreeItemCollapsibleState.None; + } + this.computeIconsAvailability(node.children || [], result); + if (result.hasIconForLeafNode && result.hasIconForParentNode) { + return result; + } + } + } + return result; + } + + private hasIcon(node: ITreeItem): boolean { + const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark; + if (icon) { + return true; + } + if (node.resourceUri) { + const fileIconTheme = this.themeService.getFileIconTheme(); + if (node.collapsibleState !== TreeItemCollapsibleState.None) { + return fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons; + } + return fileIconTheme.hasFileIcons; + } + return false; + } + + private onSelection({ payload }: any): void { + const selection: ITreeItem = this.tree.getSelection()[0]; if (selection) { if (selection.command) { const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent; const isMouseEvent = payload && payload.origin === 'mouse'; const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2; - if (!isMouseEvent || tree.openOnSingleClick || isDoubleClick) { + if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) { this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || [])); } } } } - - dispose(): void { - dispose(this.disposables); - super.dispose(); - } -} - -class Root implements ITreeItem { - label = 'root'; - handle = '0'; - parentHandle = null; - collapsibleState = TreeItemCollapsibleState.Expanded; } class TreeDataSource implements IDataSource { constructor( - private treeItemViewer: ITreeItemViewer, - @IProgressService private progressService: IProgressService + private treeView: ITreeViewer, + @IProgressService2 private progressService: IProgressService2 ) { } @@ -337,26 +347,13 @@ class TreeDataSource implements IDataSource { } public hasChildren(tree: ITree, node: ITreeItem): boolean { - if (!this.treeItemViewer.dataProvider) { - return false; - } - return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded; + return this.treeView.dataProvider && node.collapsibleState !== TreeItemCollapsibleState.None; } public getChildren(tree: ITree, node: ITreeItem): TPromise { - if (node.children) { - return TPromise.as(node.children); + if (this.treeView.dataProvider) { + return this.progressService.withProgress({ location: ProgressLocation.Explorer }, () => this.treeView.dataProvider.getChildren(node)); } - - if (this.treeItemViewer.dataProvider) { - const promise = node instanceof Root ? this.treeItemViewer.dataProvider.getElements() : this.treeItemViewer.dataProvider.getChildren(node); - this.progressService.showWhile(promise, 100); - return promise.then(children => { - node.children = children; - return children; - }); - } - return TPromise.as([]); } @@ -372,9 +369,8 @@ class TreeDataSource implements IDataSource { interface ITreeExplorerTemplateData { label: HTMLElement; resourceLabel: ResourceLabel; - icon: HTMLElement; + icon: TreeItemIcon; actionBar: ActionBar; - aligner: Aligner; } class TreeRenderer implements IRenderer { @@ -384,6 +380,7 @@ class TreeRenderer implements IRenderer { constructor( private treeViewId: string, + private treeViewer: ITreeViewer, private menus: Menus, private actionItemProvider: IActionItemProvider, @IInstantiationService private instantiationService: IInstantiationService, @@ -400,18 +397,18 @@ class TreeRenderer implements IRenderer { } public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITreeExplorerTemplateData { - const el = DOM.append(container, DOM.$('.custom-view-tree-node-item')); + DOM.addClass(container, 'custom-view-tree-node-item'); - const icon = DOM.append(el, DOM.$('.custom-view-tree-node-item-icon')); - const label = DOM.append(el, DOM.$('.custom-view-tree-node-item-label')); - const resourceLabel = this.instantiationService.createInstance(ResourceLabel, el, {}); - const actionsContainer = DOM.append(el, DOM.$('.actions')); + const icon = this.instantiationService.createInstance(TreeItemIcon, container, this.treeViewer); + const label = DOM.append(container, DOM.$('.custom-view-tree-node-item-label')); + const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, {}); + const actionsContainer = DOM.append(container, DOM.$('.actions')); const actionBar = new ActionBar(actionsContainer, { actionItemProvider: this.actionItemProvider, actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection()) }); - return { label, resourceLabel, icon, actionBar, aligner: new Aligner(container, tree, this.themeService) }; + return { label, resourceLabel, icon, actionBar }; } public renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void { @@ -425,7 +422,6 @@ class TreeRenderer implements IRenderer { templateData.label.textContent = ''; DOM.removeClass(templateData.label, 'custom-view-tree-node-item-label'); DOM.removeClass(templateData.resourceLabel.element, 'custom-view-tree-node-item-resourceLabel'); - DOM.removeClass(templateData.icon, 'custom-view-tree-node-item-icon'); if (resource && !icon) { templateData.resourceLabel.setLabel({ name, resource }, { fileKind: node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE }); @@ -433,95 +429,64 @@ class TreeRenderer implements IRenderer { } else { templateData.label.textContent = name; DOM.addClass(templateData.label, 'custom-view-tree-node-item-label'); - templateData.icon.style.backgroundImage = `url('${icon}')`; - if (icon) { - DOM.addClass(templateData.icon, 'custom-view-tree-node-item-icon'); - } } + templateData.icon.treeItem = node; templateData.actionBar.context = ({ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }); templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); - - templateData.aligner.align(node); } public disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void { templateData.resourceLabel.dispose(); - templateData.aligner.dispose(); + templateData.icon.dispose(); } } -class Aligner extends Disposable { +class TreeItemIcon extends Disposable { - private node: ITreeItem; + private _treeItem: ITreeItem; + private iconElement: HTMLElement; constructor( - private container: HTMLElement, - private tree: ITree, - private themeService: IWorkbenchThemeService + container: HTMLElement, + private treeViewer: CustomTreeViewer, + @IInstantiationService instantiationService: IInstantiationService, + @IWorkbenchThemeService private themeService: IWorkbenchThemeService ) { super(); - this._register(this.themeService.onDidFileIconThemeChange(() => this.alignByTheme())); + this.iconElement = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); + this._register(this.treeViewer.onDidIconsChange(() => this.render())); } - align(treeItem: ITreeItem): void { - this.node = treeItem; - this.alignByTheme(); + set treeItem(treeItem: ITreeItem) { + this._treeItem = treeItem; + this.render(); } - private alignByTheme(): void { - if (this.node) { - DOM.toggleClass(this.container, 'align-with-twisty', this.hasToAlignWithTwisty()); - } - } - - private hasToAlignWithTwisty(): boolean { - if (this.hasParentHasIcon()) { - return false; - } - - const fileIconTheme = this.themeService.getFileIconTheme(); - if (!(fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons)) { - return false; - } - if (this.node.collapsibleState !== TreeItemCollapsibleState.None) { - return false; - } - const icon = this.themeService.getTheme().type === LIGHT ? this.node.icon : this.node.iconDark; - const hasIcon = !!icon || !!this.node.resourceUri; - if (!hasIcon) { - return false; - } - - const siblingsWithChildren = this.getSiblings().filter(s => s.collapsibleState !== TreeItemCollapsibleState.None); - for (const s of siblingsWithChildren) { - const icon = this.themeService.getTheme().type === LIGHT ? s.icon : s.iconDark; - if (icon) { - return false; - } - } - - return true; - } - - private getSiblings(): ITreeItem[] { - const parent: ITreeItem = this.tree.getNavigator(this.node).parent() || this.tree.getInput(); - return parent.children; - } - - private hasParentHasIcon(): boolean { - const parent = this.tree.getNavigator(this.node).parent() || this.tree.getInput(); - const icon = this.themeService.getTheme().type === LIGHT ? parent.icon : parent.iconDark; - if (icon) { - return true; - } - if (parent.resourceUri) { + private render(): void { + if (this._treeItem) { const fileIconTheme = this.themeService.getFileIconTheme(); - if (fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons) { - return true; - } + const contributedIcon = this.themeService.getTheme().type === LIGHT ? this._treeItem.icon : this._treeItem.iconDark; + + const hasContributedIcon = !!contributedIcon; + const hasChildren = this._treeItem.collapsibleState !== TreeItemCollapsibleState.None; + const hasResource = !!this._treeItem.resourceUri; + const isFolder = hasResource && hasChildren; + const isFile = hasResource && !hasChildren; + const hasThemeFolderIcon = isFolder && fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons; + const hasThemeFileIcon = isFile && fileIconTheme.hasFileIcons; + const hasIcon = hasContributedIcon || hasThemeFolderIcon || hasThemeFileIcon; + const hasFolderPlaceHolderIcon = hasIcon ? false : isFolder && this.treeViewer.hasIconForParentNode; + const hasFilePlaceHolderIcon = hasIcon ? false : isFile && this.treeViewer.hasIconForLeafNode; + const hasContainerPlaceHolderIcon = hasIcon || hasFolderPlaceHolderIcon ? false : hasChildren && this.treeViewer.hasIconForParentNode; + const hasLeafPlaceHolderIcon = hasIcon || hasFilePlaceHolderIcon ? false : !hasChildren && (this.treeViewer.hasIconForParentNode || this.treeViewer.hasIconForLeafNode); + + this.iconElement.style.backgroundImage = hasContributedIcon ? `url('${contributedIcon}')` : ''; + DOM.toggleClass(this.iconElement, 'folder-icon', hasFolderPlaceHolderIcon); + DOM.toggleClass(this.iconElement, 'file-icon', hasFilePlaceHolderIcon); + DOM.toggleClass(this.iconElement, 'placeholder-icon', hasContainerPlaceHolderIcon); + DOM.toggleClass(this.iconElement, 'custom-view-tree-node-item-icon', hasContributedIcon || hasFolderPlaceHolderIcon || hasFilePlaceHolderIcon || hasContainerPlaceHolderIcon || hasLeafPlaceHolderIcon); } - return false; } } @@ -599,15 +564,7 @@ class MultipleSelectionActionRunner extends ActionRunner { } } -class Menus implements IDisposable { - - private disposables: IDisposable[] = []; - private titleDisposable: IDisposable = EmptyDisposable; - private titleActions: IAction[] = []; - private titleSecondaryActions: IAction[] = []; - - private _onDidChangeTitle = new Emitter(); - get onDidChangeTitle(): Event { return this._onDidChangeTitle.event; } +class Menus extends Disposable implements IDisposable { constructor( private id: string, @@ -615,40 +572,7 @@ class Menus implements IDisposable { @IMenuService private menuService: IMenuService, @IContextMenuService private contextMenuService: IContextMenuService ) { - if (this.titleDisposable) { - this.titleDisposable.dispose(); - this.titleDisposable = EmptyDisposable; - } - - const _contextKeyService = this.contextKeyService.createScoped(); - _contextKeyService.createKey('view', id); - - const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService); - const updateActions = () => { - this.titleActions = []; - this.titleSecondaryActions = []; - fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }, this.contextMenuService); - this._onDidChangeTitle.fire(); - }; - - const listener = titleMenu.onDidChange(updateActions); - updateActions(); - - this.titleDisposable = toDisposable(() => { - listener.dispose(); - titleMenu.dispose(); - _contextKeyService.dispose(); - this.titleActions = []; - this.titleSecondaryActions = []; - }); - } - - getTitleActions(): IAction[] { - return this.titleActions; - } - - getTitleSecondaryActions(): IAction[] { - return this.titleSecondaryActions; + super(); } getResourceActions(element: ITreeItem): IAction[] { @@ -675,8 +599,4 @@ class Menus implements IDisposable { return result; } - - dispose(): void { - this.disposables = dispose(this.disposables); - } -} +} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/customViewPanel.ts b/src/vs/workbench/browser/parts/views/customViewPanel.ts new file mode 100644 index 00000000000..9378f5dbb5a --- /dev/null +++ b/src/vs/workbench/browser/parts/views/customViewPanel.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/views'; +import Event, { Emitter } from 'vs/base/common/event'; +import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IAction, IActionItem } from 'vs/base/common/actions'; +import { IMessageService } from 'vs/platform/message/common/message'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { fillInActions, ContextAwareMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICustomViewsService, ITreeViewer } from 'vs/workbench/common/views'; +import { IViewletViewOptions, IViewOptions, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet'; + +export class CustomTreeViewPanel extends ViewsViewletPanel { + + private menus: Menus; + private treeViewer: ITreeViewer; + + constructor( + options: IViewletViewOptions, + @IMessageService private messageService: IMessageService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IInstantiationService private instantiationService: IInstantiationService, + @ICustomViewsService customViewsService: ICustomViewsService, + ) { + super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService); + this.treeViewer = customViewsService.getTreeViewer(this.id); + this.menus = this.instantiationService.createInstance(Menus, this.id); + this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables); + this.updateTreeVisibility(); + } + + setVisible(visible: boolean): TPromise { + return super.setVisible(visible).then(() => this.updateTreeVisibility()); + } + + focus(): void { + super.focus(); + this.treeViewer.focus(); + } + + renderBody(container: HTMLElement): void { + this.treeViewer.render(container); + } + + setExpanded(expanded: boolean): void { + this.treeViewer.setVisibility(this.isVisible() && expanded); + super.setExpanded(expanded); + } + + layoutBody(size: number): void { + this.treeViewer.layout(size); + } + + getActions(): IAction[] { + return [...this.menus.getTitleActions()]; + } + + getSecondaryActions(): IAction[] { + return this.menus.getTitleSecondaryActions(); + } + + getActionItem(action: IAction): IActionItem { + return action instanceof MenuItemAction ? new ContextAwareMenuItemActionItem(action, this.keybindingService, this.messageService, this.contextMenuService) : undefined; + } + + getOptimalWidth(): number { + return this.treeViewer.getOptimalWidth(); + } + + private updateTreeVisibility(): void { + this.treeViewer.setVisibility(this.isVisible() && this.isExpanded()); + } + + dispose(): void { + dispose(this.disposables); + super.dispose(); + } +} + +export class Menus implements IDisposable { + + private disposables: IDisposable[] = []; + private titleDisposable: IDisposable = EmptyDisposable; + private titleActions: IAction[] = []; + private titleSecondaryActions: IAction[] = []; + + private _onDidChangeTitle = new Emitter(); + get onDidChangeTitle(): Event { return this._onDidChangeTitle.event; } + + constructor( + id: string, + @IContextKeyService private contextKeyService: IContextKeyService, + @IMenuService private menuService: IMenuService, + @IContextMenuService private contextMenuService: IContextMenuService + ) { + if (this.titleDisposable) { + this.titleDisposable.dispose(); + this.titleDisposable = EmptyDisposable; + } + + const _contextKeyService = this.contextKeyService.createScoped(); + _contextKeyService.createKey('view', id); + + const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService); + const updateActions = () => { + this.titleActions = []; + this.titleSecondaryActions = []; + fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }, this.contextMenuService); + this._onDidChangeTitle.fire(); + }; + + const listener = titleMenu.onDidChange(updateActions); + updateActions(); + + this.titleDisposable = toDisposable(() => { + listener.dispose(); + titleMenu.dispose(); + _contextKeyService.dispose(); + this.titleActions = []; + this.titleSecondaryActions = []; + }); + } + + getTitleActions(): IAction[] { + return this.titleActions; + } + + getTitleSecondaryActions(): IAction[] { + return this.titleSecondaryActions; + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/media/Document_16x.svg b/src/vs/workbench/browser/parts/views/media/Document_16x.svg new file mode 100644 index 00000000000..46a9f38cc88 --- /dev/null +++ b/src/vs/workbench/browser/parts/views/media/Document_16x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/media/Document_16x_inverse.svg b/src/vs/workbench/browser/parts/views/media/Document_16x_inverse.svg new file mode 100755 index 00000000000..14abfb51077 --- /dev/null +++ b/src/vs/workbench/browser/parts/views/media/Document_16x_inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/media/FolderOpen_16x.svg b/src/vs/workbench/browser/parts/views/media/FolderOpen_16x.svg new file mode 100644 index 00000000000..1a3933d6351 --- /dev/null +++ b/src/vs/workbench/browser/parts/views/media/FolderOpen_16x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/media/FolderOpen_16x_inverse.svg b/src/vs/workbench/browser/parts/views/media/FolderOpen_16x_inverse.svg new file mode 100755 index 00000000000..fbf57c927f2 --- /dev/null +++ b/src/vs/workbench/browser/parts/views/media/FolderOpen_16x_inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/media/Folder_16x.svg b/src/vs/workbench/browser/parts/views/media/Folder_16x.svg new file mode 100644 index 00000000000..3d64ae71db4 --- /dev/null +++ b/src/vs/workbench/browser/parts/views/media/Folder_16x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/media/Folder_16x_inverse.svg b/src/vs/workbench/browser/parts/views/media/Folder_16x_inverse.svg new file mode 100755 index 00000000000..13b18d18016 --- /dev/null +++ b/src/vs/workbench/browser/parts/views/media/Folder_16x_inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index f017fb80c94..0d8382b371a 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -50,9 +50,12 @@ display: none; } -.tree-explorer-viewlet-tree-view.file-icon-themable-tree.align-icons-and-twisties .monaco-tree-row:not(.has-children) .content:not(.align-with-twisty)::before, -.tree-explorer-viewlet-tree-view.file-icon-themable-tree.hide-arrows .monaco-tree-row .content::before { - display: inline-block; +.tree-explorer-viewlet-tree-view.file-icon-themable-tree.custom-view-align-icons-and-twisties .monaco-tree-row:not(.has-children) .content::before { + display: none; +} + +.tree-explorer-viewlet-tree-view.file-icon-themable-tree.align-icons-and-twisties:not(.custom-view-align-icons-and-twisties) .monaco-tree-row:not(.has-children) .content::before { + display: block; } .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item { @@ -82,6 +85,48 @@ -webkit-font-smoothing: antialiased; } +.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.placeholder-icon { + border: 1px; + border-style:dashed; + border-radius: 1px; + margin: 5px 6px 5px 2px; + height: 12px; + width: 6px; +} + +.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.file-icon { + content: ' '; + background-image: url('Document_16x.svg'); +} + +.hs-black .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.file-icon, +.vs-dark .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.file-icon { + content: ' '; + background-image: url('Document_16x_inverse.svg'); +} + +.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon { + content: ' '; + background-image: url('Folder_16x.svg'); +} + +.hs-black .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon, +.vs-dark .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon { + content: ' '; + background-image: url('Folder_16x_inverse.svg'); +} + +.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.expanded .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon { + content: ' '; + background-image: url('FolderOpen_16x.svg'); +} + +.hc-black .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.expanded .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon, +.vs-dark .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.expanded .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon { + content: ' '; + background-image: url('FolderOpen_16x_inverse.svg'); +} + .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .actions { display: none; padding-right: 6px; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index c6a9f6bd4a5..cab9d6035ca 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -12,6 +12,7 @@ import { ITreeViewDataProvider } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IDisposable } from 'vs/base/common/lifecycle'; export class ViewLocation { @@ -69,6 +70,8 @@ export interface IViewsRegistry { getViews(loc: ViewLocation): IViewDescriptor[]; + getAllViews(): IViewDescriptor[]; + getView(id: string): IViewDescriptor; } @@ -128,6 +131,12 @@ export const ViewsRegistry: IViewsRegistry = new class implements IViewsRegistry return this._views.get(loc) || []; } + getAllViews(): IViewDescriptor[] { + const result: IViewDescriptor[] = []; + this._views.forEach(views => result.push(...views)); + return result; + } + getView(id: string): IViewDescriptor { for (const viewLocation of this._viewLocations) { const viewDescriptor = (this._views.get(viewLocation) || []).filter(v => v.id === id)[0]; @@ -147,17 +156,26 @@ export interface IViewsViewlet extends IViewlet { // Custom views -export interface ITreeItemViewer { +export interface ITreeViewer extends IDisposable { - dataProvider: ITreeViewDataProvider; + readonly dataProvider: ITreeViewDataProvider; - refresh(treeItems: ITreeItem[]): TPromise; + refresh(treeItems?: ITreeItem[]): TPromise; + setVisibility(visible: boolean): void; + + focus(): void; + + layout(height: number): void; + + render(container: HTMLElement); + + getOptimalWidth(): number; } export interface ICustomViewDescriptor extends IViewDescriptor { - treeItemView?: boolean; + treeView?: boolean; } @@ -166,7 +184,7 @@ export const ICustomViewsService = createDecorator('customV export interface ICustomViewsService { _serviceBrand: any; - getTreeItemViewer(id: string): ITreeItemViewer; + getTreeViewer(id: string): ITreeViewer; registerTreeViewDataProvider(id: string, ITreeViewDataProvider): void; } @@ -211,7 +229,5 @@ export interface ITreeViewDataProvider { onDispose: Event; - getElements(): TPromise; - - getChildren(element: ITreeItem): TPromise; + getChildren(element?: ITreeItem): TPromise; } \ No newline at end of file diff --git a/src/vs/workbench/services/progress/browser/progressService2.ts b/src/vs/workbench/services/progress/browser/progressService2.ts index ad8d9889ea9..cf356213af6 100644 --- a/src/vs/workbench/services/progress/browser/progressService2.ts +++ b/src/vs/workbench/services/progress/browser/progressService2.ts @@ -73,6 +73,8 @@ export class ProgressService2 implements IProgressService2 { switch (location) { case ProgressLocation.Window: return this._withWindowProgress(options, task); + case ProgressLocation.Explorer: + return this._withViewletProgress('workbench.view.explorer', task); case ProgressLocation.Scm: return this._withViewletProgress('workbench.view.scm', task); case ProgressLocation.Extensions: diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index fa8cae0b3ef..38e3479a08e 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -72,7 +72,7 @@ suite('ExtHostTreeView', function () { testObject.registerTreeDataProvider('testNodeTreeProvider', aNodeTreeDataProvider()); testObject.registerTreeDataProvider('testNodeWithIdTreeProvider', aNodeWithIdTreeDataProvider()); - testObject.$getElements('testNodeTreeProvider').then(elements => { + testObject.$getChildren('testNodeTreeProvider').then(elements => { for (const element of elements) { testObject.$getChildren('testNodeTreeProvider', element.handle); } @@ -80,7 +80,7 @@ suite('ExtHostTreeView', function () { }); test('construct node tree', () => { - return testObject.$getElements('testNodeTreeProvider') + return testObject.$getChildren('testNodeTreeProvider') .then(elements => { const actuals = elements.map(e => e.handle); assert.deepEqual(actuals, ['0/0:a', '0/0:b']); @@ -108,7 +108,7 @@ suite('ExtHostTreeView', function () { }); test('construct id tree', () => { - return testObject.$getElements('testNodeWithIdTreeProvider') + return testObject.$getChildren('testNodeWithIdTreeProvider') .then(elements => { const actuals = elements.map(e => e.handle); assert.deepEqual(actuals, ['1/a', '1/b']); @@ -139,7 +139,7 @@ suite('ExtHostTreeView', function () { tree['a'] = { 'a': {} }; - return testObject.$getElements('testNodeWithIdTreeProvider') + return testObject.$getChildren('testNodeWithIdTreeProvider') .then(elements => { const actuals = elements.map(e => e.handle); assert.deepEqual(actuals, ['1/a', '1/b']); @@ -300,7 +300,7 @@ suite('ExtHostTreeView', function () { onDidChangeTreeNode.fire(); - return testObject.$getElements('testNodeTreeProvider') + return testObject.$getChildren('testNodeTreeProvider') .then(elements => { assert.deepEqual(elements.map(e => e.handle), ['0/0:a//0:b']); }); @@ -338,7 +338,7 @@ suite('ExtHostTreeView', function () { tree['f'] = {}; tree[dupItems['adup2']] = {}; - return testObject.$getElements('testNodeTreeProvider') + return testObject.$getChildren('testNodeTreeProvider') .then(elements => { const actuals = elements.map(e => e.handle); assert.deepEqual(actuals, ['0/0:a', '0/0:b', '0/1:a', '0/0:d', '0/1:b', '0/0:f', '0/2:a']);