diff --git a/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts b/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts index df97ee7f83c70b3fcb16e6fab6fafdcfcefef928..3718e0927b9ee7e52fac35bc232b1e3f4ef6f251 100644 --- a/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts +++ b/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts @@ -45,5 +45,5 @@ export class ToggleActivityBarVisibilityAction extends Action { } } -let registry = Registry.as(Extensions.WorkbenchActions); +const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', nls.localize('view', "View")); \ No newline at end of file diff --git a/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts b/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts index 40c20df23c32033b6d09090082d25972ca752361..d06dafc4db031de7b222830b571f62583e4812c9 100644 --- a/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts +++ b/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts @@ -45,5 +45,5 @@ export class ToggleStatusbarVisibilityAction extends Action { } } -let registry = Registry.as(Extensions.WorkbenchActions); +const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', nls.localize('view', "View")); \ No newline at end of file diff --git a/src/vs/workbench/browser/actions/toggleZenMode.ts b/src/vs/workbench/browser/actions/toggleZenMode.ts index b9a13bea08d28f51205a2165c94cfeeaac867411..d542e39b8ee1520ee175b58540153542a720149b 100644 --- a/src/vs/workbench/browser/actions/toggleZenMode.ts +++ b/src/vs/workbench/browser/actions/toggleZenMode.ts @@ -32,5 +32,5 @@ class ToggleZenMode extends Action { } } -let registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', nls.localize('view', "View")); +const registry = Registry.as(Extensions.WorkbenchActions); +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', nls.localize('view', "View")); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/activitybar/activityAction.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts similarity index 76% rename from src/vs/workbench/browser/parts/activitybar/activityAction.ts rename to src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index d80c7a3ba0198bbe4ad07ba38fe1635c1881ea33..209d1b52191897cac5cd8608e9c4193f57780441 100644 --- a/src/vs/workbench/browser/parts/activitybar/activityAction.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -13,8 +13,8 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Builder, $ } from 'vs/base/browser/builder'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { Action } from 'vs/base/common/actions'; -import { BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ProgressBadge, TextBadge, NumberBadge, IconBadge, IBadge } from 'vs/workbench/services/activity/common/activityService'; +import { BaseActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActivityBarService, ProgressBadge, TextBadge, NumberBadge, IconBadge, IBadge } from 'vs/workbench/services/activity/common/activityBarService'; import Event, { Emitter } from 'vs/base/common/event'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -22,7 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IViewletService, } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; export class ActivityAction extends Action { @@ -105,7 +105,6 @@ export class ViewletActivityAction extends ActivityAction { export class ViewletOverflowActivityAction extends ActivityAction { constructor( - private viewlets: ViewletDescriptor[], private showMenu: () => void ) { super('activitybar.additionalViewlets.action', nls.localize('additionalViewlets', "Additional Viewlets"), 'toggle-more'); @@ -126,7 +125,7 @@ export class ViewletOverflowActivityActionItem extends BaseActionItem { constructor( action: ActivityAction, - private viewlets: ViewletDescriptor[], + private getOverflowingViewlets: () => ViewletDescriptor[], private getBadge: (viewlet: ViewletDescriptor) => IBadge, @IInstantiationService private instantiationService: IInstantiationService, @IViewletService private viewletService: IViewletService, @@ -136,7 +135,6 @@ export class ViewletOverflowActivityActionItem extends BaseActionItem { this.cssClass = action.class; this.name = action.label; - this.actions = viewlets.map(viewlet => this.instantiationService.createInstance(OpenViewletAction, viewlet)); } public render(container: HTMLElement): void { @@ -151,18 +149,24 @@ export class ViewletOverflowActivityActionItem extends BaseActionItem { } public showMenu(): void { - this.updateActions(); + if (this.actions) { + dispose(this.actions); + } + + this.actions = this.getActions(); this.contextMenuService.showContextMenu({ getAnchor: () => this.builder.getHTMLElement(), - getActions: () => TPromise.as(this.actions) + getActions: () => TPromise.as(this.actions), + onHide: () => dispose(this.actions) }); } - private updateActions(): void { + private getActions(): OpenViewletAction[] { const activeViewlet = this.viewletService.getActiveViewlet(); - this.actions.forEach(action => { + return this.getOverflowingViewlets().map(viewlet => { + const action = this.instantiationService.createInstance(OpenViewletAction, viewlet); action.radio = activeViewlet && activeViewlet.getId() === action.id; const badge = this.getBadge(action.viewlet); @@ -178,6 +182,8 @@ export class ViewletOverflowActivityActionItem extends BaseActionItem { } else { action.label = action.viewlet.name; } + + return action; }); } @@ -188,8 +194,11 @@ export class ViewletOverflowActivityActionItem extends BaseActionItem { } } -let manageExtensionAction: ManageExtensionAction; export class ActivityActionItem extends BaseActionItem { + + private static manageExtensionAction: ManageExtensionAction; + private static toggleViewletPinnedAction: ToggleViewletPinnedAction; + private $e: Builder; private name: string; private _keybinding: string; @@ -202,6 +211,8 @@ export class ActivityActionItem extends BaseActionItem { action: ActivityAction, private viewlet: ViewletDescriptor, @IContextMenuService private contextMenuService: IContextMenuService, + @IViewletService private viewletService: IViewletService, + @IActivityBarService private activityBarService: IActivityBarService, @IKeybindingService private keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService ) { @@ -212,8 +223,12 @@ export class ActivityActionItem extends BaseActionItem { this._keybinding = this.getKeybindingLabel(viewlet.id); action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose); - if (!manageExtensionAction) { - manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); + if (!ActivityActionItem.manageExtensionAction) { + ActivityActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); + } + + if (!ActivityActionItem.toggleViewletPinnedAction) { + ActivityActionItem.toggleViewletPinnedAction = instantiationService.createInstance(ToggleViewletPinnedAction, void 0); } } @@ -234,17 +249,11 @@ export class ActivityActionItem extends BaseActionItem { role: 'button' }).appendTo(this.builder); - if (this.viewlet.extensionId) { - $(container).on('contextmenu', e => { - DOM.EventHelper.stop(e, true); - - this.contextMenuService.showContextMenu({ - getAnchor: () => container, - getActionsContext: () => this.viewlet.extensionId, - getActions: () => TPromise.as([manageExtensionAction]) - }); - }, this.toDispose); - } + $(container).on('contextmenu', e => { + DOM.EventHelper.stop(e, true); + + this.showContextMenu(container); + }, this.toDispose); if (this.cssClass) { this.$e.addClass(this.cssClass); @@ -266,6 +275,27 @@ export class ActivityActionItem extends BaseActionItem { })); } + private showContextMenu(container: HTMLElement): void { + const actions: Action[] = [ActivityActionItem.toggleViewletPinnedAction]; + if (this.viewlet.extensionId) { + actions.push(new Separator()); + actions.push(ActivityActionItem.manageExtensionAction); + } + + const isPinned = this.activityBarService.isPinned(this.viewlet.id); + if (isPinned) { + ActivityActionItem.toggleViewletPinnedAction.label = nls.localize('removeFromActivityBar', "Remove from Activity Bar"); + } else { + ActivityActionItem.toggleViewletPinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar"); + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => container, + getActionsContext: () => this.viewlet, + getActions: () => TPromise.as(actions) + }); + } + public focus(): void { this.$e.domFocus(); } @@ -376,8 +406,8 @@ class ManageExtensionAction extends Action { super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension")); } - public run(extensionId: string): TPromise { - return this.commandService.executeCommand('_extensions.manage', extensionId); + public run(viewlet: ViewletDescriptor): TPromise { + return this.commandService.executeCommand('_extensions.manage', viewlet.extensionId); } } @@ -402,6 +432,30 @@ class OpenViewletAction extends Action { this.viewletService.openViewlet(this.viewlet.id, true).done(null, errors.onUnexpectedError); } + return TPromise.as(true); + } +} + +export class ToggleViewletPinnedAction extends Action { + + constructor( + private viewlet: ViewletDescriptor, + @IActivityBarService private activityBarService: IActivityBarService + ) { + super('activitybar.show.toggleViewletPinned', viewlet ? viewlet.name : nls.localize('toggle', "Toggle Viewlet Pinned")); + + this.checked = this.viewlet && this.activityBarService.isPinned(this.viewlet.id); + } + + public run(context?: ViewletDescriptor): TPromise { + const viewlet = this.viewlet || context; + + if (this.activityBarService.isPinned(viewlet.id)) { + this.activityBarService.unpin(viewlet.id); + } else { + this.activityBarService.pin(viewlet.id); + } + return TPromise.as(true); } } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 529565db35f0cfc1da96e81b7fc8f0de16b5db98..7ee590466ac218ec96edcaa3a86e34d2ed1fc23b 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -7,28 +7,38 @@ import 'vs/css!./media/activitybarpart'; import nls = require('vs/nls'); +import { TPromise } from 'vs/base/common/winjs.base'; +import DOM = require('vs/base/browser/dom'); +import * as arrays from 'vs/base/common/arrays'; import { Builder, $, Dimension } from 'vs/base/browser/builder'; import { Action } from 'vs/base/common/actions'; -import { ActionsOrientation, ActionBar, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IComposite } from 'vs/workbench/common/composite'; +import { ActionsOrientation, ActionBar, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Part } from 'vs/workbench/browser/part'; -import { ViewletActivityAction, ActivityAction, ActivityActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem } from 'vs/workbench/browser/parts/activitybar/activityAction'; +import { IViewlet } from 'vs/workbench/common/viewlet'; +import { ToggleViewletPinnedAction, ViewletActivityAction, ActivityAction, ActivityActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IActivityService, IBadge } from 'vs/workbench/services/activity/common/activityService'; +import { IActivityBarService, IBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { Scope as MementoScope } from 'vs/workbench/common/memento'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { dispose } from 'vs/base/common/lifecycle'; +import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/toggleActivityBarVisibility'; interface IViewletActivity { badge: IBadge; clazz: string; } -export class ActivitybarPart extends Part implements IActivityService { +export class ActivitybarPart extends Part implements IActivityBarService { - private static ACTIVITY_ACTION_HEIGHT = 50; + private static readonly ACTIVITY_ACTION_HEIGHT = 50; + private static readonly UNPINNED_VIEWLETS = 'workbench.activity.unpinnedViewlets'; public _serviceBrand: any; @@ -42,11 +52,17 @@ export class ActivitybarPart extends Part implements IActivityService { private viewletIdToActions: { [viewletId: string]: ActivityAction; }; private viewletIdToActivity: { [viewletId: string]: IViewletActivity; }; + private memento: any; + private unpinnedViewlets: string[]; + private activeUnpinnedViewlet: ViewletDescriptor; + constructor( id: string, @IViewletService private viewletService: IViewletService, @IExtensionService private extensionService: IExtensionService, @IKeybindingService private keybindingService: IKeybindingService, + @IStorageService private storageService: IStorageService, + @IContextMenuService private contextMenuService: IContextMenuService, @IInstantiationService private instantiationService: IInstantiationService, @IPartService private partService: IPartService ) { @@ -56,6 +72,9 @@ export class ActivitybarPart extends Part implements IActivityService { this.viewletIdToActions = Object.create(null); this.viewletIdToActivity = Object.create(null); + this.memento = this.getMemento(this.storageService, MementoScope.GLOBAL); + this.unpinnedViewlets = this.memento[ActivitybarPart.UNPINNED_VIEWLETS] || []; + // Update viewlet switcher when external viewlets become ready this.extensionService.onReady().then(() => this.updateViewletSwitcher()); @@ -65,21 +84,31 @@ export class ActivitybarPart extends Part implements IActivityService { private registerListeners(): void { // Activate viewlet action on opening of a viewlet - this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.onActiveCompositeChanged(viewlet))); + this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.onDidViewletOpen(viewlet))); // Deactivate viewlet action on close - this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.onCompositeClosed(viewlet))); + this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.onDidViewletClose(viewlet))); } - private onActiveCompositeChanged(composite: IComposite): void { - if (this.viewletIdToActions[composite.getId()]) { - this.viewletIdToActions[composite.getId()].activate(); + private onDidViewletOpen(viewlet: IViewlet): void { + const id = viewlet.getId(); + + if (this.viewletIdToActions[id]) { + this.viewletIdToActions[id].activate(); + } + + const activeUnpinnedViewletShouldClose = this.activeUnpinnedViewlet && this.activeUnpinnedViewlet.id !== viewlet.getId(); + const activeUnpinnedViewletShouldShow = !this.getPinnedViewlets().some(v => v.id === viewlet.getId()); + if (activeUnpinnedViewletShouldShow || activeUnpinnedViewletShouldClose) { + this.updateViewletSwitcher(); } } - private onCompositeClosed(composite: IComposite): void { - if (this.viewletIdToActions[composite.getId()]) { - this.viewletIdToActions[composite.getId()].deactivate(); + private onDidViewletClose(viewlet: IViewlet): void { + const id = viewlet.getId(); + + if (this.viewletIdToActions[id]) { + this.viewletIdToActions[id].deactivate(); } } @@ -113,9 +142,30 @@ export class ActivitybarPart extends Part implements IActivityService { // Top Actionbar with action items for each viewlet action this.createViewletSwitcher($result.clone()); + // Contextmenu for viewlets + $(parent).on('contextmenu', (e: MouseEvent) => { + DOM.EventHelper.stop(e, true); + + this.showContextMenu(e); + }, this.toUnbind); + return $result; } + private showContextMenu(e: MouseEvent): void { + const event = new StandardMouseEvent(e); + + const actions: Action[] = this.viewletService.getViewlets().map(viewlet => this.instantiationService.createInstance(ToggleViewletPinnedAction, viewlet)); + actions.push(new Separator()); + actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))); + + this.contextMenuService.showContextMenu({ + getAnchor: () => { return { x: event.posx + 1, y: event.posy }; }, + getActions: () => TPromise.as(actions), + onHide: () => dispose(actions) + }); + } + private createViewletSwitcher(div: Builder): void { this.viewletSwitcherBar = new ActionBar(div, { actionItemProvider: (action: Action) => action instanceof ViewletOverflowActivityAction ? this.viewletOverflowActionItem : this.activityActionItems[action.id], @@ -128,22 +178,30 @@ export class ActivitybarPart extends Part implements IActivityService { } private updateViewletSwitcher() { - let viewlets = this.viewletService.getViewlets(); - let viewletsToShow = viewlets; + let viewletsToShow = this.getPinnedViewlets(); + + // Always show the active viewlet even if it is marked to be hidden + const activeViewlet = this.viewletService.getActiveViewlet(); + if (activeViewlet && !viewletsToShow.some(v => v.id === activeViewlet.getId())) { + this.activeUnpinnedViewlet = this.viewletService.getViewlet(activeViewlet.getId()); + viewletsToShow.push(this.activeUnpinnedViewlet); + } else { + this.activeUnpinnedViewlet = void 0; + } // Ensure we are not showing more viewlets than we have height for let overflows = false; if (this.dimension) { const maxVisible = Math.floor(this.dimension.height / ActivitybarPart.ACTIVITY_ACTION_HEIGHT); - overflows = viewlets.length > maxVisible; + overflows = viewletsToShow.length > maxVisible; if (overflows) { - viewletsToShow = viewlets.slice(0, maxVisible - 1 /* make room for overflow action */); + viewletsToShow = viewletsToShow.slice(0, maxVisible - 1 /* make room for overflow action */); } } const visibleViewlets = Object.keys(this.viewletIdToActions); - const visibleViewletsChange = (viewletsToShow.length !== visibleViewlets.length); + const visibleViewletsChange = !arrays.equals(viewletsToShow.map(v => v.id), visibleViewlets); // Pull out overflow action if there is a viewlet change so that we can add it to the end later if (this.viewletOverflowAction && visibleViewletsChange) { @@ -156,7 +214,7 @@ export class ActivitybarPart extends Part implements IActivityService { this.viewletOverflowActionItem = null; } - // Pull out viewlets that overflow + // Pull out viewlets that overflow or got hidden const viewletIdsToShow = viewletsToShow.map(v => v.id); visibleViewlets.forEach(viewletId => { if (viewletIdsToShow.indexOf(viewletId) === -1) { @@ -197,15 +255,34 @@ export class ActivitybarPart extends Part implements IActivityService { // Add overflow action as needed if (visibleViewletsChange && overflows) { - const viewletsOverflowing = viewlets.slice(viewletsToShow.length); - - this.viewletOverflowAction = this.instantiationService.createInstance(ViewletOverflowActivityAction, viewletsOverflowing, () => this.viewletOverflowActionItem.showMenu()); - this.viewletOverflowActionItem = this.instantiationService.createInstance(ViewletOverflowActivityActionItem, this.viewletOverflowAction, viewletsOverflowing, viewlet => this.viewletIdToActivity[viewlet.id] && this.viewletIdToActivity[viewlet.id].badge); + this.viewletOverflowAction = this.instantiationService.createInstance(ViewletOverflowActivityAction, () => this.viewletOverflowActionItem.showMenu()); + this.viewletOverflowActionItem = this.instantiationService.createInstance(ViewletOverflowActivityActionItem, this.viewletOverflowAction, () => this.getOverflowingViewlets(), viewlet => this.viewletIdToActivity[viewlet.id] && this.viewletIdToActivity[viewlet.id].badge); this.viewletSwitcherBar.push(this.viewletOverflowAction, { label: true, icon: true }); } } + private getOverflowingViewlets(): ViewletDescriptor[] { + const viewlets = this.getPinnedViewlets(); + if (this.activeUnpinnedViewlet) { + viewlets.push(this.activeUnpinnedViewlet); + } + const visibleViewlets = Object.keys(this.viewletIdToActions); + + return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) === -1); + } + + private getVisibleViewlets(): ViewletDescriptor[] { + const viewlets = this.viewletService.getViewlets(); + const visibleViewlets = Object.keys(this.viewletIdToActions); + + return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) >= 0); + } + + private getPinnedViewlets(): ViewletDescriptor[] { + return this.viewletService.getViewlets().filter(viewlet => this.isPinned(viewlet.id)); + } + private pullViewlet(viewletId: string): void { const index = Object.keys(this.viewletIdToActions).indexOf(viewletId); this.viewletSwitcherBar.pull(index); @@ -228,6 +305,71 @@ export class ActivitybarPart extends Part implements IActivityService { return action; } + public unpin(viewletId: string): void { + if (!this.isPinned(viewletId)) { + return; + } + + const activeViewlet = this.viewletService.getActiveViewlet(); + const defaultViewletId = this.viewletService.getDefaultViewletId(); + const visibleViewlets = this.getVisibleViewlets(); + + let unpinPromise: TPromise; + + // Case: viewlet is not the active one or the active one is a different one + // Solv: we do nothing + if (!activeViewlet || activeViewlet.getId() !== viewletId) { + unpinPromise = TPromise.as(null); + } + + // Case: viewlet is not the default viewlet and default viewlet is still showing + // Solv: we open the default viewlet + else if (defaultViewletId !== viewletId && this.isPinned(defaultViewletId)) { + unpinPromise = this.viewletService.openViewlet(defaultViewletId, true); + } + + // Case: we closed the last visible viewlet + // Solv: we hide the sidebar + else if (visibleViewlets.length === 1) { + unpinPromise = TPromise.as(this.partService.setSideBarHidden(true)); + } + + // Case: we closed the default viewlet + // Solv: we open the next visible viewlet from top + else { + unpinPromise = this.viewletService.openViewlet(visibleViewlets.filter(viewlet => viewlet.id !== viewletId)[0].id, true); + } + + unpinPromise.then(() => { + + // then add to unpinned and update switcher + this.unpinnedViewlets.push(viewletId); + this.unpinnedViewlets = arrays.distinct(this.unpinnedViewlets); + + this.updateViewletSwitcher(); + }); + } + + public isPinned(viewletId: string): boolean { + return this.unpinnedViewlets.indexOf(viewletId) === -1; + } + + public pin(viewletId: string): void { + if (this.isPinned(viewletId)) { + return; + } + + // first open that viewlet + this.viewletService.openViewlet(viewletId, true).then(() => { + + // then update + const index = this.unpinnedViewlets.indexOf(viewletId); + this.unpinnedViewlets.splice(index, 1); + + this.updateViewletSwitcher(); + }); + } + /** * Layout title, content and status area in the given dimension. */ @@ -252,4 +394,13 @@ export class ActivitybarPart extends Part implements IActivityService { super.dispose(); } + + public shutdown(): void { + + // Persist Hidden State + this.memento[ActivitybarPart.UNPINNED_VIEWLETS] = this.unpinnedViewlets; + + // Pass to super + super.shutdown(); + } } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 20273dc58a3692876093c79062baf1d4edf30482..42eea7c819f9fefc104c09b6e9326f45f0fc5015 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -11,7 +11,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import Event from 'vs/base/common/event'; import { Builder } from 'vs/base/browser/builder'; import { Registry } from 'vs/platform/platform'; -import { ActivityAction } from 'vs/workbench/browser/parts/activitybar/activityAction'; +import { ActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { Scope } from 'vs/workbench/browser/actionBarRegistry'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actionRegistry'; diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 0b63820f2513bb6808b50190206892953e9625f8..afb144a49a3f3dbc78561112ac4c8eaa17427a6b 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -39,7 +39,6 @@ import { StatusbarPart } from 'vs/workbench/browser/parts/statusbar/statusbarPar import { TitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; import { WorkbenchLayout } from 'vs/workbench/browser/layout'; import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actionBarRegistry'; -import { ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet'; import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickOpenController'; import { DiffEditorInput, toDiffLabel } from 'vs/workbench/common/editor/diffEditorInput'; @@ -57,7 +56,7 @@ import { ConfigurationEditingService } from 'vs/workbench/services/configuration import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IActivityService } from 'vs/workbench/services/activity/common/activityService'; +import { IActivityBarService } from 'vs/workbench/services/activity/common/activityBarService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewletService } from 'vs/workbench/services/viewlet/browser/viewletService'; import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; @@ -273,7 +272,7 @@ export class Workbench implements IPartService { } if (!viewletIdToRestore) { - viewletIdToRestore = Registry.as(ViewletExtensions.Viewlets).getDefaultViewletId(); + viewletIdToRestore = this.viewletService.getDefaultViewletId(); } viewletRestoreStopWatch = StopWatch.create(); @@ -440,7 +439,7 @@ export class Workbench implements IPartService { this.activitybarPart = this.instantiationService.createInstance(ActivitybarPart, Identifiers.ACTIVITYBAR_PART); this.toDispose.push(this.activitybarPart); this.toShutdown.push(this.activitybarPart); - serviceCollection.set(IActivityService, this.activitybarPart); + serviceCollection.set(IActivityBarService, this.activitybarPart); // Editor service (editor part) this.editorPart = this.instantiationService.createInstance(EditorPart, Identifiers.EDITOR_PART, !this.hasFilesToCreateOpenOrDiff); @@ -507,11 +506,6 @@ export class Workbench implements IPartService { this.sideBarHidden = true; // we hide sidebar in single-file-mode } - const viewletRegistry = Registry.as(ViewletExtensions.Viewlets); - if (!viewletRegistry.getDefaultViewletId()) { - this.sideBarHidden = true; // can only hide sidebar if we dont have a default Viewlet id - } - // Panel part visibility const panelRegistry = Registry.as(PanelExtensions.Panels); this.panelHidden = this.storageService.getBoolean(Workbench.panelHiddenSettingKey, StorageScope.WORKSPACE, true); @@ -702,8 +696,7 @@ export class Workbench implements IPartService { // If sidebar becomes visible, show last active Viewlet or default viewlet else if (!hidden && !this.sidebarPart.getActiveViewlet()) { - const registry = Registry.as(ViewletExtensions.Viewlets); - const viewletToOpen = this.sidebarPart.getLastActiveViewletId() || registry.getDefaultViewletId(); + const viewletToOpen = this.sidebarPart.getLastActiveViewletId() || this.viewletService.getDefaultViewletId(); if (viewletToOpen) { this.sidebarPart.openViewlet(viewletToOpen, true).done(null, errors.onUnexpectedError); } diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts index 464065e1e0fd78c34f1895305efa896d33ef7550..3de0661bb45eb173c0e1bb2666f22ed85764f385 100644 --- a/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts +++ b/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts @@ -13,7 +13,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TreeExplorerView } from 'vs/workbench/parts/explorers/browser/views/treeExplorerView'; import { TreeExplorerViewletState } from 'vs/workbench/parts/explorers/browser/views/treeExplorerViewer'; -import { IActivityService } from 'vs/workbench/services/activity/common/activityService'; export class TreeExplorerViewlet extends Viewlet { @@ -27,8 +26,7 @@ export class TreeExplorerViewlet extends Viewlet { constructor( viewletId: string, @ITelemetryService telemetryService: ITelemetryService, - @IInstantiationService private instantiationService: IInstantiationService, - @IActivityService private activityService: IActivityService + @IInstantiationService private instantiationService: IInstantiationService ) { super(viewletId, telemetryService); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 2d9851cd8118d0f244f268d5a7bab774f0f106c2..89a9af1c423d68c237dbb1c14364aad1982473f8 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -44,7 +44,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IMessageService, CloseAction } from 'vs/platform/message/common/message'; import Severity from 'vs/base/common/severity'; -import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activityService'; +import { IActivityBarService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; interface SearchInputEvent extends Event { @@ -421,7 +421,7 @@ export class StatusUpdater implements IWorkbenchContribution { private disposables: IDisposable[]; constructor( - @IActivityService private activityService: IActivityService, + @IActivityBarService private activityBarService: IActivityBarService, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService ) { extensionsWorkbenchService.onChange(this.onServiceChange, this, this.disposables); @@ -433,16 +433,16 @@ export class StatusUpdater implements IWorkbenchContribution { private onServiceChange(): void { if (this.extensionsWorkbenchService.local.some(e => e.state === ExtensionState.Installing)) { - this.activityService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', 'Extensions')), 'extensions-badge progress-badge'); + this.activityBarService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', 'Extensions')), 'extensions-badge progress-badge'); return; } const outdated = this.extensionsWorkbenchService.local.reduce((r, e) => r + (e.outdated ? 1 : 0), 0); if (outdated > 0) { const badge = new NumberBadge(outdated, n => localize('outdatedExtensions', '{0} Outdated Extensions', n)); - this.activityService.showActivity(VIEWLET_ID, badge, 'extensions-badge count-badge'); + this.activityBarService.showActivity(VIEWLET_ID, badge, 'extensions-badge count-badge'); } else { - this.activityService.showActivity(VIEWLET_ID, null, 'extensions-badge'); + this.activityBarService.showActivity(VIEWLET_ID, null, 'extensions-badge'); } } diff --git a/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts b/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts index 8b17bef1205e7d2c2a98f7867f3f1b0688e8bf29..b03fc6c8f74fd42d77e62a0a63bcb5f7f9265de9 100644 --- a/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts +++ b/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts @@ -19,7 +19,7 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activityService'; +import { IActivityBarService, NumberBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import arrays = require('vs/base/common/arrays'); @@ -38,7 +38,7 @@ export class DirtyFilesTracker implements IWorkbenchContribution { @ILifecycleService private lifecycleService: ILifecycleService, @IEditorGroupService editorGroupService: IEditorGroupService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IActivityService private activityService: IActivityService, + @IActivityBarService private activityBarService: IActivityBarService, @IWindowService private windowService: IWindowService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { @@ -148,9 +148,9 @@ export class DirtyFilesTracker implements IWorkbenchContribution { const dirtyCount = this.textFileService.getDirty().length; this.lastDirtyCount = dirtyCount; if (dirtyCount > 0) { - this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); + this.activityBarService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); } else { - this.activityService.clearActivity(VIEWLET_ID); + this.activityBarService.clearActivity(VIEWLET_ID); } } diff --git a/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts b/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts index f0b0644c178e88d09458b8b4af96ef40238754cd..69203f26b5e8afe2d818320167b6c4d722b9d473 100644 --- a/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts +++ b/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts @@ -23,7 +23,7 @@ import confregistry = require('vs/platform/configuration/common/configurationReg import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import quickopen = require('vs/workbench/browser/quickopen'); import 'vs/workbench/parts/git/browser/gitEditorContributions'; -import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activityService'; +import { IActivityBarService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { IEventService } from 'vs/platform/event/common/event'; import { IMessageService } from 'vs/platform/message/common/message'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -37,7 +37,7 @@ export class StatusUpdater implements ext.IWorkbenchContribution { private gitService: IGitService; private eventService: IEventService; - private activityService: IActivityService; + private activityBarService: IActivityBarService; private messageService: IMessageService; private configurationService: IConfigurationService; private progressBadgeDelayer: async.Delayer; @@ -46,13 +46,13 @@ export class StatusUpdater implements ext.IWorkbenchContribution { constructor( @IGitService gitService: IGitService, @IEventService eventService: IEventService, - @IActivityService activityService: IActivityService, + @IActivityBarService activityBarService: IActivityBarService, @IMessageService messageService: IMessageService, @IConfigurationService configurationService: IConfigurationService ) { this.gitService = gitService; this.eventService = eventService; - this.activityService = activityService; + this.activityBarService = activityBarService; this.messageService = messageService; this.configurationService = configurationService; @@ -66,12 +66,12 @@ export class StatusUpdater implements ext.IWorkbenchContribution { private onGitServiceChange(): void { if (this.gitService.getState() !== git.ServiceState.OK) { this.progressBadgeDelayer.cancel(); - this.activityService.showActivity('workbench.view.git', null, 'git-viewlet-label'); + this.activityBarService.showActivity('workbench.view.git', null, 'git-viewlet-label'); } else if (this.gitService.isIdle()) { this.showChangesBadge(); } else { this.progressBadgeDelayer.trigger(() => { - this.activityService.showActivity('workbench.view.git', new ProgressBadge(() => nls.localize('gitProgressBadge', 'Running git status')), 'git-viewlet-label-progress'); + this.activityBarService.showActivity('workbench.view.git', new ProgressBadge(() => nls.localize('gitProgressBadge', 'Running git status')), 'git-viewlet-label-progress'); }); } } @@ -95,7 +95,7 @@ export class StatusUpdater implements ext.IWorkbenchContribution { .filter(filter); const badge = new NumberBadge(statuses.length, num => nls.localize('gitPendingChangesBadge', '{0} pending changes', num)); - this.activityService.showActivity('workbench.view.git', badge, 'git-viewlet-label'); + this.activityBarService.showActivity('workbench.view.git', badge, 'git-viewlet-label'); } public getId(): string { diff --git a/src/vs/workbench/parts/markers/browser/markersWorkbenchContributions.ts b/src/vs/workbench/parts/markers/browser/markersWorkbenchContributions.ts index 87eef973a1ee6f70a81b80ceaa7b665802b2bf49..8faf254ddf93daf67218d4c578931080f348da9c 100644 --- a/src/vs/workbench/parts/markers/browser/markersWorkbenchContributions.ts +++ b/src/vs/workbench/parts/markers/browser/markersWorkbenchContributions.ts @@ -11,7 +11,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import * as platform from 'vs/platform/platform'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activityService'; +import { IActivityBarService, NumberBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry'; import * as panel from 'vs/workbench/browser/panel'; @@ -25,7 +25,7 @@ class StatusUpdater implements IWorkbenchContribution { constructor( @IMarkerService private markerService: IMarkerService, - @IActivityService private activityService: IActivityService + @IActivityBarService private activityBarService: IActivityBarService ) { this.toDispose = []; @@ -37,9 +37,9 @@ class StatusUpdater implements IWorkbenchContribution { const problemCount = stats.errors + stats.warnings + stats.infos + stats.unknowns; if (problemCount > 0) { const badge = new NumberBadge(problemCount, n => localize({ comment: ['Argument represents count (number) of errors and warnings.'], key: 'errorsAndWarnings' }, '{0} Errors and Warnings', n)); - this.activityService.showActivity(Constants.MARKERS_PANEL_ID, badge); + this.activityBarService.showActivity(Constants.MARKERS_PANEL_ID, badge); } else { - this.activityService.showActivity(Constants.MARKERS_PANEL_ID, null); + this.activityBarService.showActivity(Constants.MARKERS_PANEL_ID, null); } } diff --git a/src/vs/workbench/services/activity/common/activityService.ts b/src/vs/workbench/services/activity/common/activityBarService.ts similarity index 72% rename from src/vs/workbench/services/activity/common/activityService.ts rename to src/vs/workbench/services/activity/common/activityBarService.ts index bdc5638ac0ae87975c7d7bbfca297452834f3c5f..355cf6b25eb7f2ab526aeb8834c93e7e292adfca 100644 --- a/src/vs/workbench/services/activity/common/activityService.ts +++ b/src/vs/workbench/services/activity/common/activityBarService.ts @@ -56,18 +56,33 @@ export class IconBadge extends BaseBadge { export class ProgressBadge extends BaseBadge { } -export const IActivityService = createDecorator('activityService'); +export const IActivityBarService = createDecorator('activityBarService'); -export interface IActivityService { +export interface IActivityBarService { _serviceBrand: any; /** - * Show activity in the activitybar for the given viewlet or panel. + * Show activity in the activitybar for the given viewlet. */ - showActivity(compositeId: string, badge: IBadge, clazz?: string): void; + showActivity(viewletId: string, badge: IBadge, clazz?: string): void; /** - * Clears activity shown in the activitybar for the given viewlet or panel. + * Clears activity shown in the activitybar for the given viewlet. */ - clearActivity(compositeId: string): void; + clearActivity(viewletId: string): void; + + /** + * Unpins a viewlet from the activitybar. + */ + unpin(viewletId: string): void; + + /** + * Pin a viewlet inside the activity bar. + */ + pin(viewletId: string): void; + + /** + * Find out if a viewlet is pinned in the activity bar. + */ + isPinned(viewletId: string): boolean; } \ No newline at end of file diff --git a/src/vs/workbench/services/viewlet/browser/viewlet.ts b/src/vs/workbench/services/viewlet/browser/viewlet.ts index 38fa31e77b11f90aa43ffa8c5268f17f6a109e49..8160c2e9b05f15a5a4f31d39f89df45cea5f7e51 100644 --- a/src/vs/workbench/services/viewlet/browser/viewlet.ts +++ b/src/vs/workbench/services/viewlet/browser/viewlet.ts @@ -28,6 +28,16 @@ export interface IViewletService { */ getActiveViewlet(): IViewlet; + /** + * Returns the id of the default viewlet. + */ + getDefaultViewletId(): string; + + /** + * Returns the viewlet by id. + */ + getViewlet(id: string): ViewletDescriptor; + /** * Returns all registered viewlets */ diff --git a/src/vs/workbench/services/viewlet/browser/viewletService.ts b/src/vs/workbench/services/viewlet/browser/viewletService.ts index fc4f318887577cb3db65916b3e6db5b065ddc42b..a3565893566ef70bc1fbfea02e151b38fe620825 100644 --- a/src/vs/workbench/services/viewlet/browser/viewletService.ts +++ b/src/vs/workbench/services/viewlet/browser/viewletService.ts @@ -72,7 +72,7 @@ export class ViewletService implements IViewletService { } // Fallback to default viewlet if extension viewlet is still not found (e.g. uninstalled) - return this.sidebarPart.openViewlet(this.viewletRegistry.getDefaultViewletId(), focus); + return this.sidebarPart.openViewlet(this.getDefaultViewletId(), focus); }); } @@ -91,4 +91,12 @@ export class ViewletService implements IViewletService { .filter(viewlet => !viewlet.extensionId) .sort((v1, v2) => v1.order - v2.order); } + + public getDefaultViewletId(): string { + return this.viewletRegistry.getDefaultViewletId(); + } + + public getViewlet(id: string): ViewletDescriptor { + return this.getViewlets().filter(viewlet => viewlet.id === id)[0]; + } } \ No newline at end of file diff --git a/src/vs/workbench/test/browser/services.test.ts b/src/vs/workbench/test/browser/services.test.ts index 59c76ffb782bf2649c19014a00a5a224a3b4e0b9..46bef07542c4e8ec37a540260f61b6c3d075b6bb 100644 --- a/src/vs/workbench/test/browser/services.test.ts +++ b/src/vs/workbench/test/browser/services.test.ts @@ -118,6 +118,14 @@ class TestViewletService implements IViewletService { public dispose() { } + + public getDefaultViewletId(): string { + return 'workbench.view.explorer'; + } + + public getViewlet(id: string): ViewletDescriptor { + return null; + } } class TestPanelService implements IPanelService {