diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 8fea5c400c960b915335a920e75da915e2c7d319..528ff30caa2f908704b5b18b414bdacf188f11b0 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -50,6 +50,7 @@ export class MenuId { static readonly SCMResourceGroupContext = new MenuId('12'); static readonly SCMResourceContext = new MenuId('13'); static readonly CommandPalette = new MenuId('14'); + static readonly ViewletTitle = new MenuId('15'); constructor(private _id: string) { diff --git a/src/vs/platform/actions/electron-browser/menusExtensionPoint.ts b/src/vs/platform/actions/electron-browser/menusExtensionPoint.ts index 29c0bec57b534264096cdb1d0cda77ade0619d4f..48391aa16d04c3f49213baa9cade624d186198d0 100644 --- a/src/vs/platform/actions/electron-browser/menusExtensionPoint.ts +++ b/src/vs/platform/actions/electron-browser/menusExtensionPoint.ts @@ -38,6 +38,7 @@ namespace schema { case 'scm/title': return MenuId.SCMTitle; case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext; case 'scm/resourceState/context': return MenuId.SCMResourceContext; + case 'viewlet/title': return MenuId.ViewletTitle; } return void 0; diff --git a/src/vs/workbench/api/node/mainThreadTree.ts b/src/vs/workbench/api/node/mainThreadTree.ts index b89a4c52e7935e31ea6af22748ed32d979287abc..c70a2466aff24b065c17366f94fab3f1481b5a38 100644 --- a/src/vs/workbench/api/node/mainThreadTree.ts +++ b/src/vs/workbench/api/node/mainThreadTree.ts @@ -42,7 +42,7 @@ class TreeExplorerNodeProvider implements InternalTreeExplorerNodeProvider { readonly _onRefresh: Emitter = new Emitter(); readonly onRefresh: Event = this._onRefresh.event; - constructor(private providerId: string, private rootNode: InternalTreeExplorerNodeContent, private _proxy: ExtHostTreeShape, + constructor(public readonly id: string, private rootNode: InternalTreeExplorerNodeContent, private _proxy: ExtHostTreeShape, private messageService: IMessageService, private commandService: ICommandService ) { @@ -53,11 +53,11 @@ class TreeExplorerNodeProvider implements InternalTreeExplorerNodeProvider { } resolveChildren(node: InternalTreeExplorerNodeContent): TPromise { - return this._proxy.$resolveChildren(this.providerId, node).then(children => children, err => this.messageService.show(Severity.Error, err)); + return this._proxy.$resolveChildren(this.id, node).then(children => children, err => this.messageService.show(Severity.Error, err)); } executeCommand(node: InternalTreeExplorerNodeContent): TPromise { - return this._proxy.$getInternalCommand(this.providerId, node).then(command => { + return this._proxy.$getInternalCommand(this.id, node).then(command => { return this.commandService.executeCommand(command.id, ...command.arguments); }); } diff --git a/src/vs/workbench/api/node/mainThreadTreeExplorers.ts b/src/vs/workbench/api/node/mainThreadTreeExplorers.ts index 18c8e81464553d97ff2b42cc29d19ae6c087b126..fffeed5ff745d05401f843408485a682d4a9502e 100644 --- a/src/vs/workbench/api/node/mainThreadTreeExplorers.ts +++ b/src/vs/workbench/api/node/mainThreadTreeExplorers.ts @@ -42,22 +42,22 @@ class TreeExplorerNodeProvider implements InternalTreeExplorerNodeProvider { readonly _onRefresh: Emitter = new Emitter(); readonly onRefresh: Event = this._onRefresh.event; - constructor(private providerId: string, private _proxy: ExtHostTreeExplorersShape, + constructor(public readonly id: string, private _proxy: ExtHostTreeExplorersShape, private messageService: IMessageService, private commandService: ICommandService ) { } provideRootNode(): TPromise { - return this._proxy.$provideRootNode(this.providerId).then(rootNode => rootNode, err => this.messageService.show(Severity.Error, err)); + return this._proxy.$provideRootNode(this.id).then(rootNode => rootNode, err => this.messageService.show(Severity.Error, err)); } resolveChildren(node: InternalTreeExplorerNodeContent): TPromise { - return this._proxy.$resolveChildren(this.providerId, node).then(children => children, err => this.messageService.show(Severity.Error, err)); + return this._proxy.$resolveChildren(this.id, node).then(children => children, err => this.messageService.show(Severity.Error, err)); } executeCommand(node: InternalTreeExplorerNodeContent): TPromise { - return this._proxy.$getInternalCommand(this.providerId, node).then(command => { + return this._proxy.$getInternalCommand(this.id, node).then(command => { return this.commandService.executeCommand(command.id, ...command.arguments); }); } diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 99a278558c50b0aa285058c052383c05ff313f87..1ad83a799aea67866444ab162d398802a888b569 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -440,7 +440,7 @@ export abstract class CollapsibleViewletView extends CollapsibleView implements collapsed: boolean, private viewName: string, protected messageService: IMessageService, - private keybindingService: IKeybindingService, + protected keybindingService: IKeybindingService, protected contextMenuService: IContextMenuService, headerSize?: number ) { diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerMenus.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerMenus.ts new file mode 100644 index 0000000000000000000000000000000000000000..22e73fbda29786beec964d7d2f4fb0f14f529914 --- /dev/null +++ b/src/vs/workbench/parts/explorers/browser/treeExplorerMenus.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import Event, { Emitter } from 'vs/base/common/event'; +import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IAction } from 'vs/base/common/actions'; +import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; + + +export class TreeExplorerMenus implements IDisposable { + + private disposables: IDisposable[] = []; + + private activeProviderId: string; + private titleDisposable: IDisposable = EmptyDisposable; + private titleActions: IAction[] = []; + private titleSecondaryActions: IAction[] = []; + + private _onDidChangeTitle = new Emitter(); + get onDidChangeTitle(): Event { return this._onDidChangeTitle.event; } + + constructor( + @IContextKeyService private contextKeyService: IContextKeyService, + @ITreeExplorerService private treeExplorerService: ITreeExplorerService, + @IMenuService private menuService: IMenuService + ) { + this.setActiveProvider(this.treeExplorerService.activeProvider); + this.treeExplorerService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); + } + + private setActiveProvider(activeProvider: string | undefined): void { + if (this.titleDisposable) { + this.titleDisposable.dispose(); + this.titleDisposable = EmptyDisposable; + } + + if (!activeProvider) { + return; + } + + this.activeProviderId = activeProvider; + + const titleMenu = this.menuService.createMenu(MenuId.ViewletTitle, this.contextKeyService); + const updateActions = () => { + this.titleActions = []; + this.titleSecondaryActions = []; + fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }); + this._onDidChangeTitle.fire(); + }; + + const listener = titleMenu.onDidChange(updateActions); + updateActions(); + + this.titleDisposable = toDisposable(() => { + listener.dispose(); + titleMenu.dispose(); + this.titleActions = []; + this.titleSecondaryActions = []; + }); + } + + getTitleActions(): IAction[] { + return this.titleActions; + } + + getTitleSecondaryActions(): IAction[] { + return this.titleSecondaryActions; + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts index 82ee02263c78790573bee820e17caa1eeb5c2741..4661317c4d7e95d565a813351735329c8d735b0d 100644 --- a/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts +++ b/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts @@ -10,19 +10,47 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { InternalTreeExplorerNode, InternalTreeExplorerNodeProvider } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; export class TreeExplorerService implements ITreeExplorerService { public _serviceBrand: any; + private _activeProvider: string; + private activeProviderContextKey: IContextKey; + + private _onDidChangeProvider = new Emitter(); + get onDidChangeProvider(): Event { return this._onDidChangeProvider.event; } + private _onTreeExplorerNodeProviderRegistered = new Emitter(); public get onTreeExplorerNodeProviderRegistered(): Event { return this._onTreeExplorerNodeProviderRegistered.event; }; private _treeExplorerNodeProviders: { [providerId: string]: InternalTreeExplorerNodeProvider }; constructor( - @IMessageService private messageService: IMessageService, + @IContextKeyService private contextKeyService: IContextKeyService, + @IMessageService private messageService: IMessageService ) { this._treeExplorerNodeProviders = Object.create(null); + this.activeProviderContextKey = this.contextKeyService.createKey('treeExplorerProvider', void 0); + } + + get activeProvider(): string { + return this._activeProvider; + } + + set activeProvider(provider: string) { + if (!provider) { + throw new Error('invalid provider'); + } + + if (provider && !!this._treeExplorerNodeProviders[provider]) { + throw new Error('Provider not registered'); + } + + this._activeProvider = provider; + this.activeProviderContextKey.set(provider ? provider : void 0); + + this._onDidChangeProvider.fire(provider); } public registerTreeExplorerNodeProvider(providerId: string, provider: InternalTreeExplorerNodeProvider): void { diff --git a/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts b/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts index dd8fffaebdf2fb52bdfdcd326d026857d9d2c10f..34f4102dc1c16ee404e49bcb7f3dc5daca083167 100644 --- a/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts +++ b/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts @@ -10,8 +10,8 @@ import * as DOM from 'vs/base/browser/dom'; import { Builder, $ } from 'vs/base/browser/builder'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { CollapsibleViewletView } from 'vs/workbench/browser/viewlet'; -import { IAction, IActionRunner } from 'vs/base/common/actions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IAction, IActionRunner, 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'; @@ -21,13 +21,16 @@ import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeEx import { ITree } from 'vs/base/parts/tree/browser/tree'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { TreeExplorerViewletState, TreeDataSource, TreeRenderer, TreeController } from 'vs/workbench/parts/explorers/browser/views/treeExplorerViewer'; +import { TreeExplorerMenus } from 'vs/workbench/parts/explorers/browser/treeExplorerMenus'; import { RefreshViewExplorerAction } from 'vs/workbench/parts/explorers/browser/treeExplorerActions'; import { attachListStyler } from "vs/platform/theme/common/styler"; import { IThemeService } from "vs/platform/theme/common/themeService"; +import { createActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; export class TreeExplorerView extends CollapsibleViewletView { - private providerDisposables = IDisposable[]; + private providerDisposables: IDisposable[]; + private menus: TreeExplorerMenus; constructor( private viewletState: TreeExplorerViewletState, @@ -44,7 +47,8 @@ export class TreeExplorerView extends CollapsibleViewletView { @IThemeService private themeService: IThemeService ) { super(actionRunner, false, nls.localize('treeExplorerViewlet.tree', "Tree Explorer Section"), messageService, keybindingService, contextMenuService, headerSize); - + this.treeExplorerService.activeProvider = treeNodeProviderId; + this.menus = this.instantiationService.createInstance(TreeExplorerMenus); this.create(); } @@ -74,10 +78,16 @@ export class TreeExplorerView extends CollapsibleViewletView { return tree; } - public getActions(): IAction[] { - const refresh = this.instantiationService.createInstance(RefreshViewExplorerAction, this); + getActions(): IAction[] { + return [...this.menus.getTitleActions(), new RefreshViewExplorerAction(this)]; + } + + getSecondaryActions(): IAction[] { + return this.menus.getTitleSecondaryActions(); + } - return [refresh]; + getActionItem(action: IAction): IActionItem { + return createActionItem(action, this.keybindingService, this.messageService); } public create(): TPromise { diff --git a/src/vs/workbench/parts/explorers/common/treeExplorerService.ts b/src/vs/workbench/parts/explorers/common/treeExplorerService.ts index dc41d8a146868d53452d3e0c58df7a1af7bf50b9..a0b370dc955c0aa94b9dbe3ea6e241c4d683b3b2 100644 --- a/src/vs/workbench/parts/explorers/common/treeExplorerService.ts +++ b/src/vs/workbench/parts/explorers/common/treeExplorerService.ts @@ -14,8 +14,10 @@ export const ITreeExplorerService = createDecorator('treeE export interface ITreeExplorerService { _serviceBrand: any; - onTreeExplorerNodeProviderRegistered: Event; + onDidChangeProvider: Event; + activeProvider: string; + onTreeExplorerNodeProviderRegistered: Event; registerTreeExplorerNodeProvider(providerId: string, provider: InternalTreeExplorerNodeProvider): void; hasProvider(providerId: string): boolean; getProvider(providerId: string): InternalTreeExplorerNodeProvider; diff --git a/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts b/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts index e623b1a4295644437fe13ea62c16039d07c172d5..161a6955ae7499be161be349ed194dd5f11f0a9b 100644 --- a/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts +++ b/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts @@ -18,6 +18,7 @@ export interface InternalTreeExplorerNode extends InternalTreeExplorerNodeConten } export interface InternalTreeExplorerNodeProvider { + id: string; provideRootNode(): Thenable; resolveChildren(node: InternalTreeExplorerNodeContent): Thenable; executeCommand(node: InternalTreeExplorerNodeContent): TPromise;