diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 29ac27a90b665..9cdf544c79230 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 { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; @@ -88,7 +88,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IThemeService themeService: IThemeService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, - @IViewsService private readonly viewsService: IViewsService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, @@ -189,7 +189,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewletDescriptor) { const viewContainer = this.getViewContainer(viewletDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors?.activeViewDescriptors.length === 0) { this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding } @@ -410,7 +410,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.enableCompositeActions(viewlet); const viewContainer = this.getViewContainer(viewlet.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors) { this.onDidChangeActiveViews(viewlet, viewDescriptors); this.viewletDisposables.set(viewlet.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors))); @@ -551,7 +551,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewlet) { const views: { when: string | undefined }[] = []; if (viewContainer) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors) { for (const { when } of viewDescriptors.allViewDescriptors) { views.push({ when: when ? when.serialize() : undefined }); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 354466a8da051..c381e6021cd6e 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -33,7 +33,7 @@ import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/con import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection } from 'vs/workbench/common/views'; interface ICachedPanel { id: string; @@ -98,9 +98,9 @@ export class PanelPart extends CompositePart implements IPanelService { @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IExtensionService private readonly extensionService: IExtensionService, - @IViewsService private readonly viewsService: IViewsService, ) { super( notificationService, @@ -184,7 +184,7 @@ export class PanelPart extends CompositePart implements IPanelService { this.enableCompositeActions(panel); const viewContainer = this.getViewContainer(panel.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors) { this.onDidChangeActiveViews(panel, viewDescriptors); this.panelDisposables.set(panel.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors))); @@ -295,7 +295,7 @@ export class PanelPart extends CompositePart implements IPanelService { if (panelDescriptor) { const viewContainer = this.getViewContainer(panelDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors?.activeViewDescriptors.length === 0) { this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index a3b9148647abf..91ca9c36bd059 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IViewsService, IViewsViewlet, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -21,6 +21,8 @@ import { values } from 'vs/base/common/map'; import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IPaneComposite } from 'vs/workbench/common/panecomposite'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; function filterViewRegisterEvent(container: ViewContainer, event: Event<{ viewContainer: ViewContainer, views: IViewDescriptor[]; }>): Event { return Event.chain(event) @@ -93,14 +95,14 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl constructor( container: ViewContainer, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService ) { super(); - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - const onRelevantViewsRegistered = filterViewRegisterEvent(container, viewsRegistry.onViewsRegistered); + const onRelevantViewsRegistered = filterViewRegisterEvent(container, this.viewDescriptorService.onViewsRegistered); this._register(onRelevantViewsRegistered(this.onViewsRegistered, this)); - const onRelevantViewsMoved = filterViewMoveEvent(container, viewsRegistry.onDidChangeContainer); + const onRelevantViewsMoved = filterViewMoveEvent(container, this.viewDescriptorService.onDidChangeContainer); this._register(onRelevantViewsMoved(({ added, removed }) => { if (isNonEmptyArray(added)) { this.onViewsRegistered(added); @@ -110,13 +112,14 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } })); - const onRelevantViewsDeregistered = filterViewRegisterEvent(container, viewsRegistry.onViewsDeregistered); + const onRelevantViewsDeregistered = filterViewRegisterEvent(container, this.viewDescriptorService.onViewsDeregistered); this._register(onRelevantViewsDeregistered(this.onViewsDeregistered, this)); const onRelevantContextChange = Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys)); this._register(onRelevantContextChange(this.onContextChanged, this)); - this.onViewsRegistered(viewsRegistry.getViews(container)); + + this.onViewsRegistered(this.viewDescriptorService.getViews(container)); } private onViewsRegistered(viewDescriptors: IViewDescriptor[]): void { @@ -249,7 +252,7 @@ export class ContributableViewsModel extends Disposable { constructor( container: ViewContainer, - viewsService: IViewsService, + viewsService: IViewDescriptorService, protected viewStates = new Map(), ) { super(); @@ -487,13 +490,13 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { constructor( container: ViewContainer, viewletStateStorageId: string, - @IViewsService viewsService: IViewsService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IStorageService storageService: IStorageService, ) { const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); - super(container, viewsService, viewStates); + super(container, viewDescriptorService, viewStates); this.workspaceViewsStateStorageId = viewletStateStorageId; this.globalViewsStateStorageId = globalViewsStateStorageId; @@ -624,45 +627,170 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { } } -export class ViewsService extends Disposable implements IViewsService { +export class ViewOpenerService extends Disposable implements IViewsService { _serviceBrand: undefined; - private readonly viewDescriptorCollections: Map; + private readonly viewContainersRegistry: IViewContainersRegistry; private readonly viewDisposable: Map; + + constructor( + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IPanelService private readonly panelService: IPanelService, + @IViewletService private readonly viewletService: IViewletService + ) { + super(); + + this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); + this.viewDisposable = new Map(); + + this._register(toDisposable(() => { + this.viewDisposable.forEach(disposable => disposable.dispose()); + this.viewDisposable.clear(); + })); + + this._register(this.viewDescriptorService.onViewsRegistered(({ views, viewContainer }) => this.onViewsRegistered(views, viewContainer))); + this._register(this.viewDescriptorService.onViewsDeregistered(({ views, viewContainer }) => this.onViewsDeregistered(views, viewContainer))); + } + + private onViewsRegistered(views: IViewDescriptor[], container: ViewContainer): void { + const location = this.viewContainersRegistry.getViewContainerLocation(container); + if (location === undefined) { + return; + } + + const composite = this.getComposite(container.id, location); + for (const viewDescriptor of views) { + const disposables = new DisposableStore(); + const command: ICommandAction = { + id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, + title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) }, + category: composite ? composite.name : localize('view category', "View"), + }; + const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`); + + disposables.add(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true))); + + disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command, + when + })); + + if (viewDescriptor.focusCommand && viewDescriptor.focusCommand.keybindings) { + KeybindingsRegistry.registerKeybindingRule({ + id: command.id, + when, + weight: KeybindingWeight.WorkbenchContrib, + primary: viewDescriptor.focusCommand.keybindings.primary, + secondary: viewDescriptor.focusCommand.keybindings.secondary, + linux: viewDescriptor.focusCommand.keybindings.linux, + mac: viewDescriptor.focusCommand.keybindings.mac, + win: viewDescriptor.focusCommand.keybindings.win + }); + } + + this.viewDisposable.set(viewDescriptor, disposables); + } + } + + private onViewsDeregistered(views: IViewDescriptor[], container: ViewContainer): void { + for (const view of views) { + const disposable = this.viewDisposable.get(view); + if (disposable) { + disposable.dispose(); + this.viewDisposable.delete(view); + } + } + } + + + private async openComposite(compositeId: string, location: ViewContainerLocation, focus?: boolean): Promise { + if (location === ViewContainerLocation.Sidebar) { + return this.viewletService.openViewlet(compositeId, focus); + } else if (location === ViewContainerLocation.Panel) { + return this.panelService.openPanel(compositeId, focus) as IPaneComposite; + } + return undefined; + } + + private getComposite(compositeId: string, location: ViewContainerLocation): { id: string, name: string } | undefined { + if (location === ViewContainerLocation.Sidebar) { + return this.viewletService.getViewlet(compositeId); + } else if (location === ViewContainerLocation.Panel) { + return this.panelService.getPanel(compositeId); + } + + return undefined; + } + + async openView(id: string, focus: boolean): Promise { + const viewContainer = this.viewDescriptorService.getViewContainer(id); + if (viewContainer) { + const location = this.viewContainersRegistry.getViewContainerLocation(viewContainer); + const compositeDescriptor = this.getComposite(viewContainer.id, location!); + if (compositeDescriptor) { + const paneComposite = await this.openComposite(compositeDescriptor.id, location!, focus) as IPaneComposite | undefined; + if (paneComposite && paneComposite.openView) { + return paneComposite.openView(id, focus); + } + } + } + + return null; + } +} + +export class ViewDescriptorService extends Disposable implements IViewDescriptorService { + + _serviceBrand: undefined; + + private readonly _onViewsRegistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>()); + readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsRegistered.event; + + private readonly _onViewsDeregistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>()); + readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsDeregistered.event; + + private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>()); + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event; + + private readonly viewDescriptorCollections: Map; + private readonly viewContainers: Map; private readonly activeViewContextKeys: Map>; + private readonly viewsRegistry: IViewsRegistry; + private readonly viewContainersRegistry: IViewContainersRegistry; + constructor( - @IViewletService private readonly viewletService: IViewletService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); this.viewDescriptorCollections = new Map(); - this.viewDisposable = new Map(); + this.viewContainers = new Map(); this.activeViewContextKeys = new Map>(); - const viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - viewContainersRegistry.all.forEach(viewContainer => { - this.onDidRegisterViews(viewContainer, viewsRegistry.getViews(viewContainer)); + this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); + this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + this.viewContainersRegistry.all.forEach(viewContainer => { + this.onDidRegisterViews(viewContainer, this.viewsRegistry.getViews(viewContainer)); this.onDidRegisterViewContainer(viewContainer); }); - this._register(viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(viewContainer, views))); - this._register(viewsRegistry.onViewsDeregistered(({ views }) => this.onDidDeregisterViews(views))); - this._register(viewsRegistry.onDidChangeContainer(({ views, to }) => { this.onDidDeregisterViews(views); this.onDidRegisterViews(to, views); })); - this._register(toDisposable(() => { - this.viewDisposable.forEach(disposable => disposable.dispose()); - this.viewDisposable.clear(); - })); - this._register(viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); - this._register(viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); + this._register(this.viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(viewContainer, views))); + this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(viewContainer, views))); + this._register(this.viewsRegistry.onDidChangeContainer(({ views, from, to }) => { this.onDidDeregisterViews(from, views); this.onDidRegisterViews(to, views); this._onDidChangeContainer.fire({ views, from, to }); })); + + 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(); })); } + getViewContainer(viewId: string): ViewContainer | null { + return this.viewContainers.get(viewId) ?? null; + } + getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null { const registeredViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(container.id); if (registeredViewContainer) { @@ -677,24 +805,28 @@ export class ViewsService extends Disposable implements IViewsService { return null; } - async openView(id: string, focus: boolean): Promise { - const viewContainer = Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(id); - if (viewContainer) { - const viewletDescriptor = this.viewletService.getViewlet(viewContainer.id); - if (viewletDescriptor) { - const viewlet = await this.viewletService.openViewlet(viewletDescriptor.id, focus) as IViewsViewlet | null; - if (viewlet && viewlet.openView) { - return viewlet.openView(id, focus); - } - } + moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { + if (!views.length) { + return; } - return null; + const from = this.viewContainers.get(views[0].id); + const to = viewContainer; + + if (from && to && from !== to) { + this.onDidDeregisterViews(from, views); + this.onDidRegisterViews(viewContainer, views); + this._onDidChangeContainer.fire({ views, from, to }); + } + } + + getViews(container: ViewContainer): IViewDescriptor[] { + return this.viewsRegistry.getViews(container); } private onDidRegisterViewContainer(viewContainer: ViewContainer): void { const disposables = new DisposableStore(); - const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(viewContainer, this.contextKeyService)); + const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(viewContainer, this.contextKeyService, this)); this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); @@ -716,48 +848,24 @@ export class ViewsService extends Disposable implements IViewsService { } private onDidRegisterViews(container: ViewContainer, views: IViewDescriptor[]): void { - const viewlet = this.viewletService.getViewlet(container.id); - for (const viewDescriptor of views) { - const disposables = new DisposableStore(); - const command: ICommandAction = { - id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) }, - category: viewlet ? viewlet.name : localize('view category', "View"), - }; - const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`); - - disposables.add(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true))); - - disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command, - when - })); - - if (viewDescriptor.focusCommand && viewDescriptor.focusCommand.keybindings) { - KeybindingsRegistry.registerKeybindingRule({ - id: command.id, - when, - weight: KeybindingWeight.WorkbenchContrib, - primary: viewDescriptor.focusCommand.keybindings.primary, - secondary: viewDescriptor.focusCommand.keybindings.secondary, - linux: viewDescriptor.focusCommand.keybindings.linux, - mac: viewDescriptor.focusCommand.keybindings.mac, - win: viewDescriptor.focusCommand.keybindings.win - }); - } + const location = this.viewContainersRegistry.getViewContainerLocation(container); + if (location === undefined) { + return; + } - this.viewDisposable.set(viewDescriptor, disposables); + for (const viewDescriptor of views) { + this.viewContainers.set(viewDescriptor.id, container); } + + this._onViewsRegistered.fire({ views, viewContainer: container }); } - private onDidDeregisterViews(views: IViewDescriptor[]): void { + private onDidDeregisterViews(container: ViewContainer, views: IViewDescriptor[]): void { for (const view of views) { - const disposable = this.viewDisposable.get(view); - if (disposable) { - disposable.dispose(); - this.viewDisposable.delete(view); - } + this.viewContainers.delete(view.id); } + + this._onViewsDeregistered.fire({ views, viewContainer: container }); } private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey { @@ -784,4 +892,5 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement, return themeService.onDidFileIconThemeChange(onDidChangeFileIconTheme); } -registerSingleton(IViewsService, ViewsService); +registerSingleton(IViewDescriptorService, ViewDescriptorService); +registerSingleton(IViewsService, ViewOpenerService); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 174df04dff654..72f90b88cd6da 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -100,6 +100,11 @@ export interface IViewContainersRegistry { * Returns all view containers in the given location */ getViewContainers(location: ViewContainerLocation): ViewContainer[]; + + /** + * Returns the view container location + */ + getViewContainerLocation(container: ViewContainer): ViewContainerLocation | undefined; } interface ViewOrderDelegate { @@ -157,6 +162,10 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe getViewContainers(location: ViewContainerLocation): ViewContainer[] { return [...(this.viewContainers.get(location) || [])]; } + + getViewContainerLocation(container: ViewContainer): ViewContainerLocation | undefined { + return keys(this.viewContainers).filter(location => this.getViewContainers(location).filter(viewContainer => viewContainer.id === container.id).length > 0)[0]; + } } Registry.add(Extensions.ViewContainersRegistry, new ViewContainersRegistryImpl()); @@ -336,14 +345,34 @@ export interface IViewsViewlet extends IViewlet { } +export const IViewDescriptorService = createDecorator('viewDescriptorService'); export const IViewsService = createDecorator('viewsService'); + export interface IViewsService { _serviceBrand: undefined; openView(id: string, focus?: boolean): Promise; +} + + +export interface IViewDescriptorService { + + _serviceBrand: undefined; + + readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>; + + readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>; + + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>; + + moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void; + + getViews(container: ViewContainer): IViewDescriptor[]; getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null; + + getViewContainer(viewId: string): ViewContainer | null; } // Custom views diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index d95ee1360d0cd..bbc5693905b31 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -4,16 +4,56 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { OutlinePane } from './outlinePane'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; +import { Action } from 'vs/base/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionsExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; // import './outlineNavigation'; +export const PANEL_ID = 'panel.view.outline'; + +export class OutlineViewPaneContainer extends ViewPaneContainer { + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IStorageService protected storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + ) { + super(PANEL_ID, `${PANEL_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + } +} + +export const VIEW_CONTAINER_PANEL: ViewContainer = + Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ + id: PANEL_ID, + ctorDescriptor: new SyncDescriptor(OutlineViewPaneContainer), + name: localize('name', "Outline"), + hideIfEmpty: true + }, ViewContainerLocation.Panel); + + const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), @@ -28,6 +68,41 @@ const _outlineDesc = { Registry.as(ViewExtensions.ViewsRegistry).registerViews([_outlineDesc], VIEW_CONTAINER); +let inPanel = false; + +export class ToggleOutlinePositionAction extends Action { + + static ID = 'outline.view.togglePosition'; + static LABEL = 'Toggle Outline View Position'; + + constructor( + id: string, + label: string, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IViewsService private readonly viewsService: IViewsService + ) { + super(id, label, '', true); + } + + async run(): Promise { + if (!inPanel) { + this.viewDescriptorService.moveViews([_outlineDesc], VIEW_CONTAINER_PANEL); + this.viewsService.openView(OutlineViewId, true); + inPanel = true; + } else { + this.viewDescriptorService.moveViews([_outlineDesc], VIEW_CONTAINER); + this.viewsService.openView(OutlineViewId, true); + + inPanel = false; + } + + } +} + +Registry.as(ActionsExtensions.WorkbenchActions) + .registerWorkbenchAction(SyncActionDescriptor.create(ToggleOutlinePositionAction, ToggleOutlinePositionAction.ID, ToggleOutlinePositionAction.LABEL), 'Show Release Notes'); + + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'id': 'outline', 'order': 117, diff --git a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts index 587dc075b247b..eefa445672855 100644 --- a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts @@ -16,7 +16,7 @@ import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { fuzzyContains, stripWildcards } from 'vs/base/common/strings'; import { matchesFuzzy } from 'vs/base/common/filters'; -import { IViewsRegistry, ViewContainer, IViewsService, IViewContainersRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, ViewContainer, IViewDescriptorService, IViewContainersRegistry, Extensions as ViewExtensions, IViewsService } from 'vs/workbench/common/views'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -70,6 +70,7 @@ export class ViewPickerHandler extends QuickOpenHandler { constructor( @IViewletService private readonly viewletService: IViewletService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IViewsService private readonly viewsService: IViewsService, @IOutputService private readonly outputService: IOutputService, @ITerminalService private readonly terminalService: ITerminalService, @@ -197,7 +198,7 @@ export class ViewPickerHandler extends QuickOpenHandler { private hasToShowViewlet(viewlet: ViewletDescriptor): boolean { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); if (viewContainer?.hideIfEmpty) { - const viewsCollection = this.viewsService.getViewDescriptors(viewContainer); + const viewsCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); return !!viewsCollection && viewsCollection.activeViewDescriptors.length > 0; } return true; diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index a5244e4de17c9..49502d7bec95e 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ContributableViewsModel, ViewsService, IViewState } from 'vs/workbench/browser/parts/views/views'; -import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ContributableViewsModel, ViewDescriptorService, IViewState } from 'vs/workbench/browser/parts/views/views'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation } 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'; @@ -38,14 +38,14 @@ class ViewDescriptorSequence { suite('ContributableViewsModel', () => { - let viewsService: IViewsService; + let viewDescriptorService: IViewDescriptorService; let contextKeyService: IContextKeyService; setup(() => { const instantiationService: TestInstantiationService = workbenchInstantiationService(); contextKeyService = instantiationService.createInstance(ContextKeyService); instantiationService.stub(IContextKeyService, contextKeyService); - viewsService = instantiationService.createInstance(ViewsService); + viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); }); teardown(() => { @@ -53,12 +53,12 @@ suite('ContributableViewsModel', () => { }); test('empty model', function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); assert.equal(model.visibleViewDescriptors.length, 0); }); test('register/unregister', () => { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -84,7 +84,7 @@ suite('ContributableViewsModel', () => { }); test('when contexts', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -128,7 +128,7 @@ suite('ContributableViewsModel', () => { }); test('when contexts - multiple', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; @@ -151,7 +151,7 @@ suite('ContributableViewsModel', () => { }); test('when contexts - multiple 2', async function () { - const model = new ContributableViewsModel(container, viewsService); + 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) }; @@ -174,7 +174,7 @@ suite('ContributableViewsModel', () => { }); test('setVisible', () => { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; @@ -219,7 +219,7 @@ suite('ContributableViewsModel', () => { }); test('move', () => { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; @@ -250,7 +250,7 @@ suite('ContributableViewsModel', () => { test('view states', async function () { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewsService, viewStates); + const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -270,7 +270,7 @@ suite('ContributableViewsModel', () => { 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, viewsService, viewStates); + const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -300,7 +300,7 @@ suite('ContributableViewsModel', () => { 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, viewsService, viewStates); + const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -344,7 +344,7 @@ suite('ContributableViewsModel', () => { }); test('remove event is not triggered if view was hidden and removed', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const viewDescriptor: IViewDescriptor = {