diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index b6bf77177b8..1963845b781 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -451,7 +451,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: COMMENTS_VIEW_ID, name: COMMENTS_VIEW_TITLE, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: COMMENTS_VIEW_TITLE, hideIfEmpty: true, order: 10, }, ViewContainerLocation.Panel); diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 354fbf83287..7a0e83d8ba8 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -320,7 +320,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { name: title, extensionId, ctorDescriptor: new SyncDescriptor( ViewPaneContainer, - [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }] + [id, { mergeViewWithContainerWhenSingleView: true }] ), hideIfEmpty: true, order, 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 add40709ef9..283444ae5a3 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'; @@ -216,8 +216,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 } } @@ -478,9 +478,14 @@ 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); - this.viewletDisposables.set(viewlet.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty))); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + this.onDidChangeActiveViews(viewlet, viewContainerModel, viewContainer.hideIfEmpty); + + const disposables = new DisposableStore(); + disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewlet, viewContainerModel, viewContainer.hideIfEmpty))); + disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewlet, viewContainerModel))); + + this.viewletDisposables.set(viewlet.id, disposables); } } @@ -494,17 +499,14 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.hideComposite(viewletId); } - private updateActivity(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { - const viewDescriptor = viewDescriptors.activeViewDescriptors[0]; - - // Use the viewlet icon if any view inside belongs to it statically - const shouldUseViewletIcon = viewDescriptors.allViewDescriptors.some(v => this.viewDescriptorService.getDefaultContainer(v.id)?.id === viewlet.id); + private updateActivity(viewlet: ViewletDescriptor, viewContainerModel: IViewContainerModel): void { + const icon = viewContainerModel.icon; const activity: IActivity = { id: viewlet.id, - name: shouldUseViewletIcon ? viewlet.name : viewDescriptor.name, - cssClass: shouldUseViewletIcon ? viewlet.cssClass : (isString(viewDescriptor.containerIcon) ? viewDescriptor.containerIcon : (viewDescriptor.containerIcon === undefined ? 'codicon-window' : undefined)), - iconUrl: shouldUseViewletIcon ? viewlet.iconUrl : (viewDescriptor.containerIcon instanceof URI ? viewDescriptor.containerIcon : undefined), + name: viewContainerModel.title, + cssClass: isString(icon) ? icon : undefined, + iconUrl: icon instanceof URI ? icon : undefined, keybindingId: viewlet.keybindingId }; @@ -516,7 +518,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - 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); @@ -644,8 +646,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/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 969d0712b2f..52af0a4bcde 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -161,9 +161,11 @@ export class ActivityActionViewItem extends BaseActionViewItem { if (this.activity.iconUrl) { // Apply background color to activity bar item provided with iconUrls this.label.style.backgroundColor = foreground ? foreground.toString() : ''; + this.label.style.color = ''; } else { // Apply foreground color to activity bar items provided with codicons this.label.style.color = foreground ? foreground.toString() : ''; + this.label.style.backgroundColor = ''; } const dragColor = colors.activeBackgroundColor || colors.activeForegroundColor; @@ -691,9 +693,4 @@ export class ToggleCompositePinnedAction extends Action { this.compositeBar.pin(id); } } - - setActivity(activity: IActivity): void { - this.activity = activity; - this.label = activity.name; - } } diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 831406fd15f..1501c87bcce 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -200,6 +200,10 @@ export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinne constructor(id: string, compositeBar: ICompositeBar) { super({ id, name: id, cssClass: undefined }, compositeBar); } + + setActivity(activity: IActivity): void { + this.label = activity.name; + } } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 9001b38a9e0..e9713d8aab2 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -33,12 +33,13 @@ 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'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { Before2D } from 'vs/workbench/browser/dnd'; +import { IActivity } from 'vs/workbench/common/activity'; interface ICachedPanel { id: string; @@ -177,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(); } @@ -209,12 +210,16 @@ export class PanelPart extends CompositePart implements IPanelService { for (const panel of panels) { this.enableCompositeActions(panel); - const viewContainer = this.getViewContainer(panel.id); - if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onDidChangeActiveViews(panel, viewDescriptors); - this.panelDisposables.set(panel.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors))); - } + const viewContainer = this.getViewContainer(panel.id)!; + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + this.onDidChangeActiveViews(panel, viewContainerModel, viewContainer.hideIfEmpty); + + const disposables = new DisposableStore(); + 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); } } @@ -230,19 +235,37 @@ export class PanelPart extends CompositePart implements IPanelService { private enableCompositeActions(panel: PanelDescriptor): void { const { activityAction, pinnedAction } = this.getCompositeActions(panel.id); - if (activityAction instanceof PlaceHolderPanelActivityAction) { - activityAction.setActivity(panel); - } + activityAction.setActivity(panel); if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { pinnedAction.setActivity(panel); } } - private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + private updateActivity(panel: PanelDescriptor, viewContainerModel: IViewContainerModel): void { + const activity: IActivity = { + id: panel.id, + name: viewContainerModel.title, + keybindingId: panel.keybindingId + }; + + const { activityAction, pinnedAction } = this.getCompositeActions(panel.id); + activityAction.setActivity(activity); + + if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { + pinnedAction.setActivity(activity); + } + } + + private onDidUpdateViews(panel: PanelDescriptor, viewDescriptors: IViewContainerModel): void { + this.updateActivity(panel, viewDescriptors); + } + + private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewContainerModel, hideIfEmpty?: boolean): void { if (viewDescriptors.activeViewDescriptors.length) { + this.updateActivity(panel, viewDescriptors); this.compositeBar.addComposite(panel); - } else { + } else if (hideIfEmpty) { this.hideComposite(panel.id); } } @@ -320,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 } } @@ -540,28 +563,6 @@ export class PanelPart extends CompositePart implements IPanelService { return false; } - protected onTitleAreaUpdate(compositeId: string): void { - super.onTitleAreaUpdate(compositeId); - - const activePanel = this.getActivePanel(); - const panel = this.createComposite(compositeId, activePanel?.getId() === compositeId); - - if (panel) { - const compositeActions = this.compositeActions.get(compositeId); - if (compositeActions) { - compositeActions.activityAction.setActivity({ - id: compositeActions.activityAction.id, - name: panel.getTitle() || compositeActions.activityAction.label - }); - - compositeActions.pinnedAction.setActivity({ - id: compositeActions.activityAction.id, - name: panel.getTitle() || compositeActions.activityAction.label - }); - } - } - } - private getToolbarWidth(): number { const activePanel = this.getActivePanel(); if (!activePanel || !this.toolBar) { @@ -609,8 +610,11 @@ export class PanelPart extends CompositePart implements IPanelService { const compositeItems = this.compositeBar.getCompositeBarItems(); for (const compositeItem of compositeItems) { - const activityAction = this.getCompositeActions(compositeItem.id).activityAction; - state.push({ id: compositeItem.id, name: activityAction.label, 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 7e771ce718f..16c7bcdd413 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -24,11 +24,10 @@ 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 } 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'; -import { PersistentContributableViewsModel, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -746,7 +745,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private readonly visibleViewsCountFromCache: number | undefined; private readonly visibleViewsStorageId: string; - protected readonly viewsModel: PersistentContributableViewsModel; + protected readonly viewContainerModel: IViewContainerModel; private viewDisposables: IDisposable[] = []; private readonly _onTitleAreaUpdate: Emitter = this._register(new Emitter()); @@ -782,7 +781,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { constructor( id: string, - viewPaneContainerStateStorageId: string, private options: IViewPaneContainerOptions, @IInstantiationService protected instantiationService: IInstantiationService, @IConfigurationService protected configurationService: IConfigurationService, @@ -808,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.viewsModel = this._register(this.instantiationService.createInstance(PersistentContributableViewsModel, container, viewPaneContainerStateStorageId)); + this.viewContainerModel = this.viewDescriptorService.getViewContainerModel(container); } create(parent: HTMLElement): void { @@ -839,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); @@ -861,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); } @@ -884,11 +882,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { })); this._register(this.onDidSashChange(() => this.saveViewSizes())); - this.viewsModel.onDidAdd(added => this.onDidAddViewDescriptors(added)); - this.viewsModel.onDidRemove(removed => this.onDidRemoveViewDescriptors(removed)); - const addedViews: IAddedViewDescriptorRef[] = this.viewsModel.visibleViewDescriptors.map((viewDescriptor, index) => { - const size = this.viewsModel.getSize(viewDescriptor.id); - const collapsed = this.viewsModel.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) { @@ -906,9 +904,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getTitle(): string { - // use registered title if any of our panes are statically registered to this container - const allViewDescriptors = this.viewDescriptorService.getViewDescriptors(this.viewContainer).allViewDescriptors; - const containerTitle = this.paneItems.length === 0 || allViewDescriptors.length === 0 || allViewDescriptors.some(v => this.viewDescriptorService.getDefaultContainer(v.id) === this.viewContainer) ? this.viewContainer.name : this.paneItems[0].pane.title; + const containerTitle = this.viewContainerModel.title; if (this.isViewMergedWithContainer()) { const paneItemTitle = this.paneItems[0].pane.title; @@ -959,10 +955,10 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } - const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => ({ + const viewToggleActions = this.viewContainerModel.activeViewDescriptors.map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, - checked: this.viewsModel.isVisible(viewDescriptor.id), + checked: this.viewContainerModel.isVisible(viewDescriptor.id), enabled: viewDescriptor.canToggleVisibility, run: () => this.toggleViewVisibility(viewDescriptor.id) })); @@ -1091,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.viewsModel.setSize(view.id, this.getPaneSize(view)); + this.viewContainerModel.setSize(view.id, this.getPaneSize(view)); } } } @@ -1100,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.viewsModel.visibleViewDescriptors.length; i++) { + for (let i = 0; i < this.viewContainerModel.visibleViewDescriptors.length; i++) { const pane = this.panes[i]; - const viewDescriptor = this.viewsModel.visibleViewDescriptors[i]; - const size = this.viewsModel.getSize(viewDescriptor.id); + const viewDescriptor = this.viewContainerModel.visibleViewDescriptors[i]; + const size = this.viewContainerModel.getSize(viewDescriptor.id); if (typeof size === 'number') { this.resizePane(pane, size); @@ -1118,8 +1114,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private computeInitialSizes(): Map { const sizes: Map = new Map(); if (this.dimension) { - const totalWeight = this.viewsModel.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0); - for (const viewDescriptor of this.viewsModel.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 { @@ -1180,7 +1176,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { }); const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => { - this.viewsModel.setCollapsed(viewDescriptor.id, collapsed); + this.viewContainerModel.setCollapsed(viewDescriptor.id, collapsed); }); this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable)); @@ -1211,13 +1207,13 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } protected toggleViewVisibility(viewId: string): void { - const visible = !this.viewsModel.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.viewsModel.setVisible(viewId, visible); + this.viewContainerModel.setVisible(viewId, visible); } private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { @@ -1272,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); @@ -1294,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); @@ -1409,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.viewsModel.visibleViewDescriptors[fromIndex]; - const toViewDescriptor = this.viewsModel.visibleViewDescriptors[toIndex]; + const fromViewDescriptor = this.viewContainerModel.visibleViewDescriptors[fromIndex]; + const toViewDescriptor = this.viewContainerModel.visibleViewDescriptors[toIndex]; if (fromIndex < 0 || fromIndex >= this.paneItems.length) { return; @@ -1425,7 +1421,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { assertIsDefined(this.paneview).movePane(from, to); - this.viewsModel.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 42ae7b7deda..d9e0c6e66a3 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -7,16 +7,14 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, ViewContainerLocation, IViewsService, IViewPaneContainer, getVisbileViewContextKey } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; -import { firstIndex, move } from 'vs/base/common/arrays'; -import { isUndefinedOrNull, isUndefined, isString } from 'vs/base/common/types'; +import { isString } from 'vs/base/common/types'; import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { values } from 'vs/base/common/map'; import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; @@ -34,488 +32,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { URI } from 'vs/base/common/uri'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; - -export interface IViewState { - visibleGlobal: boolean | undefined; - visibleWorkspace: boolean | undefined; - collapsed: boolean | undefined; - order?: number; - size?: number; -} - -export interface IViewDescriptorRef { - viewDescriptor: IViewDescriptor; - index: number; -} - -export interface IAddedViewDescriptorRef extends IViewDescriptorRef { - collapsed: boolean; - size?: number; -} - -export class ContributableViewsModel extends Disposable { - - private _viewDescriptors: IViewDescriptor[] = []; - get viewDescriptors(): ReadonlyArray { - return this._viewDescriptors; - } - - get visibleViewDescriptors(): IViewDescriptor[] { - return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)); - } - - 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( - container: ViewContainer, - viewsService: IViewDescriptorService, - protected viewStates = new Map(), - ) { - super(); - const viewDescriptorCollection = viewsService.getViewDescriptors(container); - 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 PersistentContributableViewsModel extends ContributableViewsModel { - - private readonly workspaceViewsStateStorageId: string; - private readonly globalViewsStateStorageId: string; - - private storageService: IStorageService; - - constructor( - container: ViewContainer, - viewletStateStorageId: string, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IStorageService storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService - ) { - const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; - storageKeysSyncRegistryService.registerStorageKey({ key: globalViewsStateStorageId, version: 1 }); - const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); - - super(container, viewDescriptorService, 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 = PersistentContributableViewsModel.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 = PersistentContributableViewsModel.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; - } -} export class ViewsService extends Disposable implements IViewsService { @@ -593,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; @@ -665,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 8588ffe9aac..8335c7352c3 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -6,7 +6,7 @@ import { IAction } from 'vs/base/common/actions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -16,7 +16,6 @@ import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/brow import { Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; export interface IViewletViewOptions extends IViewPaneOptions { } @@ -41,14 +40,14 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(viewletId, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this._register(onDidChangeFilterValue(newFilterValue => { this.filterValue = newFilterValue; this.onFilterChanged(newFilterValue); })); - this._register(this.viewsModel.onDidChangeActiveViews((viewDescriptors) => { - this.updateAllViews(viewDescriptors); + this._register(this.viewContainerModel.onDidChangeActiveViewDescriptors(() => { + this.updateAllViews(this.viewContainerModel.activeViewDescriptors); })); } @@ -63,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.viewsModel.setVisible(descriptor.id, false); + this.viewContainerModel.setVisible(descriptor.id, false); } }); } @@ -76,17 +75,17 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { private onFilterChanged(newFilterValue: string[]) { if (this.allViews.size === 0) { - this.updateAllViews(this.viewsModel.viewDescriptors); + this.updateAllViews(this.viewContainerModel.activeViewDescriptors); } - this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, false)); - this.getViewsForTarget(newFilterValue).forEach(item => this.viewsModel.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.viewsModel.isVisible(viewDescriptor.id), + checked: this.viewContainerModel.isVisible(viewDescriptor.id), enabled: viewDescriptor.canToggleVisibility, run: () => this.toggleViewVisibility(viewDescriptor.id) })); @@ -134,7 +133,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { } // Check that allViews is ready if (this.allViews.size === 0) { - this.updateAllViews(this.viewsModel.viewDescriptors); + this.updateAllViews(this.viewContainerModel.activeViewDescriptors); } return panes; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index c303cd3c1ab..dbdcdccdf92 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -43,6 +43,8 @@ export interface IViewContainerDescriptor { readonly ctorDescriptor: SyncDescriptor; + readonly storageId?: string; + readonly icon?: string | URI; readonly order?: number; @@ -207,11 +209,43 @@ export interface IViewDescriptor { readonly remoteAuthority?: string | string[]; } -export interface IViewDescriptorCollection extends IDisposable { - readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; - readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; - readonly activeViewDescriptors: IViewDescriptor[]; - readonly allViewDescriptors: IViewDescriptor[]; +export interface IViewDescriptorRef { + viewDescriptor: IViewDescriptor; + index: number; +} + +export interface IAddedViewDescriptorRef extends IViewDescriptorRef { + collapsed: boolean; + size?: number; +} + +export interface IViewContainerModel { + + readonly title: string; + readonly icon: string | URI | undefined; + readonly onDidChangeContainerInfo: Event<{ title?: boolean, icon?: boolean }>; + + readonly allViewDescriptors: ReadonlyArray; + readonly onDidChangeAllViewDescriptors: Event<{ added: ReadonlyArray, removed: ReadonlyArray }>; + + 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; + + isCollapsed(id: string): boolean; + setCollapsed(id: string, collapsed: boolean): void; + + getSize(id: string): number | undefined; + setSize(id: string, size: number): void + + move(from: string, to: string): void; } export enum ViewContentPriority { @@ -451,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/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index faf9b538534..289ee656910 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -352,8 +352,9 @@ const container = Registry.as(ViewContainerExtensions.V hideIfEmpty: true, ctorDescriptor: new SyncDescriptor( ViewPaneContainer, - [BulkEditPane.ID, BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] - ) + [BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] + ), + storageId: BulkEditPane.ID }, ViewContainerLocation.Panel); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 5255e0e4083..e9c6fb869bb 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -90,7 +90,8 @@ const openPanelKb: IKeybindings = { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: DEBUG_PANEL_ID, name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: DEBUG_PANEL_ID, focusCommand: { id: OpenDebugConsoleAction.ID, keybindings: openPanelKb diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index e80f6f0670b..ca458d7b809 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -62,7 +62,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { @INotificationService private readonly notificationService: INotificationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 0311e3ce1b1..f1ff8e2206e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -34,7 +34,7 @@ import Severity from 'vs/base/common/severity'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -44,7 +44,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; @@ -359,7 +358,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IPreferencesService private readonly preferencesService: IPreferencesService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.searchDelayer = new Delayer(500); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 559404a96fc..368b7ce006c 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -160,8 +160,6 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor export class ExplorerViewPaneContainer extends ViewPaneContainer { - private static readonly EXPLORER_VIEWS_STATE = 'workbench.explorer.views.state'; - private viewletVisibleContextKey: IContextKey; constructor( @@ -179,7 +177,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService); @@ -262,6 +260,7 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, hideIfEmpty: true, order: 0, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, Constants.MARKERS_VIEW_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: Constants.MARKERS_VIEW_STORAGE_ID, focusCommand: { id: ToggleMarkersPanelAction.ID, keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index b9961b11297..d075c22645d 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -62,7 +62,8 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC id: OUTPUT_VIEW_ID, name: nls.localize('output', "Output"), order: 1, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: OUTPUT_VIEW_ID, hideIfEmpty: true, focusCommand: { id: toggleOutputAcitonId, keybindings: toggleOutputActionKeybindings } }, ViewContainerLocation.Panel); 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/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 233d83deff8..476a5acab39 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -19,7 +19,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { VIEWLET_ID } from 'vs/workbench/contrib/remote/common/remote.contribution'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -46,7 +46,6 @@ import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remo import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { startsWith } from 'vs/base/common/strings'; import { TunnelPanelDescriptor, TunnelViewModel, forwardedPortsViewEnabled } from 'vs/workbench/contrib/remote/browser/tunnelView'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index ca87f4502b4..e23716150df 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -51,6 +51,7 @@ Registry.as(ViewContainerExtensions.ViewContainersRegis id: VIEWLET_ID, name: localize('source control', "Source Control"), ctorDescriptor: new SyncDescriptor(SCMViewPaneContainer), + storageId: 'workbench.scm.views.state', icon: 'codicon-source-control', order: 2 }, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index e60545de4a6..5de61ced71d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -24,12 +24,11 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewsRegistry, Extensions, IViewDescriptorService, IViewDescriptor } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions, IViewDescriptorService, IViewDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; import { MainPaneDescriptor, MainPane, IViewModel } from 'vs/workbench/contrib/scm/browser/mainPane'; import { ViewPaneContainer, IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import type { IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { debounce } from 'vs/base/common/decorators'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -80,8 +79,6 @@ export class EmptyPaneDescriptor implements IViewDescriptor { export class SCMViewPaneContainer extends ViewPaneContainer implements IViewModel { - private static readonly STATE_KEY = 'workbench.scm.views.state'; - private menus: SCMMenus; private _repositories: ISCMRepository[] = []; @@ -104,7 +101,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } get onDidChangeVisibleRepositories(): Event { - const modificationEvent = Event.debounce(Event.any(this.viewsModel.onDidAdd, this.viewsModel.onDidRemove), () => null, 0); + const modificationEvent = Event.debounce(Event.any(this.viewContainerModel.onDidAddVisibleViewDescriptors, this.viewContainerModel.onDidRemoveVisibleViewDescriptors), () => null, 0); return Event.map(modificationEvent, () => this.visibleRepositories); } @@ -126,7 +123,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.menus = instantiationService.createInstance(SCMMenus, undefined); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); @@ -143,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.viewsModel.setVisible(MainPane.ID, true); + this.viewContainerModel.setVisible(MainPane.ID, true); } })); this.repositoryCountKey = contextKeyService.createKey('scm.providerCount', 0); - this._register(this.viewsModel.onDidAdd(this.onDidShowView, this)); - this._register(this.viewsModel.onDidRemove(this.onDidHideView, this)); + this._register(this.viewContainerModel.onDidAddVisibleViewDescriptors(this.onDidShowView, this)); + this._register(this.viewContainerModel.onDidRemoveVisibleViewDescriptors(this.onDidHideView, this)); } create(parent: HTMLElement): void { @@ -217,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.viewsModel.isVisible(d.id))) { - this.viewsModel.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); } } @@ -285,9 +282,9 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } setVisibleRepositories(repositories: ISCMRepository[]): void { - const visibleViewDescriptors = this.viewsModel.visibleViewDescriptors; + const visibleViewDescriptors = this.viewContainerModel.visibleViewDescriptors; - const toSetVisible = this.viewsModel.viewDescriptors + 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 @@ -305,11 +302,11 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } } - this.viewsModel.setVisible(viewDescriptor.id, false); + this.viewContainerModel.setVisible(viewDescriptor.id, false); } for (const viewDescriptor of toSetVisible) { - this.viewsModel.setVisible(viewDescriptor.id, true, size); + this.viewContainerModel.setVisible(viewDescriptor.id, true, size); } } } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 32e4459d2a3..c010c0dd7a6 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -504,7 +504,7 @@ class ShowAllSymbolsAction extends Action { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('name', "Search"), - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), hideIfEmpty: true, icon: 'codicon-search', order: 1 diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 0b5b7d0fcaf..e842ee3aa16 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -68,7 +68,8 @@ if (platform.isWeb) { const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: TERMINAL_VIEW_ID, name: nls.localize('terminal', "Terminal"), - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: TERMINAL_VIEW_ID, focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS }, hideIfEmpty: true, order: 3 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/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index ba6877133d5..7f2c4f35174 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -39,7 +39,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { name: localize('sync preferences', "Preferences Sync"), ctorDescriptor: new SyncDescriptor( ViewPaneContainer, - ['workbench.view.sync', `workbench.view.sync.state`, { mergeViewWithContainerWhenSingleView: true }] + ['workbench.view.sync', { mergeViewWithContainerWhenSingleView: true }] ), icon: 'codicon-sync', hideIfEmpty: true, diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 7c52f852fdf..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 } from 'vs/workbench/common/views'; -import { IContextKey, RawContextKey, IContextKeyService, IReadableSet, IContextKeyChangeEvent } 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'; @@ -13,10 +13,15 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; -import { firstIndex } from 'vs/base/common/arrays'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +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 CounterSet implements IReadableSet { @@ -50,49 +55,419 @@ class CounterSet implements IReadableSet { } } -interface IViewItem { - viewDescriptor: IViewDescriptor; - active: boolean; +interface IStoredWorkspaceViewState { + collapsed: boolean; + isHidden: boolean; + size?: number; + order?: number; } -class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection { +interface IStoredGlobalViewState { + id: string; + isHidden: boolean; + order?: number; +} - private contextKeys = new CounterSet(); - private items: IViewItem[] = []; +interface IViewDescriptorState { + visibleGlobal: boolean | undefined; + visibleWorkspace: boolean | undefined; + collapsed: boolean | undefined; + active: boolean + order?: number; + size?: number; +} - 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; +class ViewDescriptorsState extends Disposable { - 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; + private readonly workspaceViewsStateStorageId: string; + private readonly globalViewsStateStorageId: string; + private readonly state: Map; - get activeViewDescriptors(): IViewDescriptor[] { - return this.items - .filter(i => i.active) - .map(i => i.viewDescriptor); - } - - get allViewDescriptors(): IViewDescriptor[] { - return this.items.map(i => i.viewDescriptor); - } + 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._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(this.onContextChanged, this)); + + 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(); } - addViews(viewDescriptors: IViewDescriptor[]): void { - const added: IViewDescriptor[] = []; + 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) { - 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()) { @@ -100,74 +475,187 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } } - if (item.active) { - added.push(viewDescriptor); + 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._onDidChangeViews.fire({ added: viewDescriptors, removed: [] }); + this.viewDescriptorItems.push(...addedItems); + this.viewDescriptorItems.sort(this.compareViewDescriptors.bind(this)); - if (added.length) { - this._onDidChangeActiveViews.fire({ added, removed: [] }); + 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); } } - removeViews(viewDescriptors: IViewDescriptor[]): void { + remove(viewDescriptors: IViewDescriptor[]): void { const removed: IViewDescriptor[] = []; + const removedItems: IViewDescriptorItem[] = []; + const removedActiveDescriptors: IViewDescriptor[] = []; + const removedVisibleItems: { index: number, viewDescriptor: 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) { + 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); } } - this._onDidChangeViews.fire({ added: [], removed: viewDescriptors }); + removedItems.forEach(item => this.viewDescriptorItems.splice(this.viewDescriptorItems.indexOf(item), 1)); - if (removed.length) { - this._onDidChangeActiveViews.fire({ added: [], removed }); + this._onDidChangeAllViewDescriptors.fire({ added: [], removed }); + if (removedActiveDescriptors.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: [], removed: removedActiveDescriptors })); + } + if (removedVisibleItems.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems); } } - private onContextChanged(event: IContextKeyChangeEvent): void { - const removed: IViewDescriptor[] = []; - const added: IViewDescriptor[] = []; + 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.items) { - const active = this.isViewDescriptorActive(item.viewDescriptor); - - if (item.active !== active) { - if (active) { - added.push(item.viewDescriptor); + 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 { - removed.push(item.viewDescriptor); + removedActiveItems.push({ item, wasVisible }); } } - - item.active = active; } - if (added.length || removed.length) { - this._onDidChangeActiveViews.fire({ added, removed }); + 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); + + 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 (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); } } - private isViewDescriptorActive(viewDescriptor: IViewDescriptor): boolean { - return !viewDescriptor.when || this.contextKeyService.contextMatchesRules(viewDescriptor.when); + private isViewDescriptorVisible(viewDescriptorItem: IViewDescriptorItem): boolean { + if (!viewDescriptorItem.state.active) { + return false; + } + return this.isViewDescriptorVisibleWhenActive(viewDescriptorItem); + } + + private isViewDescriptorVisibleWhenActive(viewDescriptorItem: IViewDescriptorItem): boolean { + if (viewDescriptorItem.viewDescriptor.workspace) { + return !!viewDescriptorItem.state.visibleWorkspace; + } + return !!viewDescriptorItem.state.visibleGlobal; + } + + 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); + } + + 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; + } + + if (a.group === b.group) { + return 0; + } + + return a.group < b.group ? -1 : 1; } } @@ -189,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>; @@ -216,6 +704,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @@ -225,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>(); @@ -249,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); })); @@ -265,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; @@ -287,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; } @@ -410,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 { @@ -492,9 +981,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.viewContainersRegistry.registerViewContainer({ id, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), name: 'Custom Views', // we don't want to see this, so no need to localize icon: location === ViewContainerLocation.Sidebar ? 'codicon-window' : undefined, + storageId: `${id}.state`, hideIfEmpty: true }, location); } @@ -544,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); @@ -580,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, @@ -628,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(new ViewDescriptorCollection(this.contextKeyService)); + 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) { @@ -650,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)); } @@ -674,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 { @@ -682,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/test/browser/viewDescriptorService.test.ts b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts new file mode 100644 index 00000000000..7b2f470bc49 --- /dev/null +++ b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts @@ -0,0 +1,268 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +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); +const panelContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testPanel', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Panel); + +suite('ViewDescriptorService', () => { + + let viewDescriptorService: IViewDescriptorService; + + setup(() => { + const instantiationService: TestInstantiationService = workbenchInstantiationService(); + instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService)); + viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); + }); + + teardown(() => { + ViewsRegistry.deregisterViews(ViewsRegistry.getViews(sidebarContainer), sidebarContainer); + ViewsRegistry.deregisterViews(ViewsRegistry.getViews(panelContainer), panelContainer); + }); + + test('Empty Containers', function () { + 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.'); + }); + + test('Register/Deregister', () => { + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + } + ]; + + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(2), 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'); + + ViewsRegistry.deregisterViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.deregisterViews(viewDescriptors.slice(2), 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'); + }); + + test('move views to existing containers', async function () { + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + } + ]; + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); + + viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(2), sidebarContainer); + viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(0, 2), 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'); + + assert.notEqual(sidebarViews.activeViewDescriptors.indexOf(viewDescriptors[2]), -1, `Sidebar should have ${viewDescriptors[2].name}`); + assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[0]), -1, `Panel should have ${viewDescriptors[0].name}`); + assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[1]), -1, `Panel should have ${viewDescriptors[1].name}`); + }); + + test('move views to generated containers', async function () { + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + } + ]; + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); + + viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); + viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); + + 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'); + + const generatedPanel = assertIsDefined(viewDescriptorService.getViewContainer(viewDescriptors[0].id)); + const generatedSidebar = assertIsDefined(viewDescriptorService.getViewContainer(viewDescriptors[2].id)); + + assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel'); + assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar'); + + assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), viewDescriptorService.getViewLocation(viewDescriptors[0].id), 'Panel view location and container location should match'); + assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), viewDescriptorService.getViewLocation(viewDescriptors[2].id), 'Sidebar view location and container location should match'); + + assert.equal(viewDescriptorService.getDefaultContainer(viewDescriptors[2].id), panelContainer, `${viewDescriptors[2].name} has wrong default container`); + assert.equal(viewDescriptorService.getDefaultContainer(viewDescriptors[0].id), sidebarContainer, `${viewDescriptors[0].name} has wrong default container`); + + viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); + viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); + + 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'); + + assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar'); + assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel'); + }); + + test('move view events', async function () { + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + } + ]; + + + let expectedSequence = ''; + let actualSequence = ''; + const disposables = []; + + const containerMoveString = (view: IViewDescriptor, from: ViewContainer, to: ViewContainer) => { + return `Moved ${view.id} from ${from.id} to ${to.id}\n`; + }; + + const locationMoveString = (view: IViewDescriptor, from: ViewContainerLocation, to: ViewContainerLocation) => { + return `Moved ${view.id} from ${from === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'} to ${to === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'}\n`; + }; + disposables.push(viewDescriptorService.onDidChangeContainer(({ views, from, to }) => { + views.forEach(view => { + actualSequence += containerMoveString(view, from, to); + }); + })); + + disposables.push(viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + views.forEach(view => { + actualSequence += locationMoveString(view, from, to); + }); + })); + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); + + expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); + expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, viewDescriptorService.getViewContainer(viewDescriptors[0].id)!); + + expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); + viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); + expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, viewDescriptorService.getViewContainer(viewDescriptors[2].id)!); + + + expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); + expectedSequence += containerMoveString(viewDescriptors[0], viewDescriptorService.getViewContainer(viewDescriptors[0].id)!, sidebarContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], sidebarContainer); + + expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + expectedSequence += containerMoveString(viewDescriptors[2], viewDescriptorService.getViewContainer(viewDescriptors[2].id)!, panelContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], panelContainer); + + expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, panelContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], panelContainer); + + expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); + expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, sidebarContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], sidebarContainer); + + expectedSequence += locationMoveString(viewDescriptors[1], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + expectedSequence += containerMoveString(viewDescriptors[1], sidebarContainer, panelContainer); + expectedSequence += containerMoveString(viewDescriptors[2], sidebarContainer, panelContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[1], viewDescriptors[2]], panelContainer); + + assert.equal(actualSequence, expectedSequence, 'Event sequence not matching expected sequence'); + }); + +}); diff --git a/src/vs/workbench/services/views/test/browser/viewsModel.test.ts b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts new file mode 100644 index 00000000000..e6d4fd83eab --- /dev/null +++ b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts @@ -0,0 +1,385 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +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 { 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: IViewContainerModel) { + this.elements = [...model.visibleViewDescriptors]; + 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() { + this.disposables = dispose(this.disposables); + } +} + +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 () { + 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', () => { + 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', + ctorDescriptor: null!, + name: 'Test View 1' + }; + + ViewsRegistry.registerViews([viewDescriptor], container); + + assert.equal(testObject.visibleViewDescriptors.length, 1); + assert.equal(target.elements.length, 1); + assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor); + assert.deepEqual(target.elements[0], viewDescriptor); + + ViewsRegistry.deregisterViews([viewDescriptor], container); + + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + }); + + test('when contexts', async function () { + 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', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true) + }; + + 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(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(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(testObject.visibleViewDescriptors.length, 0, 'view should disappear'); + assert.equal(target.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(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); + assert.equal(target.elements.length, 0); + }); + + test('when contexts - multiple', async function () { + 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) }; + + 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(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(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); + + ViewsRegistry.deregisterViews([view1, view2], container); + }); + + test('when contexts - multiple 2', async function () { + 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' }; + + 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(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(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); + + ViewsRegistry.deregisterViews([view1, view2], container); + }); + + test('setVisible', () => { + 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 }; + + ViewsRegistry.registerViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3]); + assert.deepEqual(target.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]); + + testObject.setVisible('view2', false); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view2 should hide'); + assert.deepEqual(target.elements, [view1, view3]); + + testObject.setVisible('view1', false); + assert.deepEqual(testObject.visibleViewDescriptors, [view3], 'view1 should hide'); + assert.deepEqual(target.elements, [view3]); + + testObject.setVisible('view3', false); + assert.deepEqual(testObject.visibleViewDescriptors, [], 'view3 shoud hide'); + assert.deepEqual(target.elements, []); + + testObject.setVisible('view1', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'view1 should show'); + assert.deepEqual(target.elements, [view1]); + + testObject.setVisible('view3', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view3 should show'); + assert.deepEqual(target.elements, [view1, view3]); + + testObject.setVisible('view2', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); + assert.deepEqual(target.elements, [view1, view2, view3]); + + ViewsRegistry.deregisterViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, []); + assert.deepEqual(target.elements, []); + }); + + test('move', () => { + 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' }; + + 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'); + + testObject.move('view3', 'view1'); + assert.deepEqual(testObject.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); + assert.deepEqual(target.elements, [view3, view1, view2]); + + testObject.move('view1', 'view2'); + assert.deepEqual(testObject.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); + assert.deepEqual(target.elements, [view3, view2, view1]); + + testObject.move('view1', 'view3'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); + assert.deepEqual(target.elements, [view1, view3, view2]); + + 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 () { + 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(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1' + }; + + 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 () { + 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(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true) + }; + + 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(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(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 () { + 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(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + + const view1: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview', true) + }; + const view2: IViewDescriptor = { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + }; + const view3: IViewDescriptor = { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + when: ContextKeyExpr.equals('showview', true) + }; + + 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(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(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(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 () { + 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!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true), + canToggleVisibility: true + }; + + ViewsRegistry.registerViews([viewDescriptor], container); + + const key = contextKeyService.createKey('showview1', true); + await new Promise(c => setTimeout(c, 30)); + assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear after context is set'); + assert.equal(target.elements.length, 1); + + 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 targetEvent = sinon.spy(testObject.onDidRemoveVisibleViewDescriptors); + key.set(false); + await new Promise(c => setTimeout(c, 30)); + assert.ok(!targetEvent.called, 'remove event should not be called since it is already hidden'); + }); + +}); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts deleted file mode 100644 index 066b67fa550..00000000000 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ /dev/null @@ -1,628 +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 * as assert from 'assert'; -import { ContributableViewsModel, IViewState } from 'vs/workbench/browser/parts/views/views'; -import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { move } from 'vs/base/common/arrays'; -import { Registry } from 'vs/platform/registry/common/platform'; -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 sinon = require('sinon'); -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService'; -import { assertIsDefined } from 'vs/base/common/types'; - -const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); -const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); - -class ViewDescriptorSequence { - - readonly elements: IViewDescriptor[]; - private disposables: IDisposable[] = []; - - constructor(model: ContributableViewsModel) { - 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); - } - - dispose() { - this.disposables = dispose(this.disposables); - } -} - -suite('ContributableViewsModel', () => { - - let viewDescriptorService: IViewDescriptorService; - let contextKeyService: IContextKeyService; - - setup(() => { - const instantiationService: TestInstantiationService = workbenchInstantiationService(); - contextKeyService = instantiationService.createInstance(ContextKeyService); - instantiationService.stub(IContextKeyService, contextKeyService); - viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); - }); - - teardown(() => { - ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container); - }); - - test('empty model', function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - assert.equal(model.visibleViewDescriptors.length, 0); - }); - - test('register/unregister', () => { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1' - }; - - 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); - - ViewsRegistry.deregisterViews([viewDescriptor], container); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - }); - - test('when contexts', async function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview1', true) - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(seq.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); - - 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); - - 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); - - ViewsRegistry.deregisterViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(seq.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); - }); - - test('when contexts - multiple', async function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - 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) }; - - ViewsRegistry.registerViews([view1, view2], container); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'only view1 should be visible'); - assert.deepEqual(seq.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'); - - 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'); - - ViewsRegistry.deregisterViews([view1, view2], container); - }); - - test('when contexts - multiple 2', async function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - 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' }; - - ViewsRegistry.registerViews([view1, view2], container); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'only view2 should be visible'); - assert.deepEqual(seq.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'); - - 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'); - - ViewsRegistry.deregisterViews([view1, view2], container); - }); - - test('setVisible', () => { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - 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 }; - - ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3]); - assert.deepEqual(seq.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]); - - model.setVisible('view2', false); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view2 should hide'); - assert.deepEqual(seq.elements, [view1, view3]); - - model.setVisible('view1', false); - assert.deepEqual(model.visibleViewDescriptors, [view3], 'view1 should hide'); - assert.deepEqual(seq.elements, [view3]); - - model.setVisible('view3', false); - assert.deepEqual(model.visibleViewDescriptors, [], 'view3 shoud hide'); - assert.deepEqual(seq.elements, []); - - model.setVisible('view1', true); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'view1 should show'); - assert.deepEqual(seq.elements, [view1]); - - model.setVisible('view3', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view3 should show'); - assert.deepEqual(seq.elements, [view1, view3]); - - model.setVisible('view2', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); - assert.deepEqual(seq.elements, [view1, view2, view3]); - - ViewsRegistry.deregisterViews([view1, view2, view3], container); - assert.deepEqual(model.visibleViewDescriptors, []); - assert.deepEqual(seq.elements, []); - }); - - test('move', () => { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - 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' }; - - ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); - assert.deepEqual(seq.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]); - - model.move('view1', 'view2'); - assert.deepEqual(model.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); - assert.deepEqual(seq.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]); - - model.move('view2', 'view3'); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); - assert.deepEqual(seq.elements, [view1, view2, view3]); - }); - - test('view states', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1' - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - 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); - }); - - test('view states and when contexts', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview1', true) - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(seq.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); - - 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); - }); - - test('view states and when contexts multiple views', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const view1: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview', true) - }; - const view2: IViewDescriptor = { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - }; - const view3: IViewDescriptor = { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - when: ContextKeyExpr.equals('showview', true) - }; - - ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); - - const key = contextKeyService.createKey('showview', false); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.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]); - - 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]); - }); - - test('remove event is not triggered if view was hidden and removed', async function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview1', true), - canToggleVisibility: true - }; - - 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); - - model.setVisible('view1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); - assert.equal(seq.elements.length, 0); - - const target = sinon.spy(model.onDidRemove); - 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'); - }); - -}); - -const sidebarContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testSidebar', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); -const panelContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testPanel', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Panel); - -suite('ViewDescriptorService', () => { - - let viewDescriptorService: IViewDescriptorService; - - setup(() => { - const instantiationService: TestInstantiationService = workbenchInstantiationService(); - viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); - }); - - teardown(() => { - ViewsRegistry.deregisterViews(ViewsRegistry.getViews(sidebarContainer), sidebarContainer); - ViewsRegistry.deregisterViews(ViewsRegistry.getViews(panelContainer), panelContainer); - }); - - test('Empty Containers', function () { - const sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - const panelViews = viewDescriptorService.getViewDescriptors(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.'); - }); - - test('Register/Deregister', () => { - const viewDescriptors: IViewDescriptor[] = [ - { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - canMoveView: true - }, - { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - canMoveView: true - }, - { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - canMoveView: true - } - ]; - - - ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - - - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - - assert.equal(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view'); - - ViewsRegistry.deregisterViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.deregisterViews(viewDescriptors.slice(2), panelContainer); - - - sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - - assert.equal(sidebarViews.activeViewDescriptors.length, 0, 'Sidebar should have no views'); - assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have no views'); - }); - - test('move views to existing containers', async function () { - const viewDescriptors: IViewDescriptor[] = [ - { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - canMoveView: true - }, - { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - canMoveView: true - }, - { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - canMoveView: true - } - ]; - - ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - - viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(2), sidebarContainer); - viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(0, 2), panelContainer); - - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - - assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 2, 'Panel should have 1 view'); - - assert.notEqual(sidebarViews.activeViewDescriptors.indexOf(viewDescriptors[2]), -1, `Sidebar should have ${viewDescriptors[2].name}`); - assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[0]), -1, `Panel should have ${viewDescriptors[0].name}`); - assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[1]), -1, `Panel should have ${viewDescriptors[1].name}`); - }); - - test('move views to generated containers', async function () { - const viewDescriptors: IViewDescriptor[] = [ - { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - canMoveView: true - }, - { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - canMoveView: true - }, - { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - canMoveView: true - } - ]; - - ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); - - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(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'); - - const generatedPanel = assertIsDefined(viewDescriptorService.getViewContainer(viewDescriptors[0].id)); - const generatedSidebar = assertIsDefined(viewDescriptorService.getViewContainer(viewDescriptors[2].id)); - - assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel'); - assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar'); - - assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), viewDescriptorService.getViewLocation(viewDescriptors[0].id), 'Panel view location and container location should match'); - assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), viewDescriptorService.getViewLocation(viewDescriptors[2].id), 'Sidebar view location and container location should match'); - - assert.equal(viewDescriptorService.getDefaultContainer(viewDescriptors[2].id), panelContainer, `${viewDescriptors[2].name} has wrong default container`); - assert.equal(viewDescriptorService.getDefaultContainer(viewDescriptors[0].id), sidebarContainer, `${viewDescriptors[0].name} has wrong default container`); - - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); - - sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - - assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view'); - - assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar'); - assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel'); - }); - - test('move view events', async function () { - const viewDescriptors: IViewDescriptor[] = [ - { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - canMoveView: true - }, - { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - canMoveView: true - }, - { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - canMoveView: true - } - ]; - - - let expectedSequence = ''; - let actualSequence = ''; - const disposables = []; - - const containerMoveString = (view: IViewDescriptor, from: ViewContainer, to: ViewContainer) => { - return `Moved ${view.id} from ${from.id} to ${to.id}\n`; - }; - - const locationMoveString = (view: IViewDescriptor, from: ViewContainerLocation, to: ViewContainerLocation) => { - return `Moved ${view.id} from ${from === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'} to ${to === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'}\n`; - }; - disposables.push(viewDescriptorService.onDidChangeContainer(({ views, from, to }) => { - views.forEach(view => { - actualSequence += containerMoveString(view, from, to); - }); - })); - - disposables.push(viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { - views.forEach(view => { - actualSequence += locationMoveString(view, from, to); - }); - })); - - ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - - expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, viewDescriptorService.getViewContainer(viewDescriptors[0].id)!); - - expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); - expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, viewDescriptorService.getViewContainer(viewDescriptors[2].id)!); - - - expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); - expectedSequence += containerMoveString(viewDescriptors[0], viewDescriptorService.getViewContainer(viewDescriptors[0].id)!, sidebarContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], sidebarContainer); - - expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[2], viewDescriptorService.getViewContainer(viewDescriptors[2].id)!, panelContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], panelContainer); - - expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, panelContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], panelContainer); - - expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); - expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, sidebarContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], sidebarContainer); - - expectedSequence += locationMoveString(viewDescriptors[1], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[1], sidebarContainer, panelContainer); - expectedSequence += containerMoveString(viewDescriptors[2], sidebarContainer, panelContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[1], viewDescriptors[2]], panelContainer); - - assert.equal(actualSequence, expectedSequence, 'Event sequence not matching expected sequence'); - }); - -});