Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance views service #87505

Merged
merged 9 commits into from
Jan 13, 2020
12 changes: 8 additions & 4 deletions src/vs/workbench/browser/parts/panel/panelPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
@IThemeService themeService: IThemeService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IExtensionService private readonly extensionService: IExtensionService,
@IViewsService private readonly viewsService: IViewsService,
) {
super(
notificationService,
Expand Down Expand Up @@ -157,10 +156,14 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
this.panelFocusContextKey = PanelFocusContext.bindTo(contextKeyService);

this.registerListeners();
this.onDidRegisterPanels([...this.getPanels()]);

setTimeout(() => {
this.onDidRegisterPanels([...this.getPanels()]);
}, 1);
}

private onDidRegisterPanels(panels: PanelDescriptor[]): void {
const viewsService = this.instantiationService.invokeFunction(accessor => accessor.get(IViewsService));
for (const panel of panels) {
const cachedPanel = this.getCachedPanels().filter(({ id }) => id === panel.id)[0];
const activePanel = this.getActivePanel();
Expand All @@ -184,7 +187,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
this.enableCompositeActions(panel);
const viewContainer = this.getViewContainer(panel.id);
if (viewContainer?.hideIfEmpty) {
const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer);
const viewDescriptors = viewsService.getViewDescriptors(viewContainer);
if (viewDescriptors) {
this.onDidChangeActiveViews(panel, viewDescriptors);
this.panelDisposables.set(panel.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors)));
Expand Down Expand Up @@ -281,6 +284,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}

private onPanelOpen(panel: IPanel): void {
const viewsService = this.instantiationService.invokeFunction(accessor => accessor.get(IViewsService));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed I suppose as you are having IViewService defined in the constructor

this.activePanelContextKey.set(panel.getId());

const foundPanel = this.panelRegistry.getPanel(panel.getId());
Expand All @@ -295,7 +299,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
if (panelDescriptor) {
const viewContainer = this.getViewContainer(panelDescriptor.id);
if (viewContainer?.hideIfEmpty) {
const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer);
const viewDescriptors = viewsService.getViewDescriptors(viewContainer);
if (viewDescriptors?.activeViewDescriptors.length === 0) {
this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding
}
Expand Down
122 changes: 96 additions & 26 deletions src/vs/workbench/browser/parts/views/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { IViewsService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry, ViewContainerLocation } 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';
Expand All @@ -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<IViewDescriptor[]> {
return Event.chain(event)
Expand Down Expand Up @@ -93,14 +95,14 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl

constructor(
container: ViewContainer,
@IContextKeyService private readonly contextKeyService: IContextKeyService
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IViewsService private readonly viewsService: IViewsService
) {
super();
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
const onRelevantViewsRegistered = filterViewRegisterEvent(container, viewsRegistry.onViewsRegistered);
const onRelevantViewsRegistered = filterViewRegisterEvent(container, this.viewsService.onViewsRegistered);
this._register(onRelevantViewsRegistered(this.onViewsRegistered, this));

const onRelevantViewsMoved = filterViewMoveEvent(container, viewsRegistry.onDidChangeContainer);
const onRelevantViewsMoved = filterViewMoveEvent(container, this.viewsService.onDidChangeContainer);
this._register(onRelevantViewsMoved(({ added, removed }) => {
if (isNonEmptyArray(added)) {
this.onViewsRegistered(added);
Expand All @@ -110,13 +112,14 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl
}
}));

const onRelevantViewsDeregistered = filterViewRegisterEvent(container, viewsRegistry.onViewsDeregistered);
const onRelevantViewsDeregistered = filterViewRegisterEvent(container, this.viewsService.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.viewsService.getViews(container));
}

private onViewsRegistered(viewDescriptors: IViewDescriptor[]): void {
Expand Down Expand Up @@ -628,35 +631,51 @@ export class ViewsService extends Disposable implements IViewsService {

_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<ViewContainer, { viewDescriptorCollection: IViewDescriptorCollection, disposable: IDisposable; }>;
private readonly viewContainers: Map<string, ViewContainer>;
private readonly viewDisposable: Map<IViewDescriptor, IDisposable>;
private readonly activeViewContextKeys: Map<string, IContextKey<boolean>>;

private readonly viewsRegistry: IViewsRegistry;
private readonly viewContainersRegistry: IViewContainersRegistry;

constructor(
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService,
@IContextKeyService private readonly contextKeyService: IContextKeyService
) {
super();

this.viewDescriptorCollections = new Map<ViewContainer, { viewDescriptorCollection: IViewDescriptorCollection, disposable: IDisposable; }>();
this.viewContainers = new Map<string, ViewContainer>();
this.viewDisposable = new Map<IViewDescriptor, IDisposable>();
this.activeViewContextKeys = new Map<string, IContextKey<boolean>>();

const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry);
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
viewContainersRegistry.all.forEach(viewContainer => {
this.onDidRegisterViews(viewContainer, viewsRegistry.getViews(viewContainer));
this.viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry);
this.viewsRegistry = Registry.as<IViewsRegistry>(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(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(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.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();
Expand All @@ -669,23 +688,62 @@ export class ViewsService extends Disposable implements IViewsService {
}

async openView(id: string, focus: boolean): Promise<IView | null> {
const viewContainer = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).getViewContainer(id);
const viewContainer = this.viewContainers.get(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);
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;
}

moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void {
if (!views.length) {
return;
}

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 async openComposite(compositeId: string, location: ViewContainerLocation, focus?: boolean): Promise<IPaneComposite | undefined> {
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;
}

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);
Expand All @@ -707,13 +765,18 @@ export class ViewsService extends Disposable implements IViewsService {
}

private onDidRegisterViews(container: ViewContainer, views: IViewDescriptor[]): void {
const viewlet = this.viewletService.getViewlet(container.id);
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: viewlet ? viewlet.name : localize('view category', "View"),
category: composite ? composite.name : localize('view category', "View"),
};
const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`);

Expand All @@ -737,18 +800,25 @@ export class ViewsService extends Disposable implements IViewsService {
});
}

this.viewContainers.set(viewDescriptor.id, container);
this.viewDisposable.set(viewDescriptor, disposables);
}

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<boolean> {
Expand Down
19 changes: 19 additions & 0 deletions src/vs/workbench/common/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,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 {
Expand Down Expand Up @@ -156,6 +161,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());
Expand Down Expand Up @@ -340,8 +349,18 @@ export const IViewsService = createDecorator<IViewsService>('viewsService');
export interface IViewsService {
_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;

openView(id: string, focus?: boolean): Promise<IView | null>;

getViews(container: ViewContainer): IViewDescriptor[];

getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null;
}

Expand Down
Loading