Path: blob/main/src/vs/workbench/browser/parts/paneCompositeBar.ts
5303 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>());9091private readonly compositeBar: CompositeBar;92readonly dndHandler: ICompositeDragAndDrop;93private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction; pinnedAction: ToggleCompositePinnedAction; badgeAction: ToggleCompositeBadgeAction }>();9495private hasExtensionsRegistered: boolean = false;9697constructor(98private readonly location: ViewContainerLocation,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.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, this.location, this.options.orientation,114async (id: string, focus?: boolean) => { return await this.paneCompositePart.openPaneComposite(id, focus) ?? null; },115(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, this.options.orientation === ActionsOrientation.VERTICAL ? before?.verticallyBefore : before?.horizontallyBefore),116() => this.compositeBar.getCompositeBarItems(),117);118119const cachedItems = this.cachedViewContainers120.map(container => ({121id: container.id,122name: container.name,123visible: !this.shouldBeHidden(container.id, container),124order: container.order,125pinned: container.pinned,126}));127this.compositeBar = this.createCompositeBar(cachedItems);128this.onDidRegisterViewContainers(this.getViewContainers());129this.registerListeners();130}131132private createCompositeBar(cachedItems: ICompositeBarItem[]) {133return this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, {134icon: this.options.icon,135compact: this.options.compact,136orientation: this.options.orientation,137activityHoverOptions: this.options.activityHoverOptions,138preventLoopNavigation: this.options.preventLoopNavigation,139openComposite: async (compositeId, preserveFocus) => {140return (await this.paneCompositePart.openPaneComposite(compositeId, !preserveFocus)) ?? null;141},142getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,143getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction,144getCompositeBadgeAction: compositeId => this.getCompositeActions(compositeId).badgeAction,145getOnCompositeClickAction: compositeId => this.getCompositeActions(compositeId).activityAction,146fillExtraContextMenuActions: (actions, e) => this.options.fillExtraContextMenuActions(actions, e),147getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId),148getDefaultCompositeId: () => this.viewDescriptorService.getDefaultViewContainer(this.location)?.id,149dndHandler: this.dndHandler,150compositeSize: this.options.compositeSize,151overflowActionSize: this.options.overflowActionSize,152colors: theme => this.options.colors(theme),153}));154}155156private getContextMenuActionsForComposite(compositeId: string): IAction[] {157const actions: IAction[] = [new Separator()];158159const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!;160const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!;161const currentLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer);162163// Move View Container164const moveActions = [];165for (const location of [ViewContainerLocation.Sidebar, ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel]) {166if (currentLocation !== location) {167moveActions.push(this.createMoveAction(viewContainer, location, defaultLocation));168}169}170171actions.push(new SubmenuAction('moveToMenu', localize('moveToMenu', "Move To"), moveActions));172173// Reset Location174if (defaultLocation !== currentLocation) {175actions.push(toAction({176id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => {177this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation, undefined, 'resetLocationAction');178this.viewService.openViewContainer(viewContainer.id, true);179}180}));181} else {182const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);183if (viewContainerModel.allViewDescriptors.length === 1) {184const viewToReset = viewContainerModel.allViewDescriptors[0];185const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!;186if (defaultContainer !== viewContainer) {187actions.push(toAction({188id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => {189this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer, undefined, 'resetLocationAction');190this.viewService.openViewContainer(viewContainer.id, true);191}192}));193}194}195}196197return actions;198}199200private createMoveAction(viewContainer: ViewContainer, newLocation: ViewContainerLocation, defaultLocation: ViewContainerLocation): IAction {201return toAction({202id: `moveViewContainerTo${newLocation}`,203label: newLocation === ViewContainerLocation.Panel ? localize('panel', "Panel") : newLocation === ViewContainerLocation.Sidebar ? localize('sidebar', "Primary Side Bar") : localize('auxiliarybar', "Secondary Side Bar"),204run: () => {205let index: number | undefined;206if (newLocation !== defaultLocation) {207index = this.viewDescriptorService.getViewContainersByLocation(newLocation).length; // move to the end of the location208} else {209index = undefined; // restore default location210}211this.viewDescriptorService.moveViewContainerToLocation(viewContainer, newLocation, index);212this.viewService.openViewContainer(viewContainer.id, true);213}214});215}216217private registerListeners(): void {218219// View Container Changes220this._register(this.viewDescriptorService.onDidChangeViewContainers(({ added, removed }) => this.onDidChangeViewContainers(added, removed)));221this._register(this.viewDescriptorService.onDidChangeContainerLocation(({ viewContainer, from, to }) => this.onDidChangeViewContainerLocation(viewContainer, from, to)));222223// View Container Visibility Changes224this._register(this.paneCompositePart.onDidPaneCompositeOpen(e => this.onDidChangeViewContainerVisibility(e.getId(), true)));225this._register(this.paneCompositePart.onDidPaneCompositeClose(e => this.onDidChangeViewContainerVisibility(e.getId(), false)));226227// Extension registration228this.extensionService.whenInstalledExtensionsRegistered().then(() => {229if (this._store.isDisposed) {230return;231}232this.onDidRegisterExtensions();233this._register(this.compositeBar.onDidChange(() => {234this.updateCompositeBarItemsFromStorage(true);235this.saveCachedViewContainers();236}));237this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, this.options.pinnedViewContainersKey, this._store)(() => this.updateCompositeBarItemsFromStorage(false)));238});239}240241private onDidChangeViewContainers(added: readonly { container: ViewContainer; location: ViewContainerLocation }[], removed: readonly { container: ViewContainer; location: ViewContainerLocation }[]) {242removed.filter(({ location }) => location === this.location).forEach(({ container }) => this.onDidDeregisterViewContainer(container));243this.onDidRegisterViewContainers(added.filter(({ location }) => location === this.location).map(({ container }) => container));244}245246private onDidChangeViewContainerLocation(container: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation) {247if (from === this.location) {248this.onDidDeregisterViewContainer(container);249}250251if (to === this.location) {252this.onDidRegisterViewContainers([container]);253}254}255256private onDidChangeViewContainerVisibility(id: string, visible: boolean) {257if (visible) {258// Activate view container action on opening of a view container259this.onDidViewContainerVisible(id);260} else {261// Deactivate view container action on close262this.compositeBar.deactivateComposite(id);263}264}265266private onDidRegisterExtensions(): void {267this.hasExtensionsRegistered = true;268269// show/hide/remove composites270for (const { id } of this.cachedViewContainers) {271const viewContainer = this.getViewContainer(id);272if (viewContainer) {273this.showOrHideViewContainer(viewContainer);274} else {275if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) {276this.removeComposite(id);277} else {278this.hideComposite(id);279}280}281}282283this.saveCachedViewContainers();284}285286private onDidViewContainerVisible(id: string): void {287const viewContainer = this.getViewContainer(id);288if (viewContainer) {289290// Update the composite bar by adding291this.addComposite(viewContainer);292this.compositeBar.activateComposite(viewContainer.id);293294if (this.shouldBeHidden(viewContainer)) {295const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);296if (viewContainerModel.activeViewDescriptors.length === 0) {297// Update the composite bar by hiding298this.hideComposite(viewContainer.id);299}300}301}302}303304create(parent: HTMLElement): HTMLElement {305return this.compositeBar.create(parent);306}307308private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction; pinnedAction: ToggleCompositePinnedAction; badgeAction: ToggleCompositeBadgeAction } {309let compositeActions = this.compositeActions.get(compositeId);310if (!compositeActions) {311const viewContainer = this.getViewContainer(compositeId);312if (viewContainer) {313const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);314compositeActions = {315activityAction: this._register(this.instantiationService.createInstance(ViewContainerActivityAction, this.toCompositeBarActionItemFrom(viewContainerModel), this.part, this.paneCompositePart)),316pinnedAction: this._register(new ToggleCompositePinnedAction(this.toCompositeBarActionItemFrom(viewContainerModel), this.compositeBar)),317badgeAction: this._register(new ToggleCompositeBadgeAction(this.toCompositeBarActionItemFrom(viewContainerModel), this.compositeBar))318};319} else {320const cachedComposite = this.cachedViewContainers.filter(c => c.id === compositeId)[0];321compositeActions = {322activityAction: this._register(this.instantiationService.createInstance(PlaceHolderViewContainerActivityAction, this.toCompositeBarActionItem(compositeId, cachedComposite?.name ?? compositeId, cachedComposite?.icon, undefined), this.part, this.paneCompositePart)),323pinnedAction: this._register(new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar)),324badgeAction: this._register(new PlaceHolderToggleCompositeBadgeAction(compositeId, this.compositeBar))325};326}327328this.compositeActions.set(compositeId, compositeActions);329}330331return compositeActions;332}333334private onDidRegisterViewContainers(viewContainers: readonly ViewContainer[]): void {335for (const viewContainer of viewContainers) {336this.addComposite(viewContainer);337338// Pin it by default if it is new339const cachedViewContainer = this.cachedViewContainers.filter(({ id }) => id === viewContainer.id)[0];340if (!cachedViewContainer) {341this.compositeBar.pin(viewContainer.id);342}343344// Active345const visibleViewContainer = this.paneCompositePart.getActivePaneComposite();346if (visibleViewContainer?.getId() === viewContainer.id) {347this.compositeBar.activateComposite(viewContainer.id);348}349350const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);351this.updateCompositeBarActionItem(viewContainer, viewContainerModel);352this.showOrHideViewContainer(viewContainer);353354const disposables = new DisposableStore();355disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateCompositeBarActionItem(viewContainer, viewContainerModel)));356disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.showOrHideViewContainer(viewContainer)));357358this.viewContainerDisposables.set(viewContainer.id, disposables);359}360}361362private onDidDeregisterViewContainer(viewContainer: ViewContainer): void {363this.viewContainerDisposables.deleteAndDispose(viewContainer.id);364this.removeComposite(viewContainer.id);365}366367private updateCompositeBarActionItem(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {368const compositeBarActionItem = this.toCompositeBarActionItemFrom(viewContainerModel);369const { activityAction, pinnedAction } = this.getCompositeActions(viewContainer.id);370activityAction.updateCompositeBarActionItem(compositeBarActionItem);371372if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) {373pinnedAction.setActivity(compositeBarActionItem);374}375376if (this.options.recomputeSizes) {377this.compositeBar.recomputeSizes();378}379380this.saveCachedViewContainers();381}382383private toCompositeBarActionItemFrom(viewContainerModel: IViewContainerModel): ICompositeBarActionItem {384return this.toCompositeBarActionItem(viewContainerModel.viewContainer.id, viewContainerModel.title, viewContainerModel.icon, viewContainerModel.keybindingId);385}386387private toCompositeBarActionItem(id: string, name: string, icon: URI | ThemeIcon | undefined, keybindingId: string | undefined): ICompositeBarActionItem {388let classNames: string[] | undefined = undefined;389let iconUrl: URI | undefined = undefined;390if (this.options.icon) {391if (URI.isUri(icon)) {392iconUrl = icon;393const cssUrl = asCSSUrl(icon);394const hash = new StringSHA1();395hash.update(cssUrl);396const iconId = `activity-${id.replace(/\./g, '-')}-${hash.digest()}`;397const iconClass = `.monaco-workbench .${this.options.partContainerClass} .monaco-action-bar .action-label.${iconId}`;398classNames = [iconId, 'uri-icon'];399createCSSRule(iconClass, `400mask: ${cssUrl} no-repeat 50% 50%;401mask-size: var(--activity-bar-icon-size, ${this.options.iconSize}px);402-webkit-mask: ${cssUrl} no-repeat 50% 50%;403-webkit-mask-size: var(--activity-bar-icon-size, ${this.options.iconSize}px);404mask-origin: padding;405-webkit-mask-origin: padding;406`);407} else if (ThemeIcon.isThemeIcon(icon)) {408classNames = ThemeIcon.asClassNameArray(icon);409}410}411412return { id, name, classNames, iconUrl, keybindingId };413}414415private showOrHideViewContainer(viewContainer: ViewContainer): void {416if (this.shouldBeHidden(viewContainer)) {417this.hideComposite(viewContainer.id);418} else {419this.addComposite(viewContainer);420421// Activate if this is the active pane composite422const activePaneComposite = this.paneCompositePart.getActivePaneComposite();423if (activePaneComposite?.getId() === viewContainer.id) {424this.compositeBar.activateComposite(viewContainer.id);425}426}427}428429private shouldBeHidden(viewContainerOrId: string | ViewContainer, cachedViewContainer?: ICachedViewContainer): boolean {430const viewContainer = isString(viewContainerOrId) ? this.getViewContainer(viewContainerOrId) : viewContainerOrId;431const viewContainerId = isString(viewContainerOrId) ? viewContainerOrId : viewContainerOrId.id;432433if (viewContainer) {434if (viewContainer.hideIfEmpty) {435if (this.viewService.isViewContainerActive(viewContainerId)) {436return false;437}438} else {439return false;440}441}442443// Check cache only if extensions are not yet registered and current window is not native (desktop) remote connection window444if (!this.hasExtensionsRegistered && !(this.part === Parts.SIDEBAR_PART && this.environmentService.remoteAuthority && isNative)) {445cachedViewContainer = cachedViewContainer || this.cachedViewContainers.find(({ id }) => id === viewContainerId);446447// Show builtin ViewContainer if not registered yet448if (!viewContainer && cachedViewContainer?.isBuiltin && cachedViewContainer?.visible) {449return false;450}451452if (cachedViewContainer?.views?.length) {453return cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)));454}455}456457return true;458}459460private addComposite(viewContainer: ViewContainer): void {461this.compositeBar.addComposite({ id: viewContainer.id, name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value, order: viewContainer.order, requestedIndex: viewContainer.requestedIndex });462}463464private hideComposite(compositeId: string): void {465this.compositeBar.hideComposite(compositeId);466467const compositeActions = this.compositeActions.get(compositeId);468if (compositeActions) {469compositeActions.activityAction.dispose();470compositeActions.pinnedAction.dispose();471this.compositeActions.delete(compositeId);472}473}474475private removeComposite(compositeId: string): void {476this.compositeBar.removeComposite(compositeId);477478const compositeActions = this.compositeActions.get(compositeId);479if (compositeActions) {480compositeActions.activityAction.dispose();481compositeActions.pinnedAction.dispose();482this.compositeActions.delete(compositeId);483}484}485486getPinnedPaneCompositeIds(): string[] {487const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id);488return this.getViewContainers()489.filter(v => this.compositeBar.isPinned(v.id))490.sort((v1, v2) => pinnedCompositeIds.indexOf(v1.id) - pinnedCompositeIds.indexOf(v2.id))491.map(v => v.id);492}493494getVisiblePaneCompositeIds(): string[] {495return this.compositeBar.getVisibleComposites()496.filter(v => this.paneCompositePart.getActivePaneComposite()?.getId() === v.id || this.compositeBar.isPinned(v.id))497.map(v => v.id);498}499500getPaneCompositeIds(): string[] {501return this.compositeBar.getVisibleComposites()502.map(v => v.id);503}504505getContextMenuActions(): IAction[] {506return this.compositeBar.getContextMenuActions();507}508509focus(index?: number): void {510this.compositeBar.focus(index);511}512513layout(width: number, height: number): void {514this.compositeBar.layout(new Dimension(width, height));515}516517private getViewContainer(id: string): ViewContainer | undefined {518const viewContainer = this.viewDescriptorService.getViewContainerById(id);519return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined;520}521522private getViewContainers(): readonly ViewContainer[] {523return this.viewDescriptorService.getViewContainersByLocation(this.location);524}525526private updateCompositeBarItemsFromStorage(retainExisting: boolean): void {527if (this.pinnedViewContainersValue === this.getStoredPinnedViewContainersValue()) {528return;529}530531this._placeholderViewContainersValue = undefined;532this._pinnedViewContainersValue = undefined;533this._cachedViewContainers = undefined;534535const newCompositeItems: ICompositeBarItem[] = [];536const compositeItems = this.compositeBar.getCompositeBarItems();537538for (const cachedViewContainer of this.cachedViewContainers) {539newCompositeItems.push({540id: cachedViewContainer.id,541name: cachedViewContainer.name,542order: cachedViewContainer.order,543pinned: cachedViewContainer.pinned,544visible: cachedViewContainer.visible && !!this.getViewContainer(cachedViewContainer.id),545});546}547548for (const viewContainer of this.getViewContainers()) {549// Add missing view containers550if (!newCompositeItems.some(({ id }) => id === viewContainer.id)) {551const index = compositeItems.findIndex(({ id }) => id === viewContainer.id);552if (index !== -1) {553const compositeItem = compositeItems[index];554newCompositeItems.splice(index, 0, {555id: viewContainer.id,556name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value,557order: compositeItem.order,558pinned: compositeItem.pinned,559visible: compositeItem.visible,560});561} else {562newCompositeItems.push({563id: viewContainer.id,564name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value,565order: viewContainer.order,566pinned: true,567visible: !this.shouldBeHidden(viewContainer),568});569}570}571}572573if (retainExisting) {574for (const compositeItem of compositeItems) {575const newCompositeItem = newCompositeItems.find(({ id }) => id === compositeItem.id);576if (!newCompositeItem) {577newCompositeItems.push(compositeItem);578}579}580}581582this.compositeBar.setCompositeBarItems(newCompositeItems);583}584585private saveCachedViewContainers(): void {586const state: ICachedViewContainer[] = [];587588const compositeItems = this.compositeBar.getCompositeBarItems();589for (const compositeItem of compositeItems) {590const viewContainer = this.getViewContainer(compositeItem.id);591if (viewContainer) {592const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);593const views: { when: string | undefined }[] = [];594for (const { when } of viewContainerModel.allViewDescriptors) {595views.push({ when: when ? when.serialize() : undefined });596}597state.push({598id: compositeItem.id,599name: viewContainerModel.title,600icon: URI.isUri(viewContainerModel.icon) && this.environmentService.remoteAuthority ? undefined : viewContainerModel.icon, // Do not cache uri icons with remote connection601views,602pinned: compositeItem.pinned,603order: compositeItem.order,604visible: compositeItem.visible,605isBuiltin: !viewContainer.extensionId606});607} else {608state.push({ id: compositeItem.id, name: compositeItem.name, pinned: compositeItem.pinned, order: compositeItem.order, visible: false, isBuiltin: false });609}610}611612this.storeCachedViewContainersState(state);613}614615private _cachedViewContainers: ICachedViewContainer[] | undefined = undefined;616private get cachedViewContainers(): ICachedViewContainer[] {617if (this._cachedViewContainers === undefined) {618this._cachedViewContainers = this.getPinnedViewContainers();619for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {620const cachedViewContainer = this._cachedViewContainers.find(cached => cached.id === placeholderViewContainer.id);621if (cachedViewContainer) {622cachedViewContainer.visible = placeholderViewContainer.visible ?? cachedViewContainer.visible;623cachedViewContainer.name = placeholderViewContainer.name;624cachedViewContainer.icon = placeholderViewContainer.themeIcon ? placeholderViewContainer.themeIcon :625placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;626if (URI.isUri(cachedViewContainer.icon) && this.environmentService.remoteAuthority) {627cachedViewContainer.icon = undefined; // Do not cache uri icons with remote connection628}629cachedViewContainer.views = placeholderViewContainer.views;630cachedViewContainer.isBuiltin = placeholderViewContainer.isBuiltin;631}632}633for (const viewContainerWorkspaceState of this.getViewContainersWorkspaceState()) {634const cachedViewContainer = this._cachedViewContainers.find(cached => cached.id === viewContainerWorkspaceState.id);635if (cachedViewContainer) {636cachedViewContainer.visible = viewContainerWorkspaceState.visible ?? cachedViewContainer.visible;637}638}639}640641return this._cachedViewContainers;642}643644private storeCachedViewContainersState(cachedViewContainers: ICachedViewContainer[]): void {645const pinnedViewContainers = this.getPinnedViewContainers();646this.setPinnedViewContainers(cachedViewContainers.map(({ id, pinned, order }) => ({647id,648pinned,649visible: Boolean(pinnedViewContainers.find(({ id: pinnedId }) => pinnedId === id)?.visible),650order651} satisfies IPinnedViewContainer)));652653this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views, isBuiltin }) => ({654id,655iconUrl: URI.isUri(icon) ? icon : undefined,656themeIcon: ThemeIcon.isThemeIcon(icon) ? icon : undefined,657name,658isBuiltin,659views660} satisfies IPlaceholderViewContainer)));661662this.setViewContainersWorkspaceState(cachedViewContainers.map(({ id, visible }) => ({663id,664visible,665} satisfies IViewContainerWorkspaceState)));666}667668private getPinnedViewContainers(): IPinnedViewContainer[] {669return JSON.parse(this.pinnedViewContainersValue);670}671672private setPinnedViewContainers(pinnedViewContainers: IPinnedViewContainer[]): void {673this.pinnedViewContainersValue = JSON.stringify(pinnedViewContainers);674}675676private _pinnedViewContainersValue: string | undefined;677private get pinnedViewContainersValue(): string {678if (!this._pinnedViewContainersValue) {679this._pinnedViewContainersValue = this.getStoredPinnedViewContainersValue();680}681682return this._pinnedViewContainersValue;683}684685private set pinnedViewContainersValue(pinnedViewContainersValue: string) {686if (this.pinnedViewContainersValue !== pinnedViewContainersValue) {687this._pinnedViewContainersValue = pinnedViewContainersValue;688this.setStoredPinnedViewContainersValue(pinnedViewContainersValue);689}690}691692private getStoredPinnedViewContainersValue(): string {693return this.storageService.get(this.options.pinnedViewContainersKey, StorageScope.PROFILE, '[]');694}695696private setStoredPinnedViewContainersValue(value: string): void {697this.storageService.store(this.options.pinnedViewContainersKey, value, StorageScope.PROFILE, StorageTarget.USER);698}699700private getPlaceholderViewContainers(): IPlaceholderViewContainer[] {701return JSON.parse(this.placeholderViewContainersValue);702}703704private setPlaceholderViewContainers(placeholderViewContainers: IPlaceholderViewContainer[]): void {705this.placeholderViewContainersValue = JSON.stringify(placeholderViewContainers);706}707708private _placeholderViewContainersValue: string | undefined;709private get placeholderViewContainersValue(): string {710if (!this._placeholderViewContainersValue) {711this._placeholderViewContainersValue = this.getStoredPlaceholderViewContainersValue();712}713714return this._placeholderViewContainersValue;715}716717private set placeholderViewContainersValue(placeholderViewContainesValue: string) {718if (this.placeholderViewContainersValue !== placeholderViewContainesValue) {719this._placeholderViewContainersValue = placeholderViewContainesValue;720this.setStoredPlaceholderViewContainersValue(placeholderViewContainesValue);721}722}723724private getStoredPlaceholderViewContainersValue(): string {725return this.storageService.get(this.options.placeholderViewContainersKey, StorageScope.PROFILE, '[]');726}727728private setStoredPlaceholderViewContainersValue(value: string): void {729this.storageService.store(this.options.placeholderViewContainersKey, value, StorageScope.PROFILE, StorageTarget.MACHINE);730}731732private getViewContainersWorkspaceState(): IViewContainerWorkspaceState[] {733return JSON.parse(this.viewContainersWorkspaceStateValue);734}735736private setViewContainersWorkspaceState(viewContainersWorkspaceState: IViewContainerWorkspaceState[]): void {737this.viewContainersWorkspaceStateValue = JSON.stringify(viewContainersWorkspaceState);738}739740private _viewContainersWorkspaceStateValue: string | undefined;741private get viewContainersWorkspaceStateValue(): string {742if (!this._viewContainersWorkspaceStateValue) {743this._viewContainersWorkspaceStateValue = this.getStoredViewContainersWorkspaceStateValue();744}745746return this._viewContainersWorkspaceStateValue;747}748749private set viewContainersWorkspaceStateValue(viewContainersWorkspaceStateValue: string) {750if (this.viewContainersWorkspaceStateValue !== viewContainersWorkspaceStateValue) {751this._viewContainersWorkspaceStateValue = viewContainersWorkspaceStateValue;752this.setStoredViewContainersWorkspaceStateValue(viewContainersWorkspaceStateValue);753}754}755756private getStoredViewContainersWorkspaceStateValue(): string {757return this.storageService.get(this.options.viewContainersWorkspaceStateKey, StorageScope.WORKSPACE, '[]');758}759760private setStoredViewContainersWorkspaceStateValue(value: string): void {761this.storageService.store(this.options.viewContainersWorkspaceStateKey, value, StorageScope.WORKSPACE, StorageTarget.MACHINE);762}763}764765class ViewContainerActivityAction extends CompositeBarAction {766767private static readonly preventDoubleClickDelay = 300;768769private lastRun = 0;770771constructor(772compositeBarActionItem: ICompositeBarActionItem,773private readonly part: Parts,774private readonly paneCompositePart: IPaneCompositePart,775@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,776@IConfigurationService private readonly configurationService: IConfigurationService,777@IActivityService private readonly activityService: IActivityService,778) {779super(compositeBarActionItem);780this.updateActivity();781this._register(this.activityService.onDidChangeActivity(viewContainerOrAction => {782if (!isString(viewContainerOrAction) && viewContainerOrAction.id === this.compositeBarActionItem.id) {783this.updateActivity();784}785}));786}787788updateCompositeBarActionItem(compositeBarActionItem: ICompositeBarActionItem): void {789this.compositeBarActionItem = compositeBarActionItem;790}791792private updateActivity(): void {793this.activities = this.activityService.getViewContainerActivities(this.compositeBarActionItem.id);794}795796override async run(event: { preserveFocus: boolean }): Promise<void> {797if (isMouseEvent(event) && event.button === 2) {798return; // do not run on right click799}800801// prevent accident trigger on a doubleclick (to help nervous people)802const now = Date.now();803if (now > this.lastRun /* https://github.com/microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewContainerActivityAction.preventDoubleClickDelay) {804return;805}806this.lastRun = now;807808const focus = (event && 'preserveFocus' in event) ? !event.preserveFocus : true;809810if (this.part === Parts.ACTIVITYBAR_PART) {811const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART);812const activeViewlet = this.paneCompositePart.getActivePaneComposite();813const focusBehavior = this.configurationService.getValue<string>('workbench.activityBar.iconClickBehavior');814815if (sideBarVisible && activeViewlet?.getId() === this.compositeBarActionItem.id) {816switch (focusBehavior) {817case 'focus':818this.paneCompositePart.openPaneComposite(this.compositeBarActionItem.id, focus);819break;820case 'toggle':821default:822// Hide sidebar if selected viewlet already visible823this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART);824break;825}826827return;828}829}830831await this.paneCompositePart.openPaneComposite(this.compositeBarActionItem.id, focus);832return this.activate();833}834}835836class PlaceHolderViewContainerActivityAction extends ViewContainerActivityAction { }837838class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction {839840constructor(id: string, compositeBar: ICompositeBar) {841super({ id, name: id, classNames: undefined }, compositeBar);842}843844setActivity(activity: ICompositeBarActionItem): void {845this.label = activity.name;846}847}848849class PlaceHolderToggleCompositeBadgeAction extends ToggleCompositeBadgeAction {850851constructor(id: string, compositeBar: ICompositeBar) {852super({ id, name: id, classNames: undefined }, compositeBar);853}854855setCompositeBarActionItem(actionItem: ICompositeBarActionItem): void {856this.label = actionItem.name;857}858}859860861