Path: blob/main/src/vs/workbench/browser/parts/paneCompositeBar.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { localize } from '../../../nls.js';6import { ActionsOrientation } from '../../../base/browser/ui/actionbar/actionbar.js';7import { IActivityService } from '../../services/activity/common/activity.js';8import { IWorkbenchLayoutService, Parts } from '../../services/layout/browser/layoutService.js';9import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';10import { IDisposable, DisposableStore, Disposable, DisposableMap } from '../../../base/common/lifecycle.js';11import { IColorTheme } from '../../../platform/theme/common/themeService.js';12import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from './compositeBar.js';13import { Dimension, isMouseEvent } from '../../../base/browser/dom.js';14import { createCSSRule } from '../../../base/browser/domStylesheets.js';15import { asCSSUrl } from '../../../base/browser/cssValue.js';16import { IStorageService, StorageScope, StorageTarget } from '../../../platform/storage/common/storage.js';17import { IExtensionService } from '../../services/extensions/common/extensions.js';18import { URI, UriComponents } from '../../../base/common/uri.js';19import { ToggleCompositePinnedAction, ICompositeBarColors, IActivityHoverOptions, ToggleCompositeBadgeAction, CompositeBarAction, ICompositeBar, ICompositeBarActionItem } from './compositeBarActions.js';20import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation } from '../../common/views.js';21import { IContextKeyService, ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js';22import { isString } from '../../../base/common/types.js';23import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js';24import { isNative } from '../../../base/common/platform.js';25import { Before2D, ICompositeDragAndDrop } from '../dnd.js';26import { ThemeIcon } from '../../../base/common/themables.js';27import { IAction, Separator, SubmenuAction, toAction } from '../../../base/common/actions.js';28import { StringSHA1 } from '../../../base/common/hash.js';29import { GestureEvent } from '../../../base/browser/touch.js';30import { IPaneCompositePart } from './paneCompositePart.js';31import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';32import { IViewsService } from '../../services/views/common/viewsService.js';3334interface IPlaceholderViewContainer {35readonly id: string;36readonly name?: string;37readonly iconUrl?: UriComponents;38readonly themeIcon?: ThemeIcon;39readonly isBuiltin?: boolean;40readonly views?: { when?: string }[];41// TODO @sandy081: Remove this after a while. Migrated to visible in IViewContainerWorkspaceState42readonly visible?: boolean;43}4445interface IPinnedViewContainer {46readonly id: string;47readonly pinned: boolean;48readonly order?: number;49// TODO @sandy081: Remove this after a while. Migrated to visible in IViewContainerWorkspaceState50readonly visible: boolean;51}5253interface IViewContainerWorkspaceState {54readonly id: string;55readonly visible: boolean;56}5758interface ICachedViewContainer {59readonly id: string;60name?: string;61icon?: URI | ThemeIcon;62readonly pinned: boolean;63readonly order?: number;64visible: boolean;65isBuiltin?: boolean;66views?: { when?: string }[];67}6869export interface IPaneCompositeBarOptions {70readonly partContainerClass: string;71readonly pinnedViewContainersKey: string;72readonly placeholderViewContainersKey: string;73readonly viewContainersWorkspaceStateKey: string;74readonly icon: boolean;75readonly compact?: boolean;76readonly iconSize: number;77readonly recomputeSizes: boolean;78readonly orientation: ActionsOrientation;79readonly compositeSize: number;80readonly overflowActionSize: number;81readonly preventLoopNavigation?: boolean;82readonly activityHoverOptions: IActivityHoverOptions;83readonly fillExtraContextMenuActions: (actions: IAction[], e?: MouseEvent | GestureEvent) => void;84readonly colors: (theme: IColorTheme) => ICompositeBarColors;85}8687export class PaneCompositeBar extends Disposable {8889private readonly viewContainerDisposables = this._register(new DisposableMap<string, IDisposable>());90private readonly location: ViewContainerLocation;9192private readonly compositeBar: CompositeBar;93readonly dndHandler: ICompositeDragAndDrop;94private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction; pinnedAction: ToggleCompositePinnedAction; badgeAction: ToggleCompositeBadgeAction }>();9596private hasExtensionsRegistered: boolean = false;9798constructor(99protected readonly options: IPaneCompositeBarOptions,100protected readonly part: Parts,101private readonly paneCompositePart: IPaneCompositePart,102@IInstantiationService protected readonly instantiationService: IInstantiationService,103@IStorageService private readonly storageService: IStorageService,104@IExtensionService private readonly extensionService: IExtensionService,105@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,106@IViewsService private readonly viewService: IViewsService,107@IContextKeyService protected readonly contextKeyService: IContextKeyService,108@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,109@IWorkbenchLayoutService protected readonly layoutService: IWorkbenchLayoutService,110) {111super();112113this.location = paneCompositePart.partId === Parts.PANEL_PART114? ViewContainerLocation.Panel : paneCompositePart.partId === Parts.AUXILIARYBAR_PART115? ViewContainerLocation.AuxiliaryBar : ViewContainerLocation.Sidebar;116117this.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, this.location, this.options.orientation,118async (id: string, focus?: boolean) => { return await this.paneCompositePart.openPaneComposite(id, focus) ?? null; },119(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, this.options.orientation === ActionsOrientation.VERTICAL ? before?.verticallyBefore : before?.horizontallyBefore),120() => this.compositeBar.getCompositeBarItems(),121);122123const cachedItems = this.cachedViewContainers124.map(container => ({125id: container.id,126name: container.name,127visible: !this.shouldBeHidden(container.id, container),128order: container.order,129pinned: container.pinned,130}));131this.compositeBar = this.createCompositeBar(cachedItems);132this.onDidRegisterViewContainers(this.getViewContainers());133this.registerListeners();134}135136private createCompositeBar(cachedItems: ICompositeBarItem[]) {137return this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, {138icon: this.options.icon,139compact: this.options.compact,140orientation: this.options.orientation,141activityHoverOptions: this.options.activityHoverOptions,142preventLoopNavigation: this.options.preventLoopNavigation,143openComposite: async (compositeId, preserveFocus) => {144return (await this.paneCompositePart.openPaneComposite(compositeId, !preserveFocus)) ?? null;145},146getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,147getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction,148getCompositeBadgeAction: compositeId => this.getCompositeActions(compositeId).badgeAction,149getOnCompositeClickAction: compositeId => this.getCompositeActions(compositeId).activityAction,150fillExtraContextMenuActions: (actions, e) => this.options.fillExtraContextMenuActions(actions, e),151getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId),152getDefaultCompositeId: () => this.viewDescriptorService.getDefaultViewContainer(this.location)?.id,153dndHandler: this.dndHandler,154compositeSize: this.options.compositeSize,155overflowActionSize: this.options.overflowActionSize,156colors: theme => this.options.colors(theme),157}));158}159160private getContextMenuActionsForComposite(compositeId: string): IAction[] {161const actions: IAction[] = [new Separator()];162163const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!;164const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!;165const currentLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer);166167// Move View Container168const moveActions = [];169for (const location of [ViewContainerLocation.Sidebar, ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel]) {170if (currentLocation !== location) {171moveActions.push(this.createMoveAction(viewContainer, location, defaultLocation));172}173}174175actions.push(new SubmenuAction('moveToMenu', localize('moveToMenu', "Move To"), moveActions));176177// Reset Location178if (defaultLocation !== currentLocation) {179actions.push(toAction({180id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => {181this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation, undefined, 'resetLocationAction');182this.viewService.openViewContainer(viewContainer.id, true);183}184}));185} else {186const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);187if (viewContainerModel.allViewDescriptors.length === 1) {188const viewToReset = viewContainerModel.allViewDescriptors[0];189const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!;190if (defaultContainer !== viewContainer) {191actions.push(toAction({192id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => {193this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer, undefined, 'resetLocationAction');194this.viewService.openViewContainer(viewContainer.id, true);195}196}));197}198}199}200201return actions;202}203204private createMoveAction(viewContainer: ViewContainer, newLocation: ViewContainerLocation, defaultLocation: ViewContainerLocation): IAction {205return toAction({206id: `moveViewContainerTo${newLocation}`,207label: newLocation === ViewContainerLocation.Panel ? localize('panel', "Panel") : newLocation === ViewContainerLocation.Sidebar ? localize('sidebar', "Primary Side Bar") : localize('auxiliarybar', "Secondary Side Bar"),208run: () => {209let index: number | undefined;210if (newLocation !== defaultLocation) {211index = this.viewDescriptorService.getViewContainersByLocation(newLocation).length; // move to the end of the location212} else {213index = undefined; // restore default location214}215this.viewDescriptorService.moveViewContainerToLocation(viewContainer, newLocation, index);216this.viewService.openViewContainer(viewContainer.id, true);217}218});219}220221private registerListeners(): void {222223// View Container Changes224this._register(this.viewDescriptorService.onDidChangeViewContainers(({ added, removed }) => this.onDidChangeViewContainers(added, removed)));225this._register(this.viewDescriptorService.onDidChangeContainerLocation(({ viewContainer, from, to }) => this.onDidChangeViewContainerLocation(viewContainer, from, to)));226227// View Container Visibility Changes228this._register(this.paneCompositePart.onDidPaneCompositeOpen(e => this.onDidChangeViewContainerVisibility(e.getId(), true)));229this._register(this.paneCompositePart.onDidPaneCompositeClose(e => this.onDidChangeViewContainerVisibility(e.getId(), false)));230231// Extension registration232this.extensionService.whenInstalledExtensionsRegistered().then(() => {233if (this._store.isDisposed) {234return;235}236this.onDidRegisterExtensions();237this._register(this.compositeBar.onDidChange(() => {238this.updateCompositeBarItemsFromStorage(true);239this.saveCachedViewContainers();240}));241this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, this.options.pinnedViewContainersKey, this._store)(() => this.updateCompositeBarItemsFromStorage(false)));242});243}244245private onDidChangeViewContainers(added: readonly { container: ViewContainer; location: ViewContainerLocation }[], removed: readonly { container: ViewContainer; location: ViewContainerLocation }[]) {246removed.filter(({ location }) => location === this.location).forEach(({ container }) => this.onDidDeregisterViewContainer(container));247this.onDidRegisterViewContainers(added.filter(({ location }) => location === this.location).map(({ container }) => container));248}249250private onDidChangeViewContainerLocation(container: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation) {251if (from === this.location) {252this.onDidDeregisterViewContainer(container);253}254255if (to === this.location) {256this.onDidRegisterViewContainers([container]);257}258}259260private onDidChangeViewContainerVisibility(id: string, visible: boolean) {261if (visible) {262// Activate view container action on opening of a view container263this.onDidViewContainerVisible(id);264} else {265// Deactivate view container action on close266this.compositeBar.deactivateComposite(id);267}268}269270private onDidRegisterExtensions(): void {271this.hasExtensionsRegistered = true;272273// show/hide/remove composites274for (const { id } of this.cachedViewContainers) {275const viewContainer = this.getViewContainer(id);276if (viewContainer) {277this.showOrHideViewContainer(viewContainer);278} else {279if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) {280this.removeComposite(id);281} else {282this.hideComposite(id);283}284}285}286287this.saveCachedViewContainers();288}289290private onDidViewContainerVisible(id: string): void {291const viewContainer = this.getViewContainer(id);292if (viewContainer) {293294// Update the composite bar by adding295this.addComposite(viewContainer);296this.compositeBar.activateComposite(viewContainer.id);297298if (this.shouldBeHidden(viewContainer)) {299const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);300if (viewContainerModel.activeViewDescriptors.length === 0) {301// Update the composite bar by hiding302this.hideComposite(viewContainer.id);303}304}305}306}307308create(parent: HTMLElement): HTMLElement {309return this.compositeBar.create(parent);310}311312private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction; pinnedAction: ToggleCompositePinnedAction; badgeAction: ToggleCompositeBadgeAction } {313let compositeActions = this.compositeActions.get(compositeId);314if (!compositeActions) {315const viewContainer = this.getViewContainer(compositeId);316if (viewContainer) {317const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);318compositeActions = {319activityAction: this._register(this.instantiationService.createInstance(ViewContainerActivityAction, this.toCompositeBarActionItemFrom(viewContainerModel), this.part, this.paneCompositePart)),320pinnedAction: this._register(new ToggleCompositePinnedAction(this.toCompositeBarActionItemFrom(viewContainerModel), this.compositeBar)),321badgeAction: this._register(new ToggleCompositeBadgeAction(this.toCompositeBarActionItemFrom(viewContainerModel), this.compositeBar))322};323} else {324const cachedComposite = this.cachedViewContainers.filter(c => c.id === compositeId)[0];325compositeActions = {326activityAction: this._register(this.instantiationService.createInstance(PlaceHolderViewContainerActivityAction, this.toCompositeBarActionItem(compositeId, cachedComposite?.name ?? compositeId, cachedComposite?.icon, undefined), this.part, this.paneCompositePart)),327pinnedAction: this._register(new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar)),328badgeAction: this._register(new PlaceHolderToggleCompositeBadgeAction(compositeId, this.compositeBar))329};330}331332this.compositeActions.set(compositeId, compositeActions);333}334335return compositeActions;336}337338private onDidRegisterViewContainers(viewContainers: readonly ViewContainer[]): void {339for (const viewContainer of viewContainers) {340this.addComposite(viewContainer);341342// Pin it by default if it is new343const cachedViewContainer = this.cachedViewContainers.filter(({ id }) => id === viewContainer.id)[0];344if (!cachedViewContainer) {345this.compositeBar.pin(viewContainer.id);346}347348// Active349const visibleViewContainer = this.paneCompositePart.getActivePaneComposite();350if (visibleViewContainer?.getId() === viewContainer.id) {351this.compositeBar.activateComposite(viewContainer.id);352}353354const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);355this.updateCompositeBarActionItem(viewContainer, viewContainerModel);356this.showOrHideViewContainer(viewContainer);357358const disposables = new DisposableStore();359disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateCompositeBarActionItem(viewContainer, viewContainerModel)));360disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.showOrHideViewContainer(viewContainer)));361362this.viewContainerDisposables.set(viewContainer.id, disposables);363}364}365366private onDidDeregisterViewContainer(viewContainer: ViewContainer): void {367this.viewContainerDisposables.deleteAndDispose(viewContainer.id);368this.removeComposite(viewContainer.id);369}370371private updateCompositeBarActionItem(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {372const compositeBarActionItem = this.toCompositeBarActionItemFrom(viewContainerModel);373const { activityAction, pinnedAction } = this.getCompositeActions(viewContainer.id);374activityAction.updateCompositeBarActionItem(compositeBarActionItem);375376if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) {377pinnedAction.setActivity(compositeBarActionItem);378}379380if (this.options.recomputeSizes) {381this.compositeBar.recomputeSizes();382}383384this.saveCachedViewContainers();385}386387private toCompositeBarActionItemFrom(viewContainerModel: IViewContainerModel): ICompositeBarActionItem {388return this.toCompositeBarActionItem(viewContainerModel.viewContainer.id, viewContainerModel.title, viewContainerModel.icon, viewContainerModel.keybindingId);389}390391private toCompositeBarActionItem(id: string, name: string, icon: URI | ThemeIcon | undefined, keybindingId: string | undefined): ICompositeBarActionItem {392let classNames: string[] | undefined = undefined;393let iconUrl: URI | undefined = undefined;394if (this.options.icon) {395if (URI.isUri(icon)) {396iconUrl = icon;397const cssUrl = asCSSUrl(icon);398const hash = new StringSHA1();399hash.update(cssUrl);400const iconId = `activity-${id.replace(/\./g, '-')}-${hash.digest()}`;401const iconClass = `.monaco-workbench .${this.options.partContainerClass} .monaco-action-bar .action-label.${iconId}`;402classNames = [iconId, 'uri-icon'];403createCSSRule(iconClass, `404mask: ${cssUrl} no-repeat 50% 50%;405mask-size: ${this.options.iconSize}px;406-webkit-mask: ${cssUrl} no-repeat 50% 50%;407-webkit-mask-size: ${this.options.iconSize}px;408mask-origin: padding;409-webkit-mask-origin: padding;410`);411} else if (ThemeIcon.isThemeIcon(icon)) {412classNames = ThemeIcon.asClassNameArray(icon);413}414}415416return { id, name, classNames, iconUrl, keybindingId };417}418419private showOrHideViewContainer(viewContainer: ViewContainer): void {420if (this.shouldBeHidden(viewContainer)) {421this.hideComposite(viewContainer.id);422} else {423this.addComposite(viewContainer);424425// Activate if this is the active pane composite426const activePaneComposite = this.paneCompositePart.getActivePaneComposite();427if (activePaneComposite?.getId() === viewContainer.id) {428this.compositeBar.activateComposite(viewContainer.id);429}430}431}432433private shouldBeHidden(viewContainerOrId: string | ViewContainer, cachedViewContainer?: ICachedViewContainer): boolean {434const viewContainer = isString(viewContainerOrId) ? this.getViewContainer(viewContainerOrId) : viewContainerOrId;435const viewContainerId = isString(viewContainerOrId) ? viewContainerOrId : viewContainerOrId.id;436437if (viewContainer) {438if (viewContainer.hideIfEmpty) {439if (this.viewService.isViewContainerActive(viewContainerId)) {440return false;441}442} else {443return false;444}445}446447// Check cache only if extensions are not yet registered and current window is not native (desktop) remote connection window448if (!this.hasExtensionsRegistered && !(this.part === Parts.SIDEBAR_PART && this.environmentService.remoteAuthority && isNative)) {449cachedViewContainer = cachedViewContainer || this.cachedViewContainers.find(({ id }) => id === viewContainerId);450451// Show builtin ViewContainer if not registered yet452if (!viewContainer && cachedViewContainer?.isBuiltin && cachedViewContainer?.visible) {453return false;454}455456if (cachedViewContainer?.views?.length) {457return cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)));458}459}460461return true;462}463464private addComposite(viewContainer: ViewContainer): void {465this.compositeBar.addComposite({ id: viewContainer.id, name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value, order: viewContainer.order, requestedIndex: viewContainer.requestedIndex });466}467468private hideComposite(compositeId: string): void {469this.compositeBar.hideComposite(compositeId);470471const compositeActions = this.compositeActions.get(compositeId);472if (compositeActions) {473compositeActions.activityAction.dispose();474compositeActions.pinnedAction.dispose();475this.compositeActions.delete(compositeId);476}477}478479private removeComposite(compositeId: string): void {480this.compositeBar.removeComposite(compositeId);481482const compositeActions = this.compositeActions.get(compositeId);483if (compositeActions) {484compositeActions.activityAction.dispose();485compositeActions.pinnedAction.dispose();486this.compositeActions.delete(compositeId);487}488}489490getPinnedPaneCompositeIds(): string[] {491const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id);492return this.getViewContainers()493.filter(v => this.compositeBar.isPinned(v.id))494.sort((v1, v2) => pinnedCompositeIds.indexOf(v1.id) - pinnedCompositeIds.indexOf(v2.id))495.map(v => v.id);496}497498getVisiblePaneCompositeIds(): string[] {499return this.compositeBar.getVisibleComposites()500.filter(v => this.paneCompositePart.getActivePaneComposite()?.getId() === v.id || this.compositeBar.isPinned(v.id))501.map(v => v.id);502}503504getPaneCompositeIds(): string[] {505return this.compositeBar.getVisibleComposites()506.map(v => v.id);507}508509getContextMenuActions(): IAction[] {510return this.compositeBar.getContextMenuActions();511}512513focus(index?: number): void {514this.compositeBar.focus(index);515}516517layout(width: number, height: number): void {518this.compositeBar.layout(new Dimension(width, height));519}520521private getViewContainer(id: string): ViewContainer | undefined {522const viewContainer = this.viewDescriptorService.getViewContainerById(id);523return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined;524}525526private getViewContainers(): readonly ViewContainer[] {527return this.viewDescriptorService.getViewContainersByLocation(this.location);528}529530private updateCompositeBarItemsFromStorage(retainExisting: boolean): void {531if (this.pinnedViewContainersValue === this.getStoredPinnedViewContainersValue()) {532return;533}534535this._placeholderViewContainersValue = undefined;536this._pinnedViewContainersValue = undefined;537this._cachedViewContainers = undefined;538539const newCompositeItems: ICompositeBarItem[] = [];540const compositeItems = this.compositeBar.getCompositeBarItems();541542for (const cachedViewContainer of this.cachedViewContainers) {543newCompositeItems.push({544id: cachedViewContainer.id,545name: cachedViewContainer.name,546order: cachedViewContainer.order,547pinned: cachedViewContainer.pinned,548visible: cachedViewContainer.visible && !!this.getViewContainer(cachedViewContainer.id),549});550}551552for (const viewContainer of this.getViewContainers()) {553// Add missing view containers554if (!newCompositeItems.some(({ id }) => id === viewContainer.id)) {555const index = compositeItems.findIndex(({ id }) => id === viewContainer.id);556if (index !== -1) {557const compositeItem = compositeItems[index];558newCompositeItems.splice(index, 0, {559id: viewContainer.id,560name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value,561order: compositeItem.order,562pinned: compositeItem.pinned,563visible: compositeItem.visible,564});565} else {566newCompositeItems.push({567id: viewContainer.id,568name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value,569order: viewContainer.order,570pinned: true,571visible: !this.shouldBeHidden(viewContainer),572});573}574}575}576577if (retainExisting) {578for (const compositeItem of compositeItems) {579const newCompositeItem = newCompositeItems.find(({ id }) => id === compositeItem.id);580if (!newCompositeItem) {581newCompositeItems.push(compositeItem);582}583}584}585586this.compositeBar.setCompositeBarItems(newCompositeItems);587}588589private saveCachedViewContainers(): void {590const state: ICachedViewContainer[] = [];591592const compositeItems = this.compositeBar.getCompositeBarItems();593for (const compositeItem of compositeItems) {594const viewContainer = this.getViewContainer(compositeItem.id);595if (viewContainer) {596const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);597const views: { when: string | undefined }[] = [];598for (const { when } of viewContainerModel.allViewDescriptors) {599views.push({ when: when ? when.serialize() : undefined });600}601state.push({602id: compositeItem.id,603name: viewContainerModel.title,604icon: URI.isUri(viewContainerModel.icon) && this.environmentService.remoteAuthority ? undefined : viewContainerModel.icon, // Do not cache uri icons with remote connection605views,606pinned: compositeItem.pinned,607order: compositeItem.order,608visible: compositeItem.visible,609isBuiltin: !viewContainer.extensionId610});611} else {612state.push({ id: compositeItem.id, name: compositeItem.name, pinned: compositeItem.pinned, order: compositeItem.order, visible: false, isBuiltin: false });613}614}615616this.storeCachedViewContainersState(state);617}618619private _cachedViewContainers: ICachedViewContainer[] | undefined = undefined;620private get cachedViewContainers(): ICachedViewContainer[] {621if (this._cachedViewContainers === undefined) {622this._cachedViewContainers = this.getPinnedViewContainers();623for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {624const cachedViewContainer = this._cachedViewContainers.find(cached => cached.id === placeholderViewContainer.id);625if (cachedViewContainer) {626cachedViewContainer.visible = placeholderViewContainer.visible ?? cachedViewContainer.visible;627cachedViewContainer.name = placeholderViewContainer.name;628cachedViewContainer.icon = placeholderViewContainer.themeIcon ? placeholderViewContainer.themeIcon :629placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;630if (URI.isUri(cachedViewContainer.icon) && this.environmentService.remoteAuthority) {631cachedViewContainer.icon = undefined; // Do not cache uri icons with remote connection632}633cachedViewContainer.views = placeholderViewContainer.views;634cachedViewContainer.isBuiltin = placeholderViewContainer.isBuiltin;635}636}637for (const viewContainerWorkspaceState of this.getViewContainersWorkspaceState()) {638const cachedViewContainer = this._cachedViewContainers.find(cached => cached.id === viewContainerWorkspaceState.id);639if (cachedViewContainer) {640cachedViewContainer.visible = viewContainerWorkspaceState.visible ?? cachedViewContainer.visible;641}642}643}644645return this._cachedViewContainers;646}647648private storeCachedViewContainersState(cachedViewContainers: ICachedViewContainer[]): void {649const pinnedViewContainers = this.getPinnedViewContainers();650this.setPinnedViewContainers(cachedViewContainers.map(({ id, pinned, order }) => ({651id,652pinned,653visible: Boolean(pinnedViewContainers.find(({ id: pinnedId }) => pinnedId === id)?.visible),654order655} satisfies IPinnedViewContainer)));656657this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views, isBuiltin }) => ({658id,659iconUrl: URI.isUri(icon) ? icon : undefined,660themeIcon: ThemeIcon.isThemeIcon(icon) ? icon : undefined,661name,662isBuiltin,663views664} satisfies IPlaceholderViewContainer)));665666this.setViewContainersWorkspaceState(cachedViewContainers.map(({ id, visible }) => ({667id,668visible,669} satisfies IViewContainerWorkspaceState)));670}671672private getPinnedViewContainers(): IPinnedViewContainer[] {673return JSON.parse(this.pinnedViewContainersValue);674}675676private setPinnedViewContainers(pinnedViewContainers: IPinnedViewContainer[]): void {677this.pinnedViewContainersValue = JSON.stringify(pinnedViewContainers);678}679680private _pinnedViewContainersValue: string | undefined;681private get pinnedViewContainersValue(): string {682if (!this._pinnedViewContainersValue) {683this._pinnedViewContainersValue = this.getStoredPinnedViewContainersValue();684}685686return this._pinnedViewContainersValue;687}688689private set pinnedViewContainersValue(pinnedViewContainersValue: string) {690if (this.pinnedViewContainersValue !== pinnedViewContainersValue) {691this._pinnedViewContainersValue = pinnedViewContainersValue;692this.setStoredPinnedViewContainersValue(pinnedViewContainersValue);693}694}695696private getStoredPinnedViewContainersValue(): string {697return this.storageService.get(this.options.pinnedViewContainersKey, StorageScope.PROFILE, '[]');698}699700private setStoredPinnedViewContainersValue(value: string): void {701this.storageService.store(this.options.pinnedViewContainersKey, value, StorageScope.PROFILE, StorageTarget.USER);702}703704private getPlaceholderViewContainers(): IPlaceholderViewContainer[] {705return JSON.parse(this.placeholderViewContainersValue);706}707708private setPlaceholderViewContainers(placeholderViewContainers: IPlaceholderViewContainer[]): void {709this.placeholderViewContainersValue = JSON.stringify(placeholderViewContainers);710}711712private _placeholderViewContainersValue: string | undefined;713private get placeholderViewContainersValue(): string {714if (!this._placeholderViewContainersValue) {715this._placeholderViewContainersValue = this.getStoredPlaceholderViewContainersValue();716}717718return this._placeholderViewContainersValue;719}720721private set placeholderViewContainersValue(placeholderViewContainesValue: string) {722if (this.placeholderViewContainersValue !== placeholderViewContainesValue) {723this._placeholderViewContainersValue = placeholderViewContainesValue;724this.setStoredPlaceholderViewContainersValue(placeholderViewContainesValue);725}726}727728private getStoredPlaceholderViewContainersValue(): string {729return this.storageService.get(this.options.placeholderViewContainersKey, StorageScope.PROFILE, '[]');730}731732private setStoredPlaceholderViewContainersValue(value: string): void {733this.storageService.store(this.options.placeholderViewContainersKey, value, StorageScope.PROFILE, StorageTarget.MACHINE);734}735736private getViewContainersWorkspaceState(): IViewContainerWorkspaceState[] {737return JSON.parse(this.viewContainersWorkspaceStateValue);738}739740private setViewContainersWorkspaceState(viewContainersWorkspaceState: IViewContainerWorkspaceState[]): void {741this.viewContainersWorkspaceStateValue = JSON.stringify(viewContainersWorkspaceState);742}743744private _viewContainersWorkspaceStateValue: string | undefined;745private get viewContainersWorkspaceStateValue(): string {746if (!this._viewContainersWorkspaceStateValue) {747this._viewContainersWorkspaceStateValue = this.getStoredViewContainersWorkspaceStateValue();748}749750return this._viewContainersWorkspaceStateValue;751}752753private set viewContainersWorkspaceStateValue(viewContainersWorkspaceStateValue: string) {754if (this.viewContainersWorkspaceStateValue !== viewContainersWorkspaceStateValue) {755this._viewContainersWorkspaceStateValue = viewContainersWorkspaceStateValue;756this.setStoredViewContainersWorkspaceStateValue(viewContainersWorkspaceStateValue);757}758}759760private getStoredViewContainersWorkspaceStateValue(): string {761return this.storageService.get(this.options.viewContainersWorkspaceStateKey, StorageScope.WORKSPACE, '[]');762}763764private setStoredViewContainersWorkspaceStateValue(value: string): void {765this.storageService.store(this.options.viewContainersWorkspaceStateKey, value, StorageScope.WORKSPACE, StorageTarget.MACHINE);766}767}768769class ViewContainerActivityAction extends CompositeBarAction {770771private static readonly preventDoubleClickDelay = 300;772773private lastRun = 0;774775constructor(776compositeBarActionItem: ICompositeBarActionItem,777private readonly part: Parts,778private readonly paneCompositePart: IPaneCompositePart,779@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,780@IConfigurationService private readonly configurationService: IConfigurationService,781@IActivityService private readonly activityService: IActivityService,782) {783super(compositeBarActionItem);784this.updateActivity();785this._register(this.activityService.onDidChangeActivity(viewContainerOrAction => {786if (!isString(viewContainerOrAction) && viewContainerOrAction.id === this.compositeBarActionItem.id) {787this.updateActivity();788}789}));790}791792updateCompositeBarActionItem(compositeBarActionItem: ICompositeBarActionItem): void {793this.compositeBarActionItem = compositeBarActionItem;794}795796private updateActivity(): void {797this.activities = this.activityService.getViewContainerActivities(this.compositeBarActionItem.id);798}799800override async run(event: { preserveFocus: boolean }): Promise<void> {801if (isMouseEvent(event) && event.button === 2) {802return; // do not run on right click803}804805// prevent accident trigger on a doubleclick (to help nervous people)806const now = Date.now();807if (now > this.lastRun /* https://github.com/microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewContainerActivityAction.preventDoubleClickDelay) {808return;809}810this.lastRun = now;811812const focus = (event && 'preserveFocus' in event) ? !event.preserveFocus : true;813814if (this.part === Parts.ACTIVITYBAR_PART) {815const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART);816const activeViewlet = this.paneCompositePart.getActivePaneComposite();817const focusBehavior = this.configurationService.getValue<string>('workbench.activityBar.iconClickBehavior');818819if (sideBarVisible && activeViewlet?.getId() === this.compositeBarActionItem.id) {820switch (focusBehavior) {821case 'focus':822this.paneCompositePart.openPaneComposite(this.compositeBarActionItem.id, focus);823break;824case 'toggle':825default:826// Hide sidebar if selected viewlet already visible827this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART);828break;829}830831return;832}833}834835await this.paneCompositePart.openPaneComposite(this.compositeBarActionItem.id, focus);836return this.activate();837}838}839840class PlaceHolderViewContainerActivityAction extends ViewContainerActivityAction { }841842class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction {843844constructor(id: string, compositeBar: ICompositeBar) {845super({ id, name: id, classNames: undefined }, compositeBar);846}847848setActivity(activity: ICompositeBarActionItem): void {849this.label = activity.name;850}851}852853class PlaceHolderToggleCompositeBadgeAction extends ToggleCompositeBadgeAction {854855constructor(id: string, compositeBar: ICompositeBar) {856super({ id, name: id, classNames: undefined }, compositeBar);857}858859setCompositeBarActionItem(actionItem: ICompositeBarActionItem): void {860this.label = actionItem.name;861}862}863864865