diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index fa9d32a681e..9630c534b18 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/activitybarpart'; import * as nls from 'vs/nls'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; +import { GLOBAL_ACTIVITY_ID, IActivity } from 'vs/workbench/common/activity'; import { Registry } from 'vs/platform/registry/common/platform'; import { Part } from 'vs/workbench/browser/part'; import { GlobalActivityActionViewItem, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction, AccountsActionViewItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; @@ -29,7 +29,7 @@ import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; +import { isUndefinedOrNull, assertIsDefined, isString } from 'vs/base/common/types'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Schemas } from 'vs/base/common/network'; @@ -458,12 +458,10 @@ export class ActivitybarPart extends Part implements IActivityBarService { for (const viewlet of viewlets) { this.enableCompositeActions(viewlet); - const viewContainer = this.getViewContainer(viewlet.id); - if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onDidChangeActiveViews(viewlet, viewDescriptors); - this.viewletDisposables.set(viewlet.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors))); - } + const viewContainer = this.getViewContainer(viewlet.id)!; + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); + this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty); + this.viewletDisposables.set(viewlet.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty))); } } @@ -477,10 +475,33 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.hideComposite(viewletId); } - private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { + private updateActivity(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { + const viewDescriptor = viewDescriptors.activeViewDescriptors[0]; + + // Use the viewlet icon if any view inside belongs to it statically + const shouldUseViewletIcon = viewDescriptors.allViewDescriptors.some(v => this.viewDescriptorService.getDefaultContainer(v.id)?.id === viewlet.id); + + const activity: IActivity = { + id: viewlet.id, + name: shouldUseViewletIcon ? viewlet.name : viewDescriptor.name, + cssClass: shouldUseViewletIcon ? viewlet.cssClass : (isString(viewDescriptor.containerIcon) ? viewDescriptor.containerIcon : (viewDescriptor.containerIcon === undefined ? 'codicon-window' : undefined)), + iconUrl: shouldUseViewletIcon ? viewlet.iconUrl : (viewDescriptor.containerIcon instanceof URI ? viewDescriptor.containerIcon : undefined), + keybindingId: viewlet.keybindingId + }; + + const { activityAction, pinnedAction } = this.getCompositeActions(viewlet.id); + activityAction.setActivity(activity); + + if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { + pinnedAction.setActivity(activity); + } + } + + private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection, hideIfEmpty?: boolean): void { if (viewDescriptors.activeViewDescriptors.length) { + this.updateActivity(viewlet, viewDescriptors); this.compositeBar.addComposite(viewlet); - } else { + } else if (hideIfEmpty) { this.hideComposite(viewlet.id); } } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index f015ec244ac..796def879c1 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -39,6 +39,7 @@ import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuAction import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { Before2D } from 'vs/workbench/browser/dnd'; +import { IActivity } from 'vs/workbench/common/activity'; interface ICachedPanel { id: string; @@ -238,9 +239,27 @@ export class PanelPart extends CompositePart implements IPanelService { } } + private updateActivity(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + const viewDescriptor = viewDescriptors.activeViewDescriptors[0]; + + const activity: IActivity = { + id: panel.id, + name: viewDescriptor.name, + keybindingId: panel.keybindingId + }; + + const { activityAction, pinnedAction } = this.getCompositeActions(panel.id); + activityAction.setActivity(activity); + + if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { + pinnedAction.setActivity(activity); + } + } + private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { if (viewDescriptors.activeViewDescriptors.length) { this.compositeBar.addComposite(panel); + this.updateActivity(panel, viewDescriptors); } else { this.hideComposite(panel.id); } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 3e7dc3d3f26..eb444c5f83d 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -906,15 +906,19 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getTitle(): string { + // use registered title if any of our panes are statically registered to this container + const allViewDescriptors = this.viewDescriptorService.getViewDescriptors(this.viewContainer).allViewDescriptors; + const containerTitle = allViewDescriptors.some(v => this.viewDescriptorService.getDefaultContainer(v.id) === this.viewContainer) ? this.viewContainer.name : allViewDescriptors[0].name; + if (this.isViewMergedWithContainer()) { const paneItemTitle = this.paneItems[0].pane.title; - if (this.options.donotShowContainerTitleWhenMergedWithContainer || this.viewContainer.name === paneItemTitle) { + if (this.options.donotShowContainerTitleWhenMergedWithContainer || containerTitle === paneItemTitle) { return this.paneItems[0].pane.title; } - return paneItemTitle ? `${this.viewContainer.name}: ${paneItemTitle}` : this.viewContainer.name; + return paneItemTitle ? `${containerTitle}: ${paneItemTitle}` : containerTitle; } - return this.viewContainer.name; + return containerTitle; } private showContextMenu(event: StandardMouseEvent): void { diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index d722f3f1c06..5255e0e4083 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -110,12 +110,12 @@ Registry.as(ViewExtensions.ViewsRegistry).registerViews([{ // Register default debug views const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); -viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); -viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); -viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); +viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); +viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(WelcomeView), order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); +viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); registerCommands(); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 93fa8408537..559404a96fc 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -123,6 +123,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: EmptyView.ID, name: EmptyView.NAME, + containerIcon: 'codicon-files', ctorDescriptor: new SyncDescriptor(EmptyView), order: 1, canToggleVisibility: true, @@ -136,6 +137,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: ExplorerView.ID, name: localize('folders', "Folders"), + containerIcon: 'codicons-files', ctorDescriptor: new SyncDescriptor(ExplorerView), order: 1, canToggleVisibility: false, diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index ab9cf63219d..e7cf90f7098 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -333,6 +333,7 @@ export class MainPaneDescriptor implements IViewDescriptor { readonly id = MainPane.ID; readonly name = MainPane.TITLE; + readonly containerIcon = 'codicon-source-control'; readonly ctorDescriptor: SyncDescriptor; readonly canToggleVisibility = true; readonly hideByDefault = false; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 1d3fdd35ad5..e60545de4a6 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -69,6 +69,7 @@ export class EmptyPane extends ViewPane { export class EmptyPaneDescriptor implements IViewDescriptor { readonly id = EmptyPane.ID; readonly name = EmptyPane.TITLE; + readonly containerIcon = 'codicon-source-control'; readonly ctorDescriptor = new SyncDescriptor(EmptyPane); readonly canToggleVisibility = true; readonly hideByDefault = false; diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index f1c7886537c..29004a48b0f 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -16,6 +16,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { firstIndex } from 'vs/base/common/arrays'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { generateUuid } from 'vs/base/common/uuid'; +import { localize } from 'vs/nls'; class CounterSet implements IReadableSet { @@ -173,7 +175,6 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl interface ICachedViewContainerInfo { containerId: string; location?: ViewContainerLocation; - sourceViewId?: string; } export class ViewDescriptorService extends Disposable implements IViewDescriptorService { @@ -198,7 +199,6 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly viewContainersRegistry: IViewContainersRegistry; private cachedViewInfo: Map; - private generatedContainerSourceViewIds: Map; private _cachedViewPositionsValue: string | undefined; private get cachedViewPositionsValue(): string { @@ -233,7 +233,6 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - this.generatedContainerSourceViewIds = new Map(); this.cachedViewInfo = this.getCachedViewPositions(); @@ -271,9 +270,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor if (containerData.cachedContainerInfo && this.shouldGenerateContainer(containerData.cachedContainerInfo)) { const containerInfo = containerData.cachedContainerInfo; - const sourceViewDescriptor = this.viewsRegistry.getView(containerInfo.sourceViewId!); - if (sourceViewDescriptor && !this.viewContainersRegistry.get(containerId)) { - this.registerViewContainerForSingleView(sourceViewDescriptor, containerInfo.location!); + if (!this.viewContainersRegistry.get(containerId)) { + this.registerGeneratedViewContainer(containerInfo.location!, containerId); } } @@ -309,12 +307,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // check if we should generate this container if (this.shouldGenerateContainer(containerInfo)) { - const sourceView = this.getViewDescriptor(containerInfo.sourceViewId!); - - if (sourceView) { - this.registerViewContainerForSingleView(sourceView, containerInfo.location!); - continue; - } + this.registerGeneratedViewContainer(containerInfo.location!, containerId); + continue; } if (fallbackToDefault) { @@ -354,7 +348,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private shouldGenerateContainer(containerInfo: ICachedViewContainerInfo): boolean { - return !!containerInfo.sourceViewId && containerInfo.location !== undefined; + return containerInfo.containerId.startsWith(ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX) && containerInfo.location !== undefined; } private onDidDeregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { @@ -422,7 +416,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void { - let container = this.registerViewContainerForSingleView(view, location); + let container = this.registerGeneratedViewContainer(location); this.moveViewsToContainer([view], container); } @@ -494,14 +488,14 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - private registerViewContainerForSingleView(sourceView: IViewDescriptor, location: ViewContainerLocation): ViewContainer { - const id = this.generateContainerIdFromSourceViewId(sourceView.id, location); + private registerGeneratedViewContainer(location: ViewContainerLocation, existingId?: string): ViewContainer { + const id = existingId || this.generateContainerId(location); return this.viewContainersRegistry.registerViewContainer({ id, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }]), - name: sourceView.name, - icon: location === ViewContainerLocation.Sidebar ? sourceView.containerIcon || 'codicon-window' : undefined, + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + name: localize('generatedContainerName', "Custom Views"), + icon: location === ViewContainerLocation.Sidebar ? 'codicon-window' : undefined, hideIfEmpty: true }, location); } @@ -535,12 +529,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const prevViewContainer = this.getViewContainer(viewId); const newViewContainerInfo = newCachedPositions.get(viewId)!; // Verify if we need to create the destination container - if (newViewContainerInfo.sourceViewId) { - const sourceViewDescriptor = this.getViewDescriptor(newViewContainerInfo.sourceViewId); - - if (!this.viewContainersRegistry.get(newViewContainerInfo.containerId) && sourceViewDescriptor) { - this.registerViewContainerForSingleView(sourceViewDescriptor, newViewContainerInfo.location!); - } + if (!this.viewContainersRegistry.get(newViewContainerInfo.containerId)) { + this.registerGeneratedViewContainer(newViewContainerInfo.location!, newViewContainerInfo.containerId); } // Try moving to the new container @@ -574,11 +564,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } // Generated Container Id Format + // {Common Prefix}.{Location}.{Uniqueness Id} + // Old Format (deprecated) // {Common Prefix}.{Uniqueness Id}.{Source View Id} - private generateContainerIdFromSourceViewId(viewId: string, location: ViewContainerLocation): string { - const result = `${ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX}.${location === ViewContainerLocation.Panel ? 'panel' : 'sidebar'}.${viewId}`; - this.generatedContainerSourceViewIds.set(result, viewId); - return result; + private generateContainerId(location: ViewContainerLocation): string { + return `${ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX}.${location === ViewContainerLocation.Panel ? 'panel' : 'sidebar'}.${generateUuid()}`; } private getStoredCachedViewPositionsValue(): string { @@ -593,12 +583,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.viewContainersRegistry.all.forEach(viewContainer => { const viewDescriptorCollection = this.getViewDescriptors(viewContainer); viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { - const sourceViewId = this.generatedContainerSourceViewIds.get(viewContainer.id); const containerLocation = this.getViewContainerLocation(viewContainer); this.cachedViewInfo.set(viewDescriptor.id, { containerId: viewContainer.id, - location: containerLocation, - sourceViewId: sourceViewId + location: containerLocation }); }); }); @@ -682,9 +670,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private addViews(container: ViewContainer, views: IViewDescriptor[]): void { // Update in memory cache const location = this.getViewContainerLocation(container); - const sourceViewId = this.generatedContainerSourceViewIds.get(container.id); views.forEach(view => { - this.cachedViewInfo.set(view.id, { containerId: container.id, location, sourceViewId }); + this.cachedViewInfo.set(view.id, { containerId: container.id, location }); this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainer(view.id) === container); });