From 116a3de416d91863f531c54593b276ff26955059 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 20 Apr 2020 20:26:56 +0200 Subject: [PATCH] Unify into single ViewContainerModel --- .../browser/actions/layoutActions.ts | 6 +- .../parts/activitybar/activitybarPart.ts | 31 +- .../workbench/browser/parts/compositeBar.ts | 4 +- .../browser/parts/panel/panelPart.ts | 39 +- .../browser/parts/views/viewPaneContainer.ts | 54 +- src/vs/workbench/browser/parts/views/views.ts | 10 +- .../browser/parts/views/viewsViewlet.ts | 16 +- src/vs/workbench/common/views.ts | 27 +- .../quickaccess/browser/viewQuickAccess.ts | 6 +- .../contrib/scm/browser/scmViewlet.ts | 20 +- .../terminal/browser/terminalService.ts | 2 +- .../views/browser/viewDescriptorService.ts | 706 ++++++++++++++++-- .../services/views/common/viewsModel.ts | 648 ---------------- .../browser/viewDescriptorService.test.ts | 27 +- .../views/test/browser/viewsModel.test.ts | 354 ++++----- 15 files changed, 945 insertions(+), 1005 deletions(-) delete mode 100644 src/vs/workbench/services/views/common/viewsModel.ts diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 5ad55ee2b3a..043ff9b3a97 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -488,9 +488,9 @@ export class ResetViewLocationsAction extends Action { async run(): Promise { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); viewContainerRegistry.all.forEach(viewContainer => { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); - viewDescriptors.allViewDescriptors.forEach(viewDescriptor => { + viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { const defaultContainer = this.viewDescriptorService.getDefaultContainer(viewDescriptor.id); const currentContainer = this.viewDescriptorService.getViewContainer(viewDescriptor.id); @@ -583,7 +583,7 @@ export class MoveFocusedViewAction extends Action { const currentContainer = this.viewDescriptorService.getViewContainer(focusedViewId)!; const currentLocation = this.viewDescriptorService.getViewLocation(focusedViewId)!; - const isViewSolo = this.viewDescriptorService.getViewDescriptors(currentContainer).allViewDescriptors.length === 1; + const isViewSolo = this.viewDescriptorService.getViewContainerModel(currentContainer).allViewDescriptors.length === 1; if (!(isViewSolo && currentLocation === ViewContainerLocation.Sidebar)) { items.push({ diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index b55d9c0fca0..11f1592d7b5 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -26,7 +26,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { URI, UriComponents } from 'vs/base/common/uri'; import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; 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 { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, 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, isString } from 'vs/base/common/types'; @@ -218,8 +218,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewletDescriptor) { const viewContainer = this.getViewContainer(viewletDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors.activeViewDescriptors.length === 0) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + if (viewContainerModel.activeViewDescriptors.length === 0) { this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding } } @@ -490,13 +490,12 @@ export class ActivitybarPart extends Part implements IActivityBarService { for (const viewlet of viewlets) { this.enableCompositeActions(viewlet); const viewContainer = this.getViewContainer(viewlet.id)!; - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + this.onDidChangeActiveViews(viewlet, viewContainerModel, viewContainer.hideIfEmpty); const disposables = new DisposableStore(); - disposables.add(viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty))); - disposables.add(viewDescriptors.onDidChangeViews(() => this.onDidUpdateViews(viewlet, viewDescriptors))); - disposables.add(viewDescriptors.onDidMove(() => this.onDidUpdateViews(viewlet, viewDescriptors))); + disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewlet, viewContainerModel, viewContainer.hideIfEmpty))); + disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewlet, viewContainerModel))); this.viewletDisposables.set(viewlet.id, disposables); } @@ -512,12 +511,12 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.hideComposite(viewletId); } - private updateActivity(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { - const icon = viewDescriptors.getIcon(); + private updateActivity(viewlet: ViewletDescriptor, viewContainerModel: IViewContainerModel): void { + const icon = viewContainerModel.icon; const activity: IActivity = { id: viewlet.id, - name: viewDescriptors.getTitle(), + name: viewContainerModel.title, cssClass: isString(icon) ? icon : undefined, iconUrl: icon instanceof URI ? icon : undefined, keybindingId: viewlet.keybindingId @@ -531,11 +530,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - private onDidUpdateViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { - this.updateActivity(viewlet, viewDescriptors); - } - - private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection, hideIfEmpty?: boolean): void { + private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewContainerModel, hideIfEmpty?: boolean): void { if (viewDescriptors.activeViewDescriptors.length) { this.updateActivity(viewlet, viewDescriptors); this.compositeBar.addComposite(viewlet); @@ -663,8 +658,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewlet) { const views: { when: string | undefined }[] = []; if (viewContainer) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - for (const { when } of viewDescriptors.allViewDescriptors) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + for (const { when } of viewContainerModel.allViewDescriptors) { views.push({ when: when ? when.serialize() : undefined }); } } diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 2b67559e904..d25f80c50d7 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -56,7 +56,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } // ... on a different composite bar else { - const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors; + const viewsToMove = this.viewDescriptorService.getViewContainerModel(currentContainer)!.allViewDescriptors; if (viewsToMove.some(v => !v.canMoveView)) { return; } @@ -119,7 +119,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } // ... to another composite location - const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors; + const draggedViews = this.viewDescriptorService.getViewContainerModel(currentContainer)!.allViewDescriptors; // ... all views must be movable return !draggedViews.some(v => !v.canMoveView); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 0f66d61a73b..e9713d8aab2 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -33,7 +33,7 @@ import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/con import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewContainerModel, ViewContainerLocation } from 'vs/workbench/common/views'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; @@ -178,9 +178,9 @@ export class PanelPart extends CompositePart implements IPanelService { const result: IAction[] = []; const container = this.getViewContainer(compositeId); if (container) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(container); - if (viewDescriptors.allViewDescriptors.length === 1) { - const viewMenuActions = this.instantiationService.createInstance(ViewMenuActions, viewDescriptors.allViewDescriptors[0].id, MenuId.ViewTitle, MenuId.ViewTitleContext); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(container); + if (viewContainerModel.allViewDescriptors.length === 1) { + const viewMenuActions = this.instantiationService.createInstance(ViewMenuActions, viewContainerModel.allViewDescriptors[0].id, MenuId.ViewTitle, MenuId.ViewTitleContext); result.push(...viewMenuActions.getContextMenuActions()); viewMenuActions.dispose(); } @@ -211,13 +211,13 @@ export class PanelPart extends CompositePart implements IPanelService { for (const panel of panels) { this.enableCompositeActions(panel); const viewContainer = this.getViewContainer(panel.id)!; - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onDidChangeActiveViews(panel, viewDescriptors, viewContainer.hideIfEmpty); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + this.onDidChangeActiveViews(panel, viewContainerModel, viewContainer.hideIfEmpty); const disposables = new DisposableStore(); - disposables.add(viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors, viewContainer.hideIfEmpty))); - disposables.add(viewDescriptors.onDidChangeViews(() => this.onDidUpdateViews(panel, viewDescriptors))); - disposables.add(viewDescriptors.onDidMove(() => this.onDidUpdateViews(panel, viewDescriptors))); + disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(panel, viewContainerModel, viewContainer.hideIfEmpty))); + disposables.add(viewContainerModel.onDidChangeAllViewDescriptors(() => this.onDidUpdateViews(panel, viewContainerModel))); + disposables.add(viewContainerModel.onDidMoveVisibleViewDescriptors(() => this.onDidUpdateViews(panel, viewContainerModel))); this.panelDisposables.set(panel.id, disposables); } @@ -242,10 +242,10 @@ export class PanelPart extends CompositePart implements IPanelService { } } - private updateActivity(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + private updateActivity(panel: PanelDescriptor, viewContainerModel: IViewContainerModel): void { const activity: IActivity = { id: panel.id, - name: viewDescriptors.getTitle(), + name: viewContainerModel.title, keybindingId: panel.keybindingId }; @@ -257,11 +257,11 @@ export class PanelPart extends CompositePart implements IPanelService { } } - private onDidUpdateViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + private onDidUpdateViews(panel: PanelDescriptor, viewDescriptors: IViewContainerModel): void { this.updateActivity(panel, viewDescriptors); } - private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection, hideIfEmpty?: boolean): void { + private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewContainerModel, hideIfEmpty?: boolean): void { if (viewDescriptors.activeViewDescriptors.length) { this.updateActivity(panel, viewDescriptors); this.compositeBar.addComposite(panel); @@ -343,8 +343,8 @@ export class PanelPart extends CompositePart implements IPanelService { if (panelDescriptor) { const viewContainer = this.getViewContainer(panelDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors.activeViewDescriptors.length === 0 && this.compositeBar.getPinnedComposites().length > 1) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + if (viewContainerModel.activeViewDescriptors.length === 0 && this.compositeBar.getPinnedComposites().length > 1) { this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding } } @@ -610,10 +610,11 @@ export class PanelPart extends CompositePart implements IPanelService { const compositeItems = this.compositeBar.getCompositeBarItems(); for (const compositeItem of compositeItems) { - const viewContainer = this.getViewContainer(compositeItem.id)!; - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - - state.push({ id: compositeItem.id, name: viewDescriptors.getTitle(), pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); + const viewContainer = this.getViewContainer(compositeItem.id); + if (viewContainer) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + state.push({ id: compositeItem.id, name: viewContainerModel.title, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); + } } this.cachedPanelsValue = JSON.stringify(state); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 41c93486799..16c7bcdd413 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -24,7 +24,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -745,7 +745,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private readonly visibleViewsCountFromCache: number | undefined; private readonly visibleViewsStorageId: string; - protected readonly viewsDescriptors: IViewDescriptorCollection; + protected readonly viewContainerModel: IViewContainerModel; private viewDisposables: IDisposable[] = []; private readonly _onTitleAreaUpdate: Emitter = this._register(new Emitter()); @@ -806,7 +806,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.visibleViewsStorageId = `${id}.numberOfVisibleViews`; this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined); this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); - this.viewsDescriptors = this.viewDescriptorService.getViewDescriptors(container); + this.viewContainerModel = this.viewDescriptorService.getViewContainerModel(container); } create(parent: HTMLElement): void { @@ -837,7 +837,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const container = viewContainerRegistry.get(dropData.id)!; - const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; if (!viewsToMove.some(v => !v.canMoveView)) { overlay = new ViewPaneDropOverlay(parent, undefined, this.themeService); @@ -859,7 +859,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const container = viewContainerRegistry.get(dropData.id)!; - const allViews = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; if (!allViews.some(v => !v.canMoveView)) { viewsToMove.push(...allViews); } @@ -882,11 +882,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { })); this._register(this.onDidSashChange(() => this.saveViewSizes())); - this.viewsDescriptors.onDidAdd(added => this.onDidAddViewDescriptors(added)); - this.viewsDescriptors.onDidRemove(removed => this.onDidRemoveViewDescriptors(removed)); - const addedViews: IAddedViewDescriptorRef[] = this.viewsDescriptors.visibleViewDescriptors.map((viewDescriptor, index) => { - const size = this.viewsDescriptors.getSize(viewDescriptor.id); - const collapsed = this.viewsDescriptors.isCollapsed(viewDescriptor.id); + this.viewContainerModel.onDidAddVisibleViewDescriptors(added => this.onDidAddViewDescriptors(added)); + this.viewContainerModel.onDidRemoveVisibleViewDescriptors(removed => this.onDidRemoveViewDescriptors(removed)); + const addedViews: IAddedViewDescriptorRef[] = this.viewContainerModel.visibleViewDescriptors.map((viewDescriptor, index) => { + const size = this.viewContainerModel.getSize(viewDescriptor.id); + const collapsed = this.viewContainerModel.isCollapsed(viewDescriptor.id); return ({ viewDescriptor, index, size, collapsed }); }); if (addedViews.length) { @@ -904,7 +904,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getTitle(): string { - const containerTitle = this.viewsDescriptors.getTitle(); + const containerTitle = this.viewContainerModel.title; if (this.isViewMergedWithContainer()) { const paneItemTitle = this.paneItems[0].pane.title; @@ -955,10 +955,10 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } - const viewToggleActions = this.viewsDescriptors.activeViewDescriptors.map(viewDescriptor => ({ + const viewToggleActions = this.viewContainerModel.activeViewDescriptors.map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, - checked: this.viewsDescriptors.isVisible(viewDescriptor.id), + checked: this.viewContainerModel.isVisible(viewDescriptor.id), enabled: viewDescriptor.canToggleVisibility, run: () => this.toggleViewVisibility(viewDescriptor.id) })); @@ -1087,7 +1087,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { // Save size only when the layout has happened if (this.didLayout) { for (const view of this.panes) { - this.viewsDescriptors.setSize(view.id, this.getPaneSize(view)); + this.viewContainerModel.setSize(view.id, this.getPaneSize(view)); } } } @@ -1096,10 +1096,10 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { // Restore sizes only when the layout has happened if (this.didLayout) { let initialSizes; - for (let i = 0; i < this.viewsDescriptors.visibleViewDescriptors.length; i++) { + for (let i = 0; i < this.viewContainerModel.visibleViewDescriptors.length; i++) { const pane = this.panes[i]; - const viewDescriptor = this.viewsDescriptors.visibleViewDescriptors[i]; - const size = this.viewsDescriptors.getSize(viewDescriptor.id); + const viewDescriptor = this.viewContainerModel.visibleViewDescriptors[i]; + const size = this.viewContainerModel.getSize(viewDescriptor.id); if (typeof size === 'number') { this.resizePane(pane, size); @@ -1114,8 +1114,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private computeInitialSizes(): Map { const sizes: Map = new Map(); if (this.dimension) { - const totalWeight = this.viewsDescriptors.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0); - for (const viewDescriptor of this.viewsDescriptors.visibleViewDescriptors) { + const totalWeight = this.viewContainerModel.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0); + for (const viewDescriptor of this.viewContainerModel.visibleViewDescriptors) { if (this.orientation === Orientation.VERTICAL) { sizes.set(viewDescriptor.id, this.dimension.height * (viewDescriptor.weight || 20) / totalWeight); } else { @@ -1176,7 +1176,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { }); const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => { - this.viewsDescriptors.setCollapsed(viewDescriptor.id, collapsed); + this.viewContainerModel.setCollapsed(viewDescriptor.id, collapsed); }); this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable)); @@ -1207,13 +1207,13 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } protected toggleViewVisibility(viewId: string): void { - const visible = !this.viewsDescriptors.isVisible(viewId); + const visible = !this.viewContainerModel.isVisible(viewId); type ViewsToggleVisibilityClassification = { viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; visible: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; this.telemetryService.publicLog2<{ viewId: String, visible: boolean }, ViewsToggleVisibilityClassification>('views.toggleVisibility', { viewId, visible }); - this.viewsDescriptors.setVisible(viewId, visible); + this.viewContainerModel.setVisible(viewId, visible); } private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { @@ -1268,7 +1268,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const container = viewContainerRegistry.get(dropData.id)!; - const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; if (!viewsToMove.some(v => !v.canMoveView)) { overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.themeService); @@ -1290,7 +1290,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const container = viewContainerRegistry.get(dropData.id)!; - const allViews = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; if (allViews.length > 0 && !allViews.some(v => !v.canMoveView)) { viewsToMove.push(...allViews); @@ -1405,8 +1405,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const fromIndex = firstIndex(this.paneItems, item => item.pane === from); const toIndex = firstIndex(this.paneItems, item => item.pane === to); - const fromViewDescriptor = this.viewsDescriptors.visibleViewDescriptors[fromIndex]; - const toViewDescriptor = this.viewsDescriptors.visibleViewDescriptors[toIndex]; + const fromViewDescriptor = this.viewContainerModel.visibleViewDescriptors[fromIndex]; + const toViewDescriptor = this.viewContainerModel.visibleViewDescriptors[toIndex]; if (fromIndex < 0 || fromIndex >= this.paneItems.length) { return; @@ -1421,7 +1421,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { assertIsDefined(this.paneview).movePane(from, to); - this.viewsDescriptors.move(fromViewDescriptor.id, toViewDescriptor.id); + this.viewContainerModel.move(fromViewDescriptor.id, toViewDescriptor.id); this.updateTitleArea(); } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 1050408d134..d9e0c6e66a3 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -109,15 +109,15 @@ export class ViewsService extends Disposable implements IViewsService { private onDidRegisterViewContainer(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { this.registerViewletOrPanel(viewContainer, viewContainerLocation); - const viewDescriptorCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onViewDescriptorsAdded(viewDescriptorCollection.allViewDescriptors, viewContainer); - this._register(viewDescriptorCollection.onDidChangeViews(({ added, removed }) => { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + this.onViewDescriptorsAdded(viewContainerModel.allViewDescriptors, viewContainer); + this._register(viewContainerModel.onDidChangeAllViewDescriptors(({ added, removed }) => { this.onViewDescriptorsAdded(added, viewContainer); this.onViewDescriptorsRemoved(removed); })); } - private onViewDescriptorsAdded(views: IViewDescriptor[], container: ViewContainer): void { + private onViewDescriptorsAdded(views: ReadonlyArray, container: ViewContainer): void { const location = this.viewContainersRegistry.getViewContainerLocation(container); if (location === undefined) { return; @@ -181,7 +181,7 @@ export class ViewsService extends Disposable implements IViewsService { } } - private onViewDescriptorsRemoved(views: IViewDescriptor[]): void { + private onViewDescriptorsRemoved(views: ReadonlyArray): void { for (const view of views) { const disposable = this.viewDisposable.get(view); if (disposable) { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 4fdd40c1197..8335c7352c3 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -46,8 +46,8 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { this.onFilterChanged(newFilterValue); })); - this._register(this.viewsDescriptors.onDidChangeActiveViews((viewDescriptors) => { - this.updateAllViews(viewDescriptors); + this._register(this.viewContainerModel.onDidChangeActiveViewDescriptors(() => { + this.updateAllViews(this.viewContainerModel.activeViewDescriptors); })); } @@ -62,7 +62,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { } this.allViews.get(filterOnValue)!.set(descriptor.id, descriptor); if (this.filterValue && !this.filterValue.includes(filterOnValue)) { - this.viewsDescriptors.setVisible(descriptor.id, false); + this.viewContainerModel.setVisible(descriptor.id, false); } }); } @@ -75,17 +75,17 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { private onFilterChanged(newFilterValue: string[]) { if (this.allViews.size === 0) { - this.updateAllViews(this.viewsDescriptors.activeViewDescriptors); + this.updateAllViews(this.viewContainerModel.activeViewDescriptors); } - this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsDescriptors.setVisible(item.id, false)); - this.getViewsForTarget(newFilterValue).forEach(item => this.viewsDescriptors.setVisible(item.id, true)); + this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewContainerModel.setVisible(item.id, false)); + this.getViewsForTarget(newFilterValue).forEach(item => this.viewContainerModel.setVisible(item.id, true)); } getContextMenuActions(): IAction[] { const result: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, - checked: this.viewsDescriptors.isVisible(viewDescriptor.id), + checked: this.viewContainerModel.isVisible(viewDescriptor.id), enabled: viewDescriptor.canToggleVisibility, run: () => this.toggleViewVisibility(viewDescriptor.id) })); @@ -133,7 +133,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { } // Check that allViews is ready if (this.allViews.size === 0) { - this.updateAllViews(this.viewsDescriptors.activeViewDescriptors); + this.updateAllViews(this.viewContainerModel.activeViewDescriptors); } return panes; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 9a75589a419..dbdcdccdf92 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -219,18 +219,22 @@ export interface IAddedViewDescriptorRef extends IViewDescriptorRef { size?: number; } -export interface IViewDescriptorCollection extends IDisposable { +export interface IViewContainerModel { - readonly allViewDescriptors: IViewDescriptor[]; - readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; + readonly title: string; + readonly icon: string | URI | undefined; + readonly onDidChangeContainerInfo: Event<{ title?: boolean, icon?: boolean }>; - readonly activeViewDescriptors: IViewDescriptor[]; - readonly onDidChangeActiveViews: Event>; + readonly allViewDescriptors: ReadonlyArray; + readonly onDidChangeAllViewDescriptors: Event<{ added: ReadonlyArray, removed: ReadonlyArray }>; - readonly visibleViewDescriptors: IViewDescriptor[]; - readonly onDidAdd: Event; - readonly onDidRemove: Event - readonly onDidMove: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> + readonly activeViewDescriptors: ReadonlyArray; + readonly onDidChangeActiveViewDescriptors: Event<{ added: ReadonlyArray, removed: ReadonlyArray }>; + + readonly visibleViewDescriptors: ReadonlyArray; + readonly onDidAddVisibleViewDescriptors: Event; + readonly onDidRemoveVisibleViewDescriptors: Event + readonly onDidMoveVisibleViewDescriptors: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> isVisible(id: string): boolean; setVisible(id: string, visible: boolean, size?: number): void; @@ -241,9 +245,6 @@ export interface IViewDescriptorCollection extends IDisposable { getSize(id: string): number | undefined; setSize(id: string, size: number): void - getTitle(): string; - getIcon(): URI | string | undefined; - move(from: string, to: string): void; } @@ -484,7 +485,7 @@ export interface IViewDescriptorService { moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer): void; - getViewDescriptors(container: ViewContainer): IViewDescriptorCollection; + getViewContainerModel(container: ViewContainer): IViewContainerModel; getViewDescriptor(viewId: string): IViewDescriptor | null; diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index e764dc28dc9..66ce39f9cec 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -96,9 +96,9 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider = []; const getViewEntriesForViewlet = (viewlet: ViewletDescriptor, viewContainer: ViewContainer): IViewQuickPickItem[] => { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); const result: IViewQuickPickItem[] = []; - for (const view of viewDescriptors.allViewDescriptors) { + for (const view of viewContainerModel.allViewDescriptors) { if (this.contextKeyService.contextMatchesRules(view.when)) { result.push({ label: view.name, @@ -176,7 +176,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider(ViewExtensions.ViewContainersRegistry).get(container.id); if (viewContainer?.hideIfEmpty) { - return this.viewDescriptorService.getViewDescriptors(viewContainer).activeViewDescriptors.length > 0; + return this.viewDescriptorService.getViewContainerModel(viewContainer).activeViewDescriptors.length > 0; } return true; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 2bd012782a3..5de61ced71d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -101,7 +101,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } get onDidChangeVisibleRepositories(): Event { - const modificationEvent = Event.debounce(Event.any(this.viewsDescriptors.onDidAdd, this.viewsDescriptors.onDidRemove), () => null, 0); + const modificationEvent = Event.debounce(Event.any(this.viewContainerModel.onDidAddVisibleViewDescriptors, this.viewContainerModel.onDidRemoveVisibleViewDescriptors), () => null, 0); return Event.map(modificationEvent, () => this.visibleRepositories); } @@ -140,14 +140,14 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('scm.alwaysShowProviders') && configurationService.getValue('scm.alwaysShowProviders')) { - this.viewsDescriptors.setVisible(MainPane.ID, true); + this.viewContainerModel.setVisible(MainPane.ID, true); } })); this.repositoryCountKey = contextKeyService.createKey('scm.providerCount', 0); - this._register(this.viewsDescriptors.onDidAdd(this.onDidShowView, this)); - this._register(this.viewsDescriptors.onDidRemove(this.onDidHideView, this)); + this._register(this.viewContainerModel.onDidAddVisibleViewDescriptors(this.onDidShowView, this)); + this._register(this.viewContainerModel.onDidRemoveVisibleViewDescriptors(this.onDidHideView, this)); } create(parent: HTMLElement): void { @@ -214,8 +214,8 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @debounce(0) private afterOnDidHideView(): void { - if (this.repositoryCountKey.get()! > 0 && this.viewDescriptors.every(d => !this.viewsDescriptors.isVisible(d.id))) { - this.viewsDescriptors.setVisible(this.viewDescriptors[0].id, true); + if (this.repositoryCountKey.get()! > 0 && this.viewDescriptors.every(d => !this.viewContainerModel.isVisible(d.id))) { + this.viewContainerModel.setVisible(this.viewDescriptors[0].id, true); } } @@ -282,9 +282,9 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } setVisibleRepositories(repositories: ISCMRepository[]): void { - const visibleViewDescriptors = this.viewsDescriptors.visibleViewDescriptors; + const visibleViewDescriptors = this.viewContainerModel.visibleViewDescriptors; - const toSetVisible = this.viewsDescriptors.activeViewDescriptors + const toSetVisible = this.viewContainerModel.activeViewDescriptors .filter((d): d is RepositoryViewDescriptor => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) > -1 && visibleViewDescriptors.indexOf(d) === -1); const toSetInvisible = visibleViewDescriptors @@ -302,11 +302,11 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } } - this.viewsDescriptors.setVisible(viewDescriptor.id, false); + this.viewContainerModel.setVisible(viewDescriptor.id, false); } for (const viewDescriptor of toSetVisible) { - this.viewsDescriptors.setVisible(viewDescriptor.id, true, size); + this.viewContainerModel.setVisible(viewDescriptor.id, true, size); } } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 667b3ed88da..e5cd0501514 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -704,7 +704,7 @@ export class TerminalService implements ITerminalService { const location = this._viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID); if (location === ViewContainerLocation.Panel) { const panel = this._viewDescriptorService.getViewContainer(TERMINAL_VIEW_ID); - if (panel && this._viewDescriptorService.getViewDescriptors(panel).activeViewDescriptors.length === 1) { + if (panel && this._viewDescriptorService.getViewContainerModel(panel).activeViewDescriptors.length === 1) { this._layoutService.setPanelHidden(true); } } diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index f834a9e7330..0967eff4b2a 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewDescriptorCollection, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; -import { IContextKey, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewContainerModel, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; +import { IContextKey, RawContextKey, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -17,68 +17,646 @@ import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { PersistentViewsModel, ViewDescriptorsModel } from 'vs/workbench/services/views/common/viewsModel'; import { URI } from 'vs/base/common/uri'; +import { firstIndex, move } from 'vs/base/common/arrays'; +import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; +import { values } from 'vs/base/common/map'; +import { isEqual } from 'vs/base/common/resources'; -class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection { +class CounterSet implements IReadableSet { - private readonly viewDescriptorsModel: ViewDescriptorsModel; - private readonly viewsModel: PersistentViewsModel; + private map = new Map(); + + add(value: T): CounterSet { + this.map.set(value, (this.map.get(value) || 0) + 1); + return this; + } + + delete(value: T): boolean { + let counter = this.map.get(value) || 0; + + if (counter === 0) { + return false; + } + + counter--; + + if (counter === 0) { + this.map.delete(value); + } else { + this.map.set(value, counter); + } + + return true; + } + + has(value: T): boolean { + return this.map.has(value); + } +} + +interface IStoredWorkspaceViewState { + collapsed: boolean; + isHidden: boolean; + size?: number; + order?: number; +} + +interface IStoredGlobalViewState { + id: string; + isHidden: boolean; + order?: number; +} + +interface IViewDescriptorState { + visibleGlobal: boolean | undefined; + visibleWorkspace: boolean | undefined; + collapsed: boolean | undefined; + active: boolean + order?: number; + size?: number; +} + +class ViewDescriptorsState extends Disposable { + + private readonly workspaceViewsStateStorageId: string; + private readonly globalViewsStateStorageId: string; + private readonly state: Map; + + private _onDidChangeStoredState = this._register(new Emitter<{ id: string, visible: boolean }[]>()); + readonly onDidChangeStoredState = this._onDidChangeStoredState.event; + + constructor( + viewContainerStorageId: string, + @IStorageService private readonly storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + ) { + super(); + + this.globalViewsStateStorageId = `${viewContainerStorageId}.hidden`; + this.workspaceViewsStateStorageId = viewContainerStorageId; + storageKeysSyncRegistryService.registerStorageKey({ key: this.globalViewsStateStorageId, version: 1 }); + this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + + this.state = this.initialize(); + } + + set(id: string, state: IViewDescriptorState): void { + this.state.set(id, state); + } + + get(id: string): IViewDescriptorState | undefined { + return this.state.get(id); + } + + updateState(viewDescriptors: ReadonlyArray): void { + this.updateWorkspaceState(viewDescriptors); + this.updateGlobalState(viewDescriptors); + } + + private updateWorkspaceState(viewDescriptors: ReadonlyArray): void { + const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + for (const viewDescriptor of viewDescriptors) { + const viewState = this.state.get(viewDescriptor.id); + if (viewState) { + storedViewsStates[viewDescriptor.id] = { + collapsed: !!viewState.collapsed, + isHidden: !viewState.visibleWorkspace, + size: viewState.size, + order: viewDescriptor.workspace && viewState ? viewState.order : undefined + }; + } + } + + if (Object.keys(storedViewsStates).length > 0) { + this.storageService.store(this.workspaceViewsStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE); + } else { + this.storageService.remove(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE); + } + } + + private updateGlobalState(viewDescriptors: ReadonlyArray): void { + const storedGlobalState = this.getStoredGlobalState(); + for (const viewDescriptor of viewDescriptors) { + const state = this.state.get(viewDescriptor.id); + storedGlobalState.set(viewDescriptor.id, { + id: viewDescriptor.id, + isHidden: state && viewDescriptor.canToggleVisibility ? !state.visibleGlobal : false, + order: !viewDescriptor.workspace && state ? state.order : undefined + }); + } + this.setStoredGlobalState(storedGlobalState); + } + + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL + && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { + this._globalViewsStatesValue = undefined; + const storedViewsVisibilityStates = this.getStoredGlobalState(); + const changedStates: { id: string, visible: boolean }[] = []; + for (const [id, storedState] of storedViewsVisibilityStates) { + const state = this.state.get(id); + if (state) { + if (state.visibleGlobal !== !storedState.isHidden) { + changedStates.push({ id, visible: !storedState.isHidden }); + } + } + } + if (changedStates.length) { + this._onDidChangeStoredState.fire(changedStates); + } + } + } + + private initialize(): Map { + const viewStates = new Map(); + const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + for (const id of Object.keys(workspaceViewsStates)) { + const workspaceViewState = workspaceViewsStates[id]; + viewStates.set(id, { + active: false, + visibleGlobal: undefined, + visibleWorkspace: isUndefined(workspaceViewState.isHidden) ? undefined : !workspaceViewState.isHidden, + collapsed: workspaceViewState.collapsed, + order: workspaceViewState.order, + size: workspaceViewState.size, + }); + } + + // Migrate to `viewletStateStorageId` + const value = this.storageService.get(this.globalViewsStateStorageId, StorageScope.WORKSPACE, '[]'); + const { state: workspaceVisibilityStates } = this.parseStoredGlobalState(value); + if (workspaceVisibilityStates.size > 0) { + for (const { id, isHidden } of values(workspaceVisibilityStates)) { + let viewState = viewStates.get(id); + // Not migrated to `viewletStateStorageId` + if (viewState) { + if (isUndefined(viewState.visibleWorkspace)) { + viewState.visibleWorkspace = !isHidden; + } + } else { + viewStates.set(id, { + active: false, + collapsed: undefined, + visibleGlobal: undefined, + visibleWorkspace: !isHidden, + }); + } + } + this.storageService.remove(this.globalViewsStateStorageId, StorageScope.WORKSPACE); + } + + const { state, hasDuplicates } = this.parseStoredGlobalState(this.globalViewsStatesValue); + if (hasDuplicates) { + this.setStoredGlobalState(state); + } + for (const { id, isHidden, order } of values(state)) { + let viewState = viewStates.get(id); + if (viewState) { + viewState.visibleGlobal = !isHidden; + if (!isUndefined(order)) { + viewState.order = order; + } + } else { + viewStates.set(id, { + active: false, + visibleGlobal: !isHidden, + order, + collapsed: undefined, + visibleWorkspace: undefined, + }); + } + } + return viewStates; + } + + private getStoredGlobalState(): Map { + return this.parseStoredGlobalState(this.globalViewsStatesValue).state; + } + + private setStoredGlobalState(storedGlobalState: Map): void { + this.globalViewsStatesValue = JSON.stringify(values(storedGlobalState)); + } + + private parseStoredGlobalState(value: string): { state: Map, hasDuplicates: boolean } { + const storedValue = >JSON.parse(value); + let hasDuplicates = false; + const state = storedValue.reduce((result, storedState) => { + if (typeof storedState === 'string' /* migration */) { + hasDuplicates = hasDuplicates || result.has(storedState); + result.set(storedState, { id: storedState, isHidden: true }); + } else { + hasDuplicates = hasDuplicates || result.has(storedState.id); + result.set(storedState.id, storedState); + } + return result; + }, new Map()); + return { state, hasDuplicates }; + } + + private _globalViewsStatesValue: string | undefined; + private get globalViewsStatesValue(): string { + if (!this._globalViewsStatesValue) { + this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); + } + + return this._globalViewsStatesValue; + } + + private set globalViewsStatesValue(globalViewsStatesValue: string) { + if (this.globalViewsStatesValue !== globalViewsStatesValue) { + this._globalViewsStatesValue = globalViewsStatesValue; + this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); + } + } + + private getStoredGlobalViewsStatesValue(): string { + return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); + } + + private setStoredGlobalViewsStatesValue(value: string): void { + this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); + } + +} + +interface IViewDescriptorItem { + viewDescriptor: IViewDescriptor; + state: IViewDescriptorState; +} + +class ViewContainerModel extends Disposable implements IViewContainerModel { + + private readonly contextKeys = new CounterSet(); + private viewDescriptorItems: IViewDescriptorItem[] = []; + private viewDescriptorsState: ViewDescriptorsState; + + // Container Info + private _title!: string; + get title(): string { return this._title; } + private _icon: URI | string | undefined; + get icon(): URI | string | undefined { return this._icon; } + + private _onDidChangeContainerInfo = this._register(new Emitter<{ title?: boolean, icon?: boolean }>()); + readonly onDidChangeContainerInfo = this._onDidChangeContainerInfo.event; + + // All View Descriptors + get allViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.map(item => item.viewDescriptor); } + private _onDidChangeAllViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray, removed: ReadonlyArray }>()); + readonly onDidChangeAllViewDescriptors = this._onDidChangeAllViewDescriptors.event; + + // Active View Descriptors + get activeViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.filter(item => item.state.active).map(item => item.viewDescriptor); } + private _onDidChangeActiveViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray, removed: ReadonlyArray }>()); + readonly onDidChangeActiveViewDescriptors = this._onDidChangeActiveViewDescriptors.event; + + // Visible View Descriptors + get visibleViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.filter(item => this.isViewDescriptorVisible(item)).map(item => item.viewDescriptor); } + + private _onDidAddVisibleViewDescriptors = this._register(new Emitter()); + readonly onDidAddVisibleViewDescriptors: Event = this._onDidAddVisibleViewDescriptors.event; + + private _onDidRemoveVisibleViewDescriptors = this._register(new Emitter()); + readonly onDidRemoveVisibleViewDescriptors: Event = this._onDidRemoveVisibleViewDescriptors.event; + + private _onDidMoveVisibleViewDescriptors = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>()); + readonly onDidMoveVisibleViewDescriptors: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> = this._onDidMoveVisibleViewDescriptors.event; constructor( private readonly container: ViewContainer, @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); - this.viewDescriptorsModel = instantiationService.createInstance(ViewDescriptorsModel); - this.viewsModel = instantiationService.createInstance(PersistentViewsModel, container.storageId || `${container.id}.state`, this.viewDescriptorsModel); + + this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(() => this.onDidChangeContext())); + this.viewDescriptorsState = this._register(instantiationService.createInstance(ViewDescriptorsState, container.storageId || `${container.id}.state`)); + this._register(this.viewDescriptorsState.onDidChangeStoredState(items => this.updateVisibility(items))); + + this._register(Event.any( + this.onDidAddVisibleViewDescriptors, + this.onDidRemoveVisibleViewDescriptors, + this.onDidMoveVisibleViewDescriptors) + (() => { + this.viewDescriptorsState.updateState(this.allViewDescriptors); + this.updateContainerInfo(); + })); + + this.updateContainerInfo(); + } + + private updateContainerInfo(): void { + /* Use default container info if one of the visible view descriptors belongs to the current container by default */ + const useDefaultContainerInfo = this.visibleViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container); + const title = useDefaultContainerInfo ? this.container.name : this.visibleViewDescriptors[0]?.name || ''; + let titleChanged: boolean = false; + if (this._title !== title) { + this._title = title; + titleChanged = true; + } + + const icon = useDefaultContainerInfo ? this.container.icon : this.visibleViewDescriptors[0]?.containerIcon || 'codicon-window'; + let iconChanged: boolean = false; + if (URI.isUri(icon) && URI.isUri(this._icon) ? isEqual(icon, this._icon) : this._icon !== icon) { + this._icon = icon; + iconChanged = true; + } + + if (titleChanged || iconChanged) { + this._onDidChangeContainerInfo.fire({ title: titleChanged, icon: iconChanged }); + } + } + + isVisible(id: string): boolean { + const viewDescriptorItem = this.viewDescriptorItems.filter(v => v.viewDescriptor.id === id)[0]; + if (!viewDescriptorItem) { + throw new Error(`Unknown view ${id}`); + } + return this.isViewDescriptorVisible(viewDescriptorItem); + } + + setVisible(id: string, visible: boolean, size?: number): void { + this.updateVisibility([{ id, visible, size }]); + } + + private updateVisibility(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { + const added: IAddedViewDescriptorRef[] = []; + const removed: IViewDescriptorRef[] = []; + + for (const { visibleIndex, viewDescriptorItem, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) { + const viewDescriptor = viewDescriptorItem.viewDescriptor; + + if (!viewDescriptor.canToggleVisibility) { + throw new Error(`Can't toggle this view's visibility`); + } + + if (this.isViewDescriptorVisible(viewDescriptorItem) === visible) { + return; + } + + if (viewDescriptor.workspace) { + viewDescriptorItem.state.visibleWorkspace = visible; + } else { + viewDescriptorItem.state.visibleGlobal = visible; + } + + if (typeof viewDescriptorItem.state.size === 'number') { + viewDescriptorItem.state.size = size; + } + + if (visible) { + added.push({ index: visibleIndex, viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed }); + } else { + removed.push({ index: visibleIndex, viewDescriptor }); + } + } + + if (added.length) { + this._onDidAddVisibleViewDescriptors.fire(added); + } + if (removed.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removed); + } + } + + isCollapsed(id: string): boolean { + return !!this.find(id).viewDescriptorItem.state.collapsed; + } + + setCollapsed(id: string, collapsed: boolean): void { + const { viewDescriptorItem } = this.find(id); + if (viewDescriptorItem.state.collapsed !== collapsed) { + viewDescriptorItem.state.collapsed = collapsed; + } + this.viewDescriptorsState.updateState(this.allViewDescriptors); + } + + getSize(id: string): number | undefined { + return this.find(id).viewDescriptorItem.state.size; + } + + setSize(id: string, size: number): void { + const { viewDescriptorItem } = this.find(id); + if (viewDescriptorItem.state.size !== size) { + viewDescriptorItem.state.size = size; + } + this.viewDescriptorsState.updateState(this.allViewDescriptors); + } + + move(from: string, to: string): void { + const fromIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === from); + const toIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === to); + + const fromViewDescriptor = this.viewDescriptorItems[fromIndex]; + const toViewDescriptor = this.viewDescriptorItems[toIndex]; + + move(this.viewDescriptorItems, fromIndex, toIndex); + + for (let index = 0; index < this.viewDescriptorItems.length; index++) { + this.viewDescriptorItems[index].state.order = index; + } + + this._onDidMoveVisibleViewDescriptors.fire({ + from: { index: fromIndex, viewDescriptor: fromViewDescriptor.viewDescriptor }, + to: { index: toIndex, viewDescriptor: toViewDescriptor.viewDescriptor } + }); + } + + add(viewDescriptors: IViewDescriptor[]): void { + const addedItems: IViewDescriptorItem[] = []; + const addedActiveDescriptors: IViewDescriptor[] = []; + const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; + + for (const viewDescriptor of viewDescriptors) { + + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.add(key); + } + } + + let state = this.viewDescriptorsState.get(viewDescriptor.id); + if (state) { + // set defaults if not set + if (viewDescriptor.workspace) { + state.visibleWorkspace = isUndefinedOrNull(state.visibleWorkspace) ? !viewDescriptor.hideByDefault : state.visibleWorkspace; + } else { + state.visibleGlobal = isUndefinedOrNull(state.visibleGlobal) ? !viewDescriptor.hideByDefault : state.visibleGlobal; + } + state.collapsed = isUndefinedOrNull(state.collapsed) ? !!viewDescriptor.collapsed : state.collapsed; + } else { + state = { + active: false, + visibleGlobal: !viewDescriptor.hideByDefault, + visibleWorkspace: !viewDescriptor.hideByDefault, + collapsed: !!viewDescriptor.collapsed, + }; + } + this.viewDescriptorsState.set(viewDescriptor.id, state); + state.active = this.contextKeyService.contextMatchesRules(viewDescriptor.when); + addedItems.push({ viewDescriptor, state }); + + if (state.active) { + addedActiveDescriptors.push(viewDescriptor); + } + } + + this.viewDescriptorItems.push(...addedItems); + this.viewDescriptorItems.sort(this.compareViewDescriptors.bind(this)); + + for (const viewDescriptorItem of addedItems) { + if (this.isViewDescriptorVisible(viewDescriptorItem)) { + const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id); + addedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed }); + } + } + + this._onDidChangeAllViewDescriptors.fire({ added: addedItems.map(({ viewDescriptor }) => viewDescriptor), removed: [] }); + if (addedActiveDescriptors.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveDescriptors, removed: [] })); + } + if (addedVisibleItems.length) { + this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems); + } } - get allViewDescriptors(): IViewDescriptor[] { return this.viewDescriptorsModel.allViewDescriptors; } - get onDidChangeViews(): Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }> { return this.viewDescriptorsModel.onDidChangeViews; } + remove(viewDescriptors: IViewDescriptor[]): void { + const removed: IViewDescriptor[] = []; + const removedItems: IViewDescriptorItem[] = []; + const removedActiveDescriptors: IViewDescriptor[] = []; + const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = []; - get activeViewDescriptors(): IViewDescriptor[] { return this.viewDescriptorsModel.activeViewDescriptors; } - get _onDidChangeActiveViews(): Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> { return this.viewDescriptorsModel.onDidChangeActiveViews; } - get onDidChangeActiveViews(): Event> { return this.viewsModel.onDidChangeActiveViews; } + for (const viewDescriptor of viewDescriptors) { + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.delete(key); + } + } + const index = firstIndex(this.viewDescriptorItems, i => i.viewDescriptor.id === viewDescriptor.id); + if (index !== -1) { + removed.push(viewDescriptor); + const viewDescriptorItem = this.viewDescriptorItems[index]; + if (viewDescriptorItem.state.active) { + removedActiveDescriptors.push(viewDescriptorItem.viewDescriptor); + } + if (this.isViewDescriptorVisible(viewDescriptorItem)) { + const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id); + removedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor }); + } + removedItems.push(viewDescriptorItem); + } + } - addViews(viewDescriptors: IViewDescriptor[]): void { return this.viewDescriptorsModel.addViews(viewDescriptors); } - removeViews(viewDescriptors: IViewDescriptor[]): void { return this.viewDescriptorsModel.removeViews(viewDescriptors); } + removedItems.forEach(item => this.viewDescriptorItems.splice(this.viewDescriptorItems.indexOf(item), 1)); - get visibleViewDescriptors(): IViewDescriptor[] { return this.viewsModel.visibleViewDescriptors; } - get onDidAdd(): Event { return this.viewsModel.onDidAdd; } - get onDidRemove(): Event { return this.viewsModel.onDidRemove; } - get onDidMove(): Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> { return this.viewsModel.onDidMove; } + this._onDidChangeAllViewDescriptors.fire({ added: [], removed }); + if (removedActiveDescriptors.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: [], removed: removedActiveDescriptors })); + } + if (removedVisibleItems.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems); + } + } - isVisible(id: string): boolean { return this.viewsModel.isVisible(id); } - setVisible(id: string, visible: boolean, size?: number): void { return this.viewsModel.setVisible(id, visible, size); } + private onDidChangeContext(): void { + const addedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = []; + const removedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = []; + const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = []; + const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; + + for (const item of this.viewDescriptorItems) { + const wasActive = item.state.active; + const wasVisible = this.isViewDescriptorVisible(item); + const isActive = this.contextKeyService.contextMatchesRules(item.viewDescriptor.when); + if (wasActive !== isActive) { + if (isActive) { + addedActiveItems.push({ item, wasVisible }); + } else { + removedActiveItems.push({ item, wasVisible }); + } + } + } - isCollapsed(id: string): boolean { return this.viewsModel.isCollapsed(id); } - setCollapsed(id: string, collapsed: boolean): void { return this.viewsModel.setCollapsed(id, collapsed); } + for (const { item, wasVisible } of removedActiveItems) { + if (wasVisible) { + const { visibleIndex } = this.find(item.viewDescriptor.id); + removedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor }); + } + } + + // Update the State + removedActiveItems.forEach(({ item }) => item.state.active = false); + addedActiveItems.forEach(({ item }) => item.state.active = true); - private shouldUseContainerInfo(): boolean { - if (this.allViewDescriptors.length === 0) { - return true; + for (const { item, wasVisible } of addedActiveItems) { + if (wasVisible !== this.isViewDescriptorVisibleWhenActive(item)) { + const { visibleIndex } = this.find(item.viewDescriptor.id); + addedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor, size: item.state.size, collapsed: !!item.state.collapsed }); + } } - if (this.visibleViewDescriptors.length === 0) { - return true; + if (addedActiveItems.length || removedActiveItems.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveItems.map(({ item }) => item.viewDescriptor), removed: removedActiveItems.map(({ item }) => item.viewDescriptor) })); } + if (removedVisibleItems.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems); + } + if (addedVisibleItems.length) { + this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems); + } + } - return this.allViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container); + private isViewDescriptorVisible(viewDescriptorItem: IViewDescriptorItem): boolean { + if (!viewDescriptorItem.state.active) { + return false; + } + return this.isViewDescriptorVisibleWhenActive(viewDescriptorItem); } - getTitle(): string { - return this.shouldUseContainerInfo() ? this.container.name : this.visibleViewDescriptors[0].name; + private isViewDescriptorVisibleWhenActive(viewDescriptorItem: IViewDescriptorItem): boolean { + if (viewDescriptorItem.viewDescriptor.workspace) { + return !!viewDescriptorItem.state.visibleWorkspace; + } + return !!viewDescriptorItem.state.visibleGlobal; } - getIcon(): URI | string | undefined { - return this.shouldUseContainerInfo() ? this.container.icon : this.visibleViewDescriptors[0]?.containerIcon || 'codicon-window'; + private find(id: string): { index: number, visibleIndex: number, viewDescriptorItem: IViewDescriptorItem; } { + for (let i = 0, visibleIndex = 0; i < this.viewDescriptorItems.length; i++) { + const viewDescriptorItem = this.viewDescriptorItems[i]; + if (viewDescriptorItem.viewDescriptor.id === id) { + return { index: i, visibleIndex, viewDescriptorItem: viewDescriptorItem }; + } + if (this.isViewDescriptorVisible(viewDescriptorItem)) { + visibleIndex++; + } + } + throw new Error(`view descriptor ${id} not found`); + } + + private compareViewDescriptors(a: IViewDescriptorItem, b: IViewDescriptorItem): number { + if (a.viewDescriptor.id === b.viewDescriptor.id) { + return 0; + } + + return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a.viewDescriptor, b.viewDescriptor); } - getSize(id: string): number | undefined { return this.viewsModel.getSize(id); } - setSize(id: string, size: number): void { return this.viewsModel.setSize(id, size); } + private getViewOrder(viewDescriptorItem: IViewDescriptorItem): number { + const viewOrder = typeof viewDescriptorItem.state.order === 'number' ? viewDescriptorItem.state.order : viewDescriptorItem.viewDescriptor.order; + return typeof viewOrder === 'number' ? viewOrder : Number.MAX_VALUE; + } + + private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) { + if (!a.group || !b.group) { + return 0; + } - move(from: string, to: string): void { return this.viewsModel.move(from, to); } + if (a.group === b.group) { + return 0; + } + + return a.group < b.group ? -1 : 1; + } } interface ICachedViewContainerInfo { @@ -99,7 +677,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly _onDidChangeLocation: Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }>()); readonly onDidChangeLocation: Event<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._onDidChangeLocation.event; - private readonly viewDescriptorCollections: Map; + private readonly viewContainerModels: Map; private readonly activeViewContextKeys: Map>; private readonly movableViewContextKeys: Map>; private readonly defaultViewLocationContextKeys: Map>; @@ -136,7 +714,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor super(); storageKeysSyncRegistryService.registerStorageKey({ key: ViewDescriptorService.CACHED_VIEW_POSITIONS, version: 1 }); - this.viewDescriptorCollections = new Map(); + this.viewContainerModels = new Map(); this.activeViewContextKeys = new Map>(); this.movableViewContextKeys = new Map>(); this.defaultViewLocationContextKeys = new Map>(); @@ -160,8 +738,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); this._register(this.viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); this._register(toDisposable(() => { - this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose()); - this.viewDescriptorCollections.clear(); + this.viewContainerModels.forEach(({ disposable }) => disposable.dispose()); + this.viewContainerModels.clear(); })); this._register(this.storageService.onDidChangeStorage((e) => { this.onDidStorageChange(e); })); @@ -176,7 +754,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const containerData = groupedViews.get(containerId)!; // The container has not been registered yet - if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) { + if (!viewContainer || !this.viewContainerModels.has(viewContainer)) { if (containerData.cachedContainerInfo && this.shouldGenerateContainer(containerData.cachedContainerInfo)) { const containerInfo = containerData.cachedContainerInfo; @@ -198,7 +776,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const viewContainer = this.viewContainersRegistry.get(viewContainerId); // The container has not been registered yet - if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) { + if (!viewContainer || !this.viewContainerModels.has(viewContainer)) { continue; } @@ -321,8 +899,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.viewsRegistry.getViewContainer(viewId) ?? null; } - getViewDescriptors(container: ViewContainer): ViewDescriptorCollection { - return this.getOrRegisterViewDescriptorCollection(container); + getViewContainerModel(container: ViewContainer): ViewContainerModel { + return this.getOrRegisterViewContainerModel(container); } moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void { @@ -456,8 +1034,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // If a value is not present in the cache, it must be reset to default this.viewContainersRegistry.all.forEach(viewContainer => { - const viewDescriptorCollection = this.getViewDescriptors(viewContainer); - viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { + const viewContainerModel = this.getViewContainerModel(viewContainer); + viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { if (!newCachedPositions.has(viewDescriptor.id)) { const currentContainer = this.getViewContainer(viewDescriptor.id); const defaultContainer = this.getDefaultContainer(viewDescriptor.id); @@ -492,8 +1070,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private saveViewPositionsToCache(): void { this.viewContainersRegistry.all.forEach(viewContainer => { - const viewDescriptorCollection = this.getViewDescriptors(viewContainer); - viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { + const viewContainerModel = this.getViewContainerModel(viewContainer); + viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { const containerLocation = this.getViewContainerLocation(viewContainer); this.cachedViewInfo.set(viewDescriptor.id, { containerId: viewContainer.id, @@ -540,20 +1118,20 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private onDidRegisterViewContainer(viewContainer: ViewContainer): void { - this.getOrRegisterViewDescriptorCollection(viewContainer); + this.getOrRegisterViewContainerModel(viewContainer); } - private getOrRegisterViewDescriptorCollection(viewContainer: ViewContainer): ViewDescriptorCollection { - let viewDescriptorCollection = this.viewDescriptorCollections.get(viewContainer)?.viewDescriptorCollection; + private getOrRegisterViewContainerModel(viewContainer: ViewContainer): ViewContainerModel { + let viewContainerModel = this.viewContainerModels.get(viewContainer)?.viewContainerModel; - if (!viewDescriptorCollection) { + if (!viewContainerModel) { const disposables = new DisposableStore(); - viewDescriptorCollection = disposables.add(this.instantiationService.createInstance(ViewDescriptorCollection, viewContainer)); + viewContainerModel = disposables.add(this.instantiationService.createInstance(ViewContainerModel, viewContainer)); - this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); - viewDescriptorCollection._onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); + this.onDidChangeActiveViews({ added: viewContainerModel.activeViewDescriptors, removed: [] }); + viewContainerModel.onDidChangeActiveViewDescriptors(changed => this.onDidChangeActiveViews(changed), this, disposables); - this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables }); + this.viewContainerModels.set(viewContainer, { viewContainerModel: viewContainerModel, disposable: disposables }); const viewsToRegister = this.getViewsByContainer(viewContainer); if (viewsToRegister.length) { @@ -562,18 +1140,18 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - return viewDescriptorCollection; + return viewContainerModel; } private onDidDeregisterViewContainer(viewContainer: ViewContainer): void { - const viewDescriptorCollectionItem = this.viewDescriptorCollections.get(viewContainer); - if (viewDescriptorCollectionItem) { - viewDescriptorCollectionItem.disposable.dispose(); - this.viewDescriptorCollections.delete(viewContainer); + const viewContainerModelItem = this.viewContainerModels.get(viewContainer); + if (viewContainerModelItem) { + viewContainerModelItem.disposable.dispose(); + this.viewContainerModels.delete(viewContainer); } } - private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[]; }): void { + private onDidChangeActiveViews({ added, removed }: { added: ReadonlyArray, removed: ReadonlyArray; }): void { added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true)); removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false)); } @@ -586,7 +1164,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainer(view.id) === container); }); - this.getViewDescriptors(container).addViews(views); + this.getViewContainerModel(container).add(views); } private removeViews(container: ViewContainer, views: IViewDescriptor[]): void { @@ -594,7 +1172,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor views.forEach(view => this.getOrCreateDefaultViewLocationContextKey(view).set(false)); // Remove the views - this.getViewDescriptors(container).removeViews(views); + this.getViewContainerModel(container).remove(views); } private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey { diff --git a/src/vs/workbench/services/views/common/viewsModel.ts b/src/vs/workbench/services/views/common/viewsModel.ts deleted file mode 100644 index e3fb06acfac..00000000000 --- a/src/vs/workbench/services/views/common/viewsModel.ts +++ /dev/null @@ -1,648 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from 'vs/base/common/lifecycle'; -import { IViewDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; -import { Event, Emitter } from 'vs/base/common/event'; -import { firstIndex, move } from 'vs/base/common/arrays'; -import { isUndefinedOrNull, isUndefined } from 'vs/base/common/types'; -import { values } from 'vs/base/common/map'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; -import { IContextKeyChangeEvent, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; - -class CounterSet implements IReadableSet { - - private map = new Map(); - - add(value: T): CounterSet { - this.map.set(value, (this.map.get(value) || 0) + 1); - return this; - } - - delete(value: T): boolean { - let counter = this.map.get(value) || 0; - - if (counter === 0) { - return false; - } - - counter--; - - if (counter === 0) { - this.map.delete(value); - } else { - this.map.set(value, counter); - } - - return true; - } - - has(value: T): boolean { - return this.map.has(value); - } -} - -interface IViewItem { - viewDescriptor: IViewDescriptor; - active: boolean; -} - -export class ViewDescriptorsModel extends Disposable { - - private contextKeys = new CounterSet(); - private items: IViewItem[] = []; - - private _onDidChangeViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); - readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeViews.event; - - private _onDidChangeActiveViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); - readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeActiveViews.event; - - get activeViewDescriptors(): IViewDescriptor[] { - return this.items - .filter(i => i.active) - .map(i => i.viewDescriptor); - } - - get allViewDescriptors(): IViewDescriptor[] { - return this.items.map(i => i.viewDescriptor); - } - - constructor( - @IContextKeyService private readonly contextKeyService: IContextKeyService, - ) { - super(); - this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(this.onContextChanged, this)); - } - - addViews(viewDescriptors: IViewDescriptor[]): void { - const added: IViewDescriptor[] = []; - - for (const viewDescriptor of viewDescriptors) { - const item = { - viewDescriptor, - active: this.isViewDescriptorActive(viewDescriptor) // TODO: should read from some state? - }; - - this.items.push(item); - - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.add(key); - } - } - - if (item.active) { - added.push(viewDescriptor); - } - } - - this._onDidChangeViews.fire({ added: viewDescriptors, removed: [] }); - - if (added.length) { - this._onDidChangeActiveViews.fire({ added, removed: [] }); - } - } - - removeViews(viewDescriptors: IViewDescriptor[]): void { - const removed: IViewDescriptor[] = []; - - for (const viewDescriptor of viewDescriptors) { - const index = firstIndex(this.items, i => i.viewDescriptor.id === viewDescriptor.id); - - if (index === -1) { - continue; - } - - const item = this.items[index]; - this.items.splice(index, 1); - - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.delete(key); - } - } - - if (item.active) { - removed.push(viewDescriptor); - } - } - - this._onDidChangeViews.fire({ added: [], removed: viewDescriptors }); - - if (removed.length) { - this._onDidChangeActiveViews.fire({ added: [], removed }); - } - } - - private onContextChanged(event: IContextKeyChangeEvent): void { - const removed: IViewDescriptor[] = []; - const added: IViewDescriptor[] = []; - - for (const item of this.items) { - const active = this.isViewDescriptorActive(item.viewDescriptor); - - if (item.active !== active) { - if (active) { - added.push(item.viewDescriptor); - } else { - removed.push(item.viewDescriptor); - } - } - - item.active = active; - } - - if (added.length || removed.length) { - this._onDidChangeActiveViews.fire({ added, removed }); - } - } - - private isViewDescriptorActive(viewDescriptor: IViewDescriptor): boolean { - return !viewDescriptor.when || this.contextKeyService.contextMatchesRules(viewDescriptor.when); - } -} - -export interface IViewState { - visibleGlobal: boolean | undefined; - visibleWorkspace: boolean | undefined; - collapsed: boolean | undefined; - order?: number; - size?: number; -} - -export class ViewsModel extends Disposable { - - private _viewDescriptors: IViewDescriptor[] = []; - get viewDescriptors(): ReadonlyArray { - return this._viewDescriptors; - } - - get visibleViewDescriptors(): IViewDescriptor[] { - return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)).sort((a, b) => { - const aIndex = this.viewStates.get(a.id)?.order; - const bIndex = this.viewStates.get(b.id)?.order; - - if (aIndex === undefined) { - return 1; - } - - if (bIndex === undefined) { - return -1; - } - - return aIndex - bIndex; - }); - } - - private _onDidAdd = this._register(new Emitter()); - readonly onDidAdd: Event = this._onDidAdd.event; - - private _onDidRemove = this._register(new Emitter()); - readonly onDidRemove: Event = this._onDidRemove.event; - - private _onDidMove = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>()); - readonly onDidMove: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> = this._onDidMove.event; - - private _onDidChangeViewState = this._register(new Emitter()); - protected readonly onDidChangeViewState: Event = this._onDidChangeViewState.event; - - private _onDidChangeActiveViews = this._register(new Emitter>()); - readonly onDidChangeActiveViews: Event> = this._onDidChangeActiveViews.event; - - constructor( - viewDescriptorCollection: ViewDescriptorsModel, - protected viewStates = new Map(), - ) { - super(); - this._register(viewDescriptorCollection.onDidChangeActiveViews(() => this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors))); - this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors); - } - - isVisible(id: string): boolean { - const viewDescriptor = this.viewDescriptors.filter(v => v.id === id)[0]; - - if (!viewDescriptor) { - throw new Error(`Unknown view ${id}`); - } - - return this.isViewDescriptorVisible(viewDescriptor); - } - - setVisible(id: string, visible: boolean, size?: number): void { - this.doSetVisible([{ id, visible, size }]); - } - - protected doSetVisible(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { - const added: IAddedViewDescriptorRef[] = []; - const removed: IViewDescriptorRef[] = []; - - for (const { visibleIndex, viewDescriptor, state, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) { - - if (!viewDescriptor.canToggleVisibility) { - throw new Error(`Can't toggle this view's visibility`); - } - - if (this.isViewDescriptorVisible(viewDescriptor) === visible) { - return; - } - - if (viewDescriptor.workspace) { - state.visibleWorkspace = visible; - } else { - state.visibleGlobal = visible; - } - - if (typeof size === 'number') { - state.size = size; - } - - if (visible) { - added.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); - } else { - removed.push({ index: visibleIndex, viewDescriptor }); - } - } - - if (added.length) { - this._onDidAdd.fire(added); - } - if (removed.length) { - this._onDidRemove.fire(removed); - } - } - - isCollapsed(id: string): boolean { - const state = this.viewStates.get(id); - - if (!state) { - throw new Error(`Unknown view ${id}`); - } - - return !!state.collapsed; - } - - setCollapsed(id: string, collapsed: boolean): void { - const { index, state, viewDescriptor } = this.find(id); - if (state.collapsed !== collapsed) { - state.collapsed = collapsed; - this._onDidChangeViewState.fire({ viewDescriptor, index }); - } - } - - getSize(id: string): number | undefined { - const state = this.viewStates.get(id); - - if (!state) { - throw new Error(`Unknown view ${id}`); - } - - return state.size; - } - - setSize(id: string, size: number): void { - const { index, state, viewDescriptor } = this.find(id); - if (state.size !== size) { - state.size = size; - this._onDidChangeViewState.fire({ viewDescriptor, index }); - } - } - - move(from: string, to: string): void { - const fromIndex = firstIndex(this.viewDescriptors, v => v.id === from); - const toIndex = firstIndex(this.viewDescriptors, v => v.id === to); - - const fromViewDescriptor = this.viewDescriptors[fromIndex]; - const toViewDescriptor = this.viewDescriptors[toIndex]; - - move(this._viewDescriptors, fromIndex, toIndex); - - for (let index = 0; index < this.viewDescriptors.length; index++) { - const state = this.viewStates.get(this.viewDescriptors[index].id)!; - state.order = index; - } - - this._onDidMove.fire({ - from: { index: fromIndex, viewDescriptor: fromViewDescriptor }, - to: { index: toIndex, viewDescriptor: toViewDescriptor } - }); - } - - private isViewDescriptorVisible(viewDescriptor: IViewDescriptor): boolean { - const viewState = this.viewStates.get(viewDescriptor.id); - if (!viewState) { - throw new Error(`Unknown view ${viewDescriptor.id}`); - } - return viewDescriptor.workspace ? !!viewState.visibleWorkspace : !!viewState.visibleGlobal; - } - - private find(id: string): { index: number, visibleIndex: number, viewDescriptor: IViewDescriptor, state: IViewState; } { - for (let i = 0, visibleIndex = 0; i < this.viewDescriptors.length; i++) { - const viewDescriptor = this.viewDescriptors[i]; - const state = this.viewStates.get(viewDescriptor.id); - if (!state) { - throw new Error(`View state for ${id} not found`); - } - - if (viewDescriptor.id === id) { - return { index: i, visibleIndex, viewDescriptor, state }; - } - - if (viewDescriptor.workspace ? state.visibleWorkspace : state.visibleGlobal) { - visibleIndex++; - } - } - - throw new Error(`view descriptor ${id} not found`); - } - - private compareViewDescriptors(a: IViewDescriptor, b: IViewDescriptor): number { - if (a.id === b.id) { - return 0; - } - - return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a, b); - } - - private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) { - if (!a.group || !b.group) { - return 0; - } - - if (a.group === b.group) { - return 0; - } - - return a.group < b.group ? -1 : 1; - } - - private getViewOrder(viewDescriptor: IViewDescriptor): number { - const viewState = this.viewStates.get(viewDescriptor.id); - const viewOrder = viewState && typeof viewState.order === 'number' ? viewState.order : viewDescriptor.order; - return typeof viewOrder === 'number' ? viewOrder : Number.MAX_VALUE; - } - - private onDidChangeViewDescriptors(viewDescriptors: IViewDescriptor[]): void { - for (const viewDescriptor of viewDescriptors) { - const viewState = this.viewStates.get(viewDescriptor.id); - if (viewState) { - // set defaults if not set - if (viewDescriptor.workspace) { - viewState.visibleWorkspace = isUndefinedOrNull(viewState.visibleWorkspace) ? !viewDescriptor.hideByDefault : viewState.visibleWorkspace; - } else { - viewState.visibleGlobal = isUndefinedOrNull(viewState.visibleGlobal) ? !viewDescriptor.hideByDefault : viewState.visibleGlobal; - } - viewState.collapsed = isUndefinedOrNull(viewState.collapsed) ? !!viewDescriptor.collapsed : viewState.collapsed; - } else { - this.viewStates.set(viewDescriptor.id, { - visibleGlobal: !viewDescriptor.hideByDefault, - visibleWorkspace: !viewDescriptor.hideByDefault, - collapsed: !!viewDescriptor.collapsed - }); - } - } - - viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); - - const toRemove: { index: number, viewDescriptor: IViewDescriptor; }[] = []; - for (let index = 0; index < this._viewDescriptors.length; index++) { - const previousViewDescriptor = this._viewDescriptors[index]; - if (this.isViewDescriptorVisible(previousViewDescriptor) && viewDescriptors.every(viewDescriptor => viewDescriptor.id !== previousViewDescriptor.id)) { - const { visibleIndex } = this.find(previousViewDescriptor.id); - toRemove.push({ index: visibleIndex, viewDescriptor: previousViewDescriptor }); - } - } - - const previous = this._viewDescriptors; - this._viewDescriptors = viewDescriptors.slice(0); - - const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; - for (let i = 0; i < this._viewDescriptors.length; i++) { - const viewDescriptor = this._viewDescriptors[i]; - if (this.isViewDescriptorVisible(viewDescriptor) && previous.every(previousViewDescriptor => previousViewDescriptor.id !== viewDescriptor.id)) { - const { visibleIndex, state } = this.find(viewDescriptor.id); - toAdd.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); - } - } - - if (toRemove.length) { - this._onDidRemove.fire(toRemove); - } - - if (toAdd.length) { - this._onDidAdd.fire(toAdd); - } - - this._onDidChangeActiveViews.fire(this.viewDescriptors); - } -} - -interface IStoredWorkspaceViewState { - collapsed: boolean; - isHidden: boolean; - size?: number; - order?: number; -} - -interface IStoredGlobalViewState { - id: string; - isHidden: boolean; - order?: number; -} - -export class PersistentViewsModel extends ViewsModel { - - private readonly workspaceViewsStateStorageId: string; - private readonly globalViewsStateStorageId: string; - - private storageService: IStorageService; - - constructor( - viewletStateStorageId: string, - viewDescriptorCollection: ViewDescriptorsModel, - @IStorageService storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService - ) { - const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; - storageKeysSyncRegistryService.registerStorageKey({ key: globalViewsStateStorageId, version: 1 }); - const viewStates = PersistentViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); - - super(viewDescriptorCollection, viewStates); - - this.storageService = storageService; - this.workspaceViewsStateStorageId = viewletStateStorageId; - this.globalViewsStateStorageId = globalViewsStateStorageId; - - this._register(Event.any( - this.onDidAdd, - this.onDidRemove, - Event.map(this.onDidMove, ({ from, to }) => [from, to]), - Event.map(this.onDidChangeViewState, viewDescriptorRef => [viewDescriptorRef])) - (viewDescriptorRefs => this.saveViewsStates())); - - this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); - this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); - } - - private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { - if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL - && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { - this._globalViewsStatesValue = undefined; - const storedViewsVisibilityStates = PersistentViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); - const changedViews: { id: string, visible: boolean }[] = []; - for (const [id, state] of storedViewsVisibilityStates) { - const viewState = this.viewStates.get(id); - if (viewState) { - if (viewState.visibleGlobal !== !state.isHidden) { - changedViews.push({ id, visible: !state.isHidden }); - } - } - } - if (changedViews.length) { - this.doSetVisible(changedViews); - } - } - } - - private saveViewsStates(): void { - this.saveWorkspaceViewsStates(); - this.saveGlobalViewsStates(); - } - - private saveWorkspaceViewsStates(): void { - const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); - for (const viewDescriptor of this.viewDescriptors) { - const viewState = this.viewStates.get(viewDescriptor.id); - if (viewState) { - storedViewsStates[viewDescriptor.id] = { - collapsed: !!viewState.collapsed, - isHidden: !viewState.visibleWorkspace, - size: viewState.size, - order: viewDescriptor.workspace && viewState ? viewState.order : undefined - }; - } - } - - if (Object.keys(storedViewsStates).length > 0) { - this.storageService.store(this.workspaceViewsStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE); - } else { - this.storageService.remove(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE); - } - } - - private saveGlobalViewsStates(): void { - const storedViewsVisibilityStates = PersistentViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); - for (const viewDescriptor of this.viewDescriptors) { - const viewState = this.viewStates.get(viewDescriptor.id); - storedViewsVisibilityStates.set(viewDescriptor.id, { - id: viewDescriptor.id, - isHidden: viewState && viewDescriptor.canToggleVisibility ? !viewState.visibleGlobal : false, - order: !viewDescriptor.workspace && viewState ? viewState.order : undefined - }); - } - this.globalViewsStatesValue = JSON.stringify(values(storedViewsVisibilityStates)); - } - - private _globalViewsStatesValue: string | undefined; - private get globalViewsStatesValue(): string { - if (!this._globalViewsStatesValue) { - this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); - } - - return this._globalViewsStatesValue; - } - - private set globalViewsStatesValue(globalViewsStatesValue: string) { - if (this.globalViewsStatesValue !== globalViewsStatesValue) { - this._globalViewsStatesValue = globalViewsStatesValue; - this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); - } - } - - private getStoredGlobalViewsStatesValue(): string { - return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); - } - - private setStoredGlobalViewsStatesValue(value: string): void { - this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); - } - - private static loadViewsStates(workspaceViewsStateStorageId: string, globalViewsStateStorageId: string, storageService: IStorageService): Map { - const viewStates = new Map(); - const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(storageService.get(workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); - for (const id of Object.keys(workspaceViewsStates)) { - const workspaceViewState = workspaceViewsStates[id]; - viewStates.set(id, { - visibleGlobal: undefined, - visibleWorkspace: isUndefined(workspaceViewState.isHidden) ? undefined : !workspaceViewState.isHidden, - collapsed: workspaceViewState.collapsed, - order: workspaceViewState.order, - size: workspaceViewState.size - }); - } - - // Migrate to `viewletStateStorageId` - const workspaceVisibilityStates = this.loadGlobalViewsState(globalViewsStateStorageId, storageService, StorageScope.WORKSPACE); - if (workspaceVisibilityStates.size > 0) { - for (const { id, isHidden } of values(workspaceVisibilityStates)) { - let viewState = viewStates.get(id); - // Not migrated to `viewletStateStorageId` - if (viewState) { - if (isUndefined(viewState.visibleWorkspace)) { - viewState.visibleWorkspace = !isHidden; - } - } else { - viewStates.set(id, { - collapsed: undefined, - visibleGlobal: undefined, - visibleWorkspace: !isHidden, - }); - } - } - storageService.remove(globalViewsStateStorageId, StorageScope.WORKSPACE); - } - - const globalViewsStates = this.loadGlobalViewsState(globalViewsStateStorageId, storageService, StorageScope.GLOBAL); - for (const { id, isHidden, order } of values(globalViewsStates)) { - let viewState = viewStates.get(id); - if (viewState) { - viewState.visibleGlobal = !isHidden; - if (!isUndefined(order)) { - viewState.order = order; - } - } else { - viewStates.set(id, { - visibleGlobal: !isHidden, - order, - collapsed: undefined, - visibleWorkspace: undefined, - }); - } - } - return viewStates; - } - - private static loadGlobalViewsState(globalViewsStateStorageId: string, storageService: IStorageService, scope: StorageScope): Map { - const storedValue = >JSON.parse(storageService.get(globalViewsStateStorageId, scope, '[]')); - let hasDuplicates = false; - const storedGlobalViewsState = storedValue.reduce((result, storedState) => { - if (typeof storedState === 'string' /* migration */) { - hasDuplicates = hasDuplicates || result.has(storedState); - result.set(storedState, { id: storedState, isHidden: true }); - } else { - hasDuplicates = hasDuplicates || result.has(storedState.id); - result.set(storedState.id, storedState); - } - return result; - }, new Map()); - - if (hasDuplicates) { - storageService.store(globalViewsStateStorageId, JSON.stringify(values(storedGlobalViewsState)), scope); - } - - return storedGlobalViewsState; - } -} diff --git a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts index ef72f7846c5..7b2f470bc49 100644 --- a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts @@ -11,6 +11,8 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService'; import { assertIsDefined } from 'vs/base/common/types'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); const sidebarContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testSidebar', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); @@ -22,6 +24,7 @@ suite('ViewDescriptorService', () => { setup(() => { const instantiationService: TestInstantiationService = workbenchInstantiationService(); + instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService)); viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); }); @@ -31,8 +34,8 @@ suite('ViewDescriptorService', () => { }); test('Empty Containers', function () { - const sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - const panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + const sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + const panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.allViewDescriptors.length, 0, 'The sidebar container should have no views yet.'); assert.equal(panelViews.allViewDescriptors.length, 0, 'The panel container should have no views yet.'); }); @@ -64,8 +67,8 @@ suite('ViewDescriptorService', () => { ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views'); assert.equal(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view'); @@ -74,8 +77,8 @@ suite('ViewDescriptorService', () => { ViewsRegistry.deregisterViews(viewDescriptors.slice(2), panelContainer); - sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 0, 'Sidebar should have no views'); assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have no views'); @@ -109,8 +112,8 @@ suite('ViewDescriptorService', () => { viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(2), sidebarContainer); viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(0, 2), panelContainer); - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); assert.equal(panelViews.activeViewDescriptors.length, 2, 'Panel should have 1 view'); @@ -148,8 +151,8 @@ suite('ViewDescriptorService', () => { viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar container should have 1 view'); assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel container should have no views'); @@ -169,8 +172,8 @@ suite('ViewDescriptorService', () => { viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); - sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view'); diff --git a/src/vs/workbench/services/views/test/browser/viewsModel.test.ts b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts index dd7e1c6c18f..e6d4fd83eab 100644 --- a/src/vs/workbench/services/views/test/browser/viewsModel.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts @@ -5,25 +5,31 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { IViewDescriptor } from 'vs/workbench/common/views'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewContainerModel, IViewDescriptorService, ViewContainer } from 'vs/workbench/common/views'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { IViewState, ViewDescriptorsModel, ViewsModel } from 'vs/workbench/services/views/common/viewsModel'; +import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; + +const ViewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); +const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { readonly elements: IViewDescriptor[]; private disposables: IDisposable[] = []; - constructor(model: ViewsModel) { + constructor(model: IViewContainerModel) { this.elements = [...model.visibleViewDescriptors]; - model.onDidAdd(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables); - model.onDidRemove(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables); - model.onDidMove(({ from, to }) => move(this.elements, from.index, to.index), null, this.disposables); + model.onDidAddVisibleViewDescriptors(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables); + model.onDidRemoveVisibleViewDescriptors(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables); + model.onDidMoveVisibleViewDescriptors(({ from, to }) => move(this.elements, from.index, to.index), null, this.disposables); } dispose() { @@ -31,29 +37,42 @@ class ViewDescriptorSequence { } } -suite('ContributableViewsModel', () => { +suite('ViewContainerModel', () => { + let container: ViewContainer; + let disposableStore: DisposableStore; let contextKeyService: IContextKeyService; + let viewDescriptorService: IViewDescriptorService; + let storageService: IStorageService; setup(() => { + disposableStore = new DisposableStore(); const instantiationService: TestInstantiationService = workbenchInstantiationService(); contextKeyService = instantiationService.createInstance(ContextKeyService); instantiationService.stub(IContextKeyService, contextKeyService); + storageService = instantiationService.get(IStorageService); + viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); + }); + + teardown(() => { + disposableStore.dispose(); + ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container); + ViewContainerRegistry.deregisterViewContainer(container); }); test('empty model', function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - assert.equal(model.visibleViewDescriptors.length, 0); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + assert.equal(testObject.visibleViewDescriptors.length, 0); }); test('register/unregister', () => { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -61,26 +80,25 @@ suite('ContributableViewsModel', () => { name: 'Test View 1' }; - viewsDescriptorsModel.addViews([viewDescriptor]); + ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 1); - assert.equal(seq.elements.length, 1); - assert.deepEqual(model.visibleViewDescriptors[0], viewDescriptor); - assert.deepEqual(seq.elements[0], viewDescriptor); + assert.equal(testObject.visibleViewDescriptors.length, 1); + assert.equal(target.elements.length, 1); + assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor); + assert.deepEqual(target.elements[0], viewDescriptor); - viewsDescriptorsModel.removeViews([viewDescriptor]); + ViewsRegistry.deregisterViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); }); test('when contexts', async function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -89,169 +107,164 @@ suite('ContributableViewsModel', () => { when: ContextKeyExpr.equals('showview1', true) }; - viewsDescriptorsModel.addViews([viewDescriptor]); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(seq.elements.length, 0); + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.equal(target.elements.length, 0); const key = contextKeyService.createKey('showview1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.equal(target.elements.length, 0); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear'); - assert.equal(seq.elements.length, 1); - assert.deepEqual(model.visibleViewDescriptors[0], viewDescriptor); - assert.equal(seq.elements[0], viewDescriptor); + assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear'); + assert.equal(target.elements.length, 1); + assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor); + assert.equal(target.elements[0], viewDescriptor); key.set(false); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear'); + assert.equal(target.elements.length, 0); - viewsDescriptorsModel.removeViews([viewDescriptor]); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(seq.elements.length, 0); + ViewsRegistry.deregisterViews([viewDescriptor], container); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); + assert.equal(target.elements.length, 0); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); + assert.equal(target.elements.length, 0); }); test('when contexts - multiple', async function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) }; - viewsDescriptorsModel.addViews([view1, view2]); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'only view1 should be visible'); - assert.deepEqual(seq.elements, [view1], 'only view1 should be visible'); + ViewsRegistry.registerViews([view1, view2], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'only view1 should be visible'); + assert.deepEqual(target.elements, [view1], 'only view1 should be visible'); const key = contextKeyService.createKey('showview2', false); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'still only view1 should be visible'); - assert.deepEqual(seq.elements, [view1], 'still only view1 should be visible'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'still only view1 should be visible'); + assert.deepEqual(target.elements, [view1], 'still only view1 should be visible'); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(seq.elements, [view1, view2], 'both views should be visible'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); - viewsDescriptorsModel.removeViews([view1, view2]); + ViewsRegistry.deregisterViews([view1, view2], container); }); test('when contexts - multiple 2', async function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; - viewsDescriptorsModel.addViews([view1, view2]); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'only view2 should be visible'); - assert.deepEqual(seq.elements, [view2], 'only view2 should be visible'); + ViewsRegistry.registerViews([view1, view2], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'only view2 should be visible'); + assert.deepEqual(target.elements, [view2], 'only view2 should be visible'); const key = contextKeyService.createKey('showview1', false); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'still only view2 should be visible'); - assert.deepEqual(seq.elements, [view2], 'still only view2 should be visible'); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'still only view2 should be visible'); + assert.deepEqual(target.elements, [view2], 'still only view2 should be visible'); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(seq.elements, [view1, view2], 'both views should be visible'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); - viewsDescriptorsModel.removeViews([view1, view2]); + ViewsRegistry.deregisterViews([view1, view2], container); }); test('setVisible', () => { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', canToggleVisibility: true }; const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3', canToggleVisibility: true }; - viewsDescriptorsModel.addViews([view1, view2, view3]); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3]); - assert.deepEqual(seq.elements, [view1, view2, view3]); + ViewsRegistry.registerViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3]); + assert.deepEqual(target.elements, [view1, view2, view3]); - model.setVisible('view2', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen'); - assert.deepEqual(seq.elements, [view1, view2, view3]); + testObject.setVisible('view2', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen'); + assert.deepEqual(target.elements, [view1, view2, view3]); - model.setVisible('view2', false); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view2 should hide'); - assert.deepEqual(seq.elements, [view1, view3]); + testObject.setVisible('view2', false); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view2 should hide'); + assert.deepEqual(target.elements, [view1, view3]); - model.setVisible('view1', false); - assert.deepEqual(model.visibleViewDescriptors, [view3], 'view1 should hide'); - assert.deepEqual(seq.elements, [view3]); + testObject.setVisible('view1', false); + assert.deepEqual(testObject.visibleViewDescriptors, [view3], 'view1 should hide'); + assert.deepEqual(target.elements, [view3]); - model.setVisible('view3', false); - assert.deepEqual(model.visibleViewDescriptors, [], 'view3 shoud hide'); - assert.deepEqual(seq.elements, []); + testObject.setVisible('view3', false); + assert.deepEqual(testObject.visibleViewDescriptors, [], 'view3 shoud hide'); + assert.deepEqual(target.elements, []); - model.setVisible('view1', true); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'view1 should show'); - assert.deepEqual(seq.elements, [view1]); + testObject.setVisible('view1', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'view1 should show'); + assert.deepEqual(target.elements, [view1]); - model.setVisible('view3', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view3 should show'); - assert.deepEqual(seq.elements, [view1, view3]); + testObject.setVisible('view3', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view3 should show'); + assert.deepEqual(target.elements, [view1, view3]); - model.setVisible('view2', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); - assert.deepEqual(seq.elements, [view1, view2, view3]); + testObject.setVisible('view2', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); + assert.deepEqual(target.elements, [view1, view2, view3]); - viewsDescriptorsModel.removeViews([view1, view2, view3]); - assert.deepEqual(model.visibleViewDescriptors, []); - assert.deepEqual(seq.elements, []); + ViewsRegistry.deregisterViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, []); + assert.deepEqual(target.elements, []); }); test('move', () => { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3' }; - viewsDescriptorsModel.addViews([view1, view2, view3]); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); - assert.deepEqual(seq.elements, [view1, view2, view3], 'sql views should be OK'); + ViewsRegistry.registerViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); + assert.deepEqual(target.elements, [view1, view2, view3], 'sql views should be OK'); - model.move('view3', 'view1'); - assert.deepEqual(model.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); - assert.deepEqual(seq.elements, [view3, view1, view2]); + testObject.move('view3', 'view1'); + assert.deepEqual(testObject.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); + assert.deepEqual(target.elements, [view3, view1, view2]); - model.move('view1', 'view2'); - assert.deepEqual(model.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); - assert.deepEqual(seq.elements, [view3, view2, view1]); + testObject.move('view1', 'view2'); + assert.deepEqual(testObject.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); + assert.deepEqual(target.elements, [view3, view2, view1]); - model.move('view1', 'view3'); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); - assert.deepEqual(seq.elements, [view1, view3, view2]); + testObject.move('view1', 'view3'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); + assert.deepEqual(target.elements, [view1, view3, view2]); - model.move('view2', 'view3'); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); - assert.deepEqual(seq.elements, [view1, view2, view3]); + testObject.move('view2', 'view3'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); + assert.deepEqual(target.elements, [view1, view2, view3]); }); test('view states', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel, viewStates); - const seq = new ViewDescriptorSequence(model); + storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -259,20 +272,19 @@ suite('ContributableViewsModel', () => { name: 'Test View 1' }; - viewsDescriptorsModel.addViews([viewDescriptor]); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); - assert.equal(seq.elements.length, 0); + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); + assert.equal(target.elements.length, 0); }); test('view states and when contexts', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel, viewStates); - const seq = new ViewDescriptorSequence(model); + storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -281,29 +293,28 @@ suite('ContributableViewsModel', () => { when: ContextKeyExpr.equals('showview1', true) }; - viewsDescriptorsModel.addViews([viewDescriptor]); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(seq.elements.length, 0); + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.equal(target.elements.length, 0); const key = contextKeyService.createKey('showview1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.equal(target.elements.length, 0); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); + assert.equal(target.elements.length, 0); }); test('view states and when contexts multiple views', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel, viewStates); - const seq = new ViewDescriptorSequence(model); + storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const view1: IViewDescriptor = { id: 'view1', @@ -323,30 +334,29 @@ suite('ContributableViewsModel', () => { when: ContextKeyExpr.equals('showview', true) }; - viewsDescriptorsModel.addViews([view1, view2, view3]); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); + ViewsRegistry.registerViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(target.elements, [view2]); const key = contextKeyService.createKey('showview', false); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(target.elements, [view2]); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); - assert.deepEqual(seq.elements, [view2, view3]); + assert.deepEqual(testObject.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); + assert.deepEqual(target.elements, [view2, view3]); key.set(false); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(target.elements, [view2]); }); test('remove event is not triggered if view was hidden and removed', async function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const viewDescriptor: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, @@ -355,21 +365,21 @@ suite('ContributableViewsModel', () => { canToggleVisibility: true }; - viewsDescriptorsModel.addViews([viewDescriptor]); + ViewsRegistry.registerViews([viewDescriptor], container); const key = contextKeyService.createKey('showview1', true); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear after context is set'); - assert.equal(seq.elements.length, 1); + assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear after context is set'); + assert.equal(target.elements.length, 1); - model.setVisible('view1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); - assert.equal(seq.elements.length, 0); + testObject.setVisible('view1', false); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); + assert.equal(target.elements.length, 0); - const target = sinon.spy(model.onDidRemove); + const targetEvent = sinon.spy(testObject.onDidRemoveVisibleViewDescriptors); key.set(false); await new Promise(c => setTimeout(c, 30)); - assert.ok(!target.called, 'remove event should not be called since it is already hidden'); + assert.ok(!targetEvent.called, 'remove event should not be called since it is already hidden'); }); }); -- GitLab