diff --git a/CHANGELOG.md b/CHANGELOG.md index d6546611..851af523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ - [diagram] Restructure some tools to have a more common infrastructure and support helper lines [#306](https://github.com/eclipse-glsp/glsp-client/pull/306) - [diagram] Fix a bug in `SelectionService` that caused issues with inversify when injecting certain services (e.g. `ActionDispatcher`) in `SelectionChangeListener` implementations [#305](https://github.com/eclipse-glsp/glsp-client/pull/305) - [diagram] Ensure that the `SelectionService` does not trigger a change event if the selection did not change on model update [#313](https://github.com/eclipse-glsp/glsp-client/pull/313) +- [API] Apply feedback commands already on `SetModelCommand` and unify `rank` and `priority` property [#323](https://github.com/eclipse-glsp/glsp-client/pull/322). + - Method `FeedbackAwareUpdateModelCommand.getFeedbackCommands` was move to `IFeedbackEmitter` for re-use, resulting in two new methods: `getFeedbackCommands` and `applyFeedbackCommands`. + - Method `FeedbackAwareUpdateModelCommand.getPriority` was replaced by a generic `rank` property and the `Ranked` namespace. + - The `priority` property (higher priority equals earlier execution) in `FeedbackCommand` was superseeded by a `rank` property (lower rank equals earlier execution). ## [v2.0.0 - 14/10/2023](https://github.com/eclipse-glsp/glsp-client/releases/tag/v2.0.0) diff --git a/packages/client/src/base/action-handler-registry.ts b/packages/client/src/base/action-handler-registry.ts index 7b130324..b063fb89 100644 --- a/packages/client/src/base/action-handler-registry.ts +++ b/packages/client/src/base/action-handler-registry.ts @@ -14,17 +14,56 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { ActionHandlerRegistry } from '@eclipse-glsp/sprotty'; +import { ActionHandlerRegistration, ActionHandlerRegistry, IActionHandler, IActionHandlerInitializer, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, named } from 'inversify'; +import { IContributionProvider } from '../utils/contribution-provider'; @injectable() export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { + @inject(TYPES.IContributionProvider) + @named(TYPES.ActionHandlerRegistration) + protected readonly registrations: IContributionProvider; + + @inject(TYPES.IContributionProvider) + @named(TYPES.IActionHandlerInitializer) + protected readonly initializers: IContributionProvider; + + protected initialized = false; + + constructor() { + super([], []); + } + + protected init(): void { + if (!this.initialized) { + this.initialized = true; + this.registrations.getContributions().forEach(registration => this.register(registration.actionKind, registration.factory())); + this.initializers.getContributions().forEach(initializer => this.initializeActionHandler(initializer)); + } + } + + override register(key: string, instance: IActionHandler): void { + this.init(); + super.register(key, instance); + } + + override get(key: string): IActionHandler[] { + this.init(); + return super.get(key); + } + + override initializeActionHandler(initializer: IActionHandlerInitializer): void { + this.init(); + super.initializeActionHandler(initializer); + } + /** * Retrieve a set of all action kinds for which (at least) one * handler is registered * @returns the set of handled action kinds */ getHandledActionKinds(): string[] { + this.init(); return Array.from(this.elements.keys()); } } diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 6690f866..6317f5c0 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -22,6 +22,7 @@ import { MoveCommand, SetDirtyStateAction, SetEditModeAction, + SetModelCommand, TYPES, bindAsService, bindOrRebind, @@ -31,10 +32,12 @@ import { } from '@eclipse-glsp/sprotty'; import '@vscode/codicons/dist/codicon.css'; import '../../css/glsp-sprotty.css'; +import { bindContributionProvider } from '../utils/contribution-provider'; import { GLSPActionDispatcher } from './action-dispatcher'; import { GLSPActionHandlerRegistry } from './action-handler-registry'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; +import { FeedbackAwareSetModelCommand } from './feedback'; import { ModifyCssFeedbackCommand } from './feedback/css-feedback'; import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command'; @@ -73,6 +76,7 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . // Model update initialization ------------------------------------ bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); configureCommand(context, FeedbackAwareUpdateModelCommand); + rebind(SetModelCommand).to(FeedbackAwareSetModelCommand); bind(GLSPMouseTool).toSelf().inSingletonScope(); bindOrRebind(context, MouseTool).toService(GLSPMouseTool); @@ -84,6 +88,9 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack).inSingletonScope(); bind(GLSPActionDispatcher).toSelf().inSingletonScope(); bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); + + bindContributionProvider(bind, TYPES.ActionHandlerRegistration); + bindContributionProvider(bind, TYPES.IActionHandlerInitializer); bind(GLSPActionHandlerRegistry).toSelf().inSingletonScope(); bindOrRebind(context, ActionHandlerRegistry).toService(GLSPActionHandlerRegistry); diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index 708b2bb9..1dcd22d9 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -13,8 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { + Action, + ActionHandlerRegistry, + Command, + CommandActionHandler, + CommandExecutionContext, + Disposable, + IActionDispatcher, + ICommand, + ILogger, + TYPES, + toTypeGuard +} from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { Action, Disposable, IActionDispatcher, ILogger, TYPES } from '@eclipse-glsp/sprotty'; +import { getFeedbackRank } from './feedback-command'; export interface IFeedbackEmitter {} @@ -57,6 +70,16 @@ export interface IFeedbackActionDispatcher { * Retrieve all `actions` sent out by currently registered `feedbackEmitter`. */ getRegisteredFeedback(): Action[]; + + /** + * Retrieves all commands based on the registered feedback actions, ordered by their rank (lowest rank first). + */ + getFeedbackCommands(): Command[]; + + /** + * Applies all current feedback commands to the given command execution context. + */ + applyFeedbackCommands(context: CommandExecutionContext): Promise; } @injectable() @@ -66,6 +89,8 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; @inject(TYPES.ILogger) protected logger: ILogger; + @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; + registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: Action[] | undefined): Disposable { if (feedbackActions.length > 0) { this.registeredFeedback.set(feedbackEmitter, feedbackActions); @@ -97,6 +122,29 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { return result; } + getFeedbackCommands(): Command[] { + return this.getRegisteredFeedback() + .flatMap(action => this.actionToCommands(action)) + .sort((left, right) => getFeedbackRank(left) - getFeedbackRank(right)); + } + + async applyFeedbackCommands(context: CommandExecutionContext): Promise { + const feedbackCommands = this.getFeedbackCommands() ?? []; + if (feedbackCommands?.length > 0) { + const results = feedbackCommands.map(command => command.execute(context)); + await Promise.all(results); + } + } + + protected actionToCommands(action: Action): ICommand[] { + return ( + this.actionHandlerRegistry + ?.get(action.kind) + .filter(toTypeGuard(CommandActionHandler)) + .map(handler => handler.handle(action)) ?? [] + ); + } + protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise { try { const actionDispatcher = await this.actionDispatcher(); diff --git a/packages/client/src/base/feedback/feedback-command.ts b/packages/client/src/base/feedback/feedback-command.ts index 1cd192a6..2d033982 100644 --- a/packages/client/src/base/feedback/feedback-command.ts +++ b/packages/client/src/base/feedback/feedback-command.ts @@ -13,11 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Command, CommandExecutionContext, CommandReturn } from '@eclipse-glsp/sprotty'; +/* eslint-disable deprecation/deprecation */ +import { Command, CommandExecutionContext, CommandReturn, ICommand } from '@eclipse-glsp/sprotty'; +import { Ranked } from '../ranked'; -export abstract class FeedbackCommand extends Command { - // used by the `FeedbackAwareUpdateModelCommand` - readonly priority: number = 0; +export abstract class FeedbackCommand extends Command implements Ranked { + /** @deprecated Use rank instead. Please note that a lower rank implies higher priority, so the order is reversed. */ + readonly priority?: number = 0; + + // backwards compatibility: convert any existing priority to an equivalent rank + readonly rank: number = this.priority ? -this.priority : Ranked.DEFAULT_RANK; undo(context: CommandExecutionContext): CommandReturn { return context.root; @@ -27,3 +32,9 @@ export abstract class FeedbackCommand extends Command { return context.root; } } + +/** Used for backwards compatibility, otherwise use Ranked.getRank or Ranked sort functions. */ +export function getFeedbackRank(command: ICommand): number { + const feedbackCommand = command as Partial; + return feedbackCommand?.priority ? -feedbackCommand.priority : feedbackCommand.rank ?? Ranked.DEFAULT_RANK; +} diff --git a/packages/client/src/base/feedback/index.ts b/packages/client/src/base/feedback/index.ts index 9a753e06..0ba61ca6 100644 --- a/packages/client/src/base/feedback/index.ts +++ b/packages/client/src/base/feedback/index.ts @@ -16,4 +16,6 @@ export * from './css-feedback'; export * from './feedback-action-dispatcher'; export * from './feedback-command'; +export * from './set-model-command'; export * from './update-model-command'; + diff --git a/packages/client/src/base/feedback/set-model-command.ts b/packages/client/src/base/feedback/set-model-command.ts new file mode 100644 index 00000000..71c37d24 --- /dev/null +++ b/packages/client/src/base/feedback/set-model-command.ts @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { CommandExecutionContext, GModelRoot, ILogger, SetModelAction, SetModelCommand, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { IFeedbackActionDispatcher } from './feedback-action-dispatcher'; + +@injectable() +export class FeedbackAwareSetModelCommand extends SetModelCommand { + @inject(TYPES.ILogger) + protected logger: ILogger; + + @inject(TYPES.IFeedbackActionDispatcher) + @optional() + protected feedbackActionDispatcher?: IFeedbackActionDispatcher; + + constructor(@inject(TYPES.Action) action: SetModelAction) { + super(action); + } + + override execute(context: CommandExecutionContext): GModelRoot { + const root = super.execute(context); + this.applyFeedback(root, context); + return root; + } + + protected applyFeedback(newRoot: GModelRoot, context: CommandExecutionContext): void { + // Create a temporary context which defines the `newRoot` as `root` + // This way we do not corrupt the redo/undo behavior of the super class + const tempContext: CommandExecutionContext = { ...context, root: newRoot }; + this.feedbackActionDispatcher?.applyFeedbackCommands(tempContext); + } +} diff --git a/packages/client/src/base/feedback/update-model-command.ts b/packages/client/src/base/feedback/update-model-command.ts index 18a8f05d..d7666786 100644 --- a/packages/client/src/base/feedback/update-model-command.ts +++ b/packages/client/src/base/feedback/update-model-command.ts @@ -13,25 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { - ActionHandlerRegistry, Animation, - Command, - CommandActionHandler, CommandExecutionContext, CommandReturn, + GModelRoot, ILogger, MorphEdgesAnimation, - GModelRoot, TYPES, UpdateAnimationData, UpdateModelAction, - UpdateModelCommand, - toTypeGuard + UpdateModelCommand } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { IFeedbackActionDispatcher } from './feedback-action-dispatcher'; -import { FeedbackCommand } from './feedback-command'; /** * A special {@link UpdateModelCommand} that retrieves all registered {@link Action}s from the {@link IFeedbackActionDispatcher} @@ -47,50 +42,18 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { @optional() protected feedbackActionDispatcher: IFeedbackActionDispatcher; - protected actionHandlerRegistry?: ActionHandlerRegistry; - - constructor( - @inject(TYPES.Action) action: UpdateModelAction, - @inject(TYPES.ActionHandlerRegistryProvider) - actionHandlerRegistryProvider: () => Promise - ) { + constructor(@inject(TYPES.Action) action: UpdateModelAction) { super({ animate: true, ...action }); - actionHandlerRegistryProvider().then(registry => (this.actionHandlerRegistry = registry)); } protected override performUpdate(oldRoot: GModelRoot, newRoot: GModelRoot, context: CommandExecutionContext): CommandReturn { - if (this.feedbackActionDispatcher && this.actionHandlerRegistry) { - // Create a temporary context which defines the `newRoot` as `root` - // This way we do not corrupt the redo/undo behavior of the super class - const tempContext: CommandExecutionContext = { - ...context, - root: newRoot - }; - - const feedbackCommands = this.getFeedbackCommands(this.actionHandlerRegistry); - feedbackCommands.forEach(command => command.execute(tempContext)); - } - + // Create a temporary context which defines the `newRoot` as `root` + // This way we do not corrupt the redo/undo behavior of the super class + const tempContext: CommandExecutionContext = { ...context, root: newRoot }; + this.feedbackActionDispatcher?.applyFeedbackCommands(tempContext); return super.performUpdate(oldRoot, newRoot, context); } - protected getFeedbackCommands(registry: ActionHandlerRegistry): Command[] { - const result: Command[] = []; - this.feedbackActionDispatcher.getRegisteredFeedback().forEach(action => { - const commands = registry - .get(action.kind) - .filter(toTypeGuard(CommandActionHandler)) - .map(handler => handler.handle(action)); - result.push(...commands); - }); - // sort commands descanting by priority - return result.sort((a, b) => this.getPriority(b) - this.getPriority(a)); - } - - protected getPriority(command: Partial): number { - return command.priority ?? 0; - } - // Override the `createAnimations` implementation and remove the animation for edge morphing. Otherwise routing & reconnect // handles flicker after each server update. protected override createAnimations(data: UpdateAnimationData, root: GModelRoot, context: CommandExecutionContext): Animation[] { diff --git a/packages/client/src/base/ranked.ts b/packages/client/src/base/ranked.ts index f1914fbb..759236b8 100644 --- a/packages/client/src/base/ranked.ts +++ b/packages/client/src/base/ranked.ts @@ -21,11 +21,16 @@ import { AnyObject, hasNumberProp } from '@eclipse-glsp/sprotty'; * orderable by a type or rank/priority. */ export interface Ranked { + /** + * A rank implies the position of this element within a sequence of other ranked elements. + * A lower rank implies a position earlier in the list. + */ rank: number; } export namespace Ranked { export const DEFAULT_RANK = 0; + export function is(object: unknown): object is Ranked { return AnyObject.is(object) && hasNumberProp(object, 'rank'); } @@ -39,4 +44,13 @@ export namespace Ranked { export function getRank(object: unknown): number { return is(object) ? object.rank : DEFAULT_RANK; } + + /** Sort function for lowest rank first. */ + export const sortAsc = (left: unknown, right: unknown): number => getRank(left) - getRank(right); + + /** Sort function for highest rank first. */ + export const sortDesc = (left: unknown, right: unknown): number => getRank(right) - getRank(left); + + /** Default sort function for rank: Lowest rank first */ + export const sort = sortAsc; } diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index eb2ee082..d33bb00d 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, GModelRoot, GNode, TYPES, initializeContainer } from '@eclipse-glsp/sprotty'; +import { Action, Command, CommandExecutionContext, Disposable, GModelRoot, GNode, TYPES, initializeContainer } from '@eclipse-glsp/sprotty'; import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import * as sinon from 'sinon'; @@ -44,6 +44,12 @@ class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { const actions = this.getRegisteredFeedback(); return actions.length === 1 ? (actions[0] as SelectFeedbackAction) : undefined; } + + getFeedbackCommands(): Command[] { + return []; + } + + async applyFeedbackCommands(context: CommandExecutionContext): Promise {} } class MockSelectionListener implements ISelectionListener { diff --git a/packages/client/src/features/bounds/set-bounds-feedback-command.ts b/packages/client/src/features/bounds/set-bounds-feedback-command.ts index 62b20e4b..de642470 100644 --- a/packages/client/src/features/bounds/set-bounds-feedback-command.ts +++ b/packages/client/src/features/bounds/set-bounds-feedback-command.ts @@ -24,6 +24,7 @@ import { isLayoutContainer } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { Ranked } from '../../base'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { LocalRequestBoundsAction } from './local-bounds'; @@ -48,7 +49,7 @@ export namespace SetBoundsFeedbackAction { export class SetBoundsFeedbackCommand extends SetBoundsCommand implements FeedbackCommand { static override readonly KIND: string = SetBoundsFeedbackAction.KIND; - readonly priority: number = 0; + readonly rank: number = Ranked.DEFAULT_RANK; @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; diff --git a/packages/client/src/features/hints/type-hint-provider.ts b/packages/client/src/features/hints/type-hint-provider.ts index 093298d8..772ffc36 100644 --- a/packages/client/src/features/hints/type-hint-provider.ts +++ b/packages/client/src/features/hints/type-hint-provider.ts @@ -13,19 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, CommandExecutionContext, Connectable, EdgeTypeHint, - IActionHandler, - RequestTypeHintsAction, GModelElement, GModelElementSchema, GModelRoot, GRoutableElement, GShapeElement, + IActionHandler, + RequestTypeHintsAction, SetTypeHintsAction, ShapeTypeHint, TYPES, @@ -36,16 +35,17 @@ import { isConnectable, moveFeature } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { IDiagramStartup } from '../../base/model/diagram-loader'; +import { GEdge } from '../../model'; import { getElementTypeId } from '../../utils/gmodel-util'; import { resizeFeature } from '../change-bounds/model'; import { reconnectFeature } from '../reconnect/model'; import { containerFeature, isContainable, reparentFeature } from './model'; -import { GEdge } from '../../model'; /** * Is dispatched by the {@link TypeHintProvider} to apply the type hints received from the server @@ -78,7 +78,7 @@ type CanConnectFn = Connectable['canConnect']; @injectable() export class ApplyTypeHintsCommand extends FeedbackCommand { public static KIND = ApplyTypeHintsAction.KIND; - public override readonly priority = 10; + public override readonly rank: number = -10; @inject(TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider; diff --git a/packages/client/src/utils/contribution-provider.ts b/packages/client/src/utils/contribution-provider.ts new file mode 100644 index 00000000..a489727a --- /dev/null +++ b/packages/client/src/utils/contribution-provider.ts @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (C) 2017 TypeFox and others. + * Modifications: (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 + *******************************************************************************/ +// eslint-disable-next-line max-len +// based on https://github.com/eclipse-theia/theia/blob/9ff0cedff1d591b0eb4be97a05f6d992789d0a24/packages/core/src/common/contribution-provider.ts + +import { BindingContext, TYPES } from '@eclipse-glsp/sprotty'; +import { interfaces } from 'inversify'; + +export interface IContributionProvider { + /** + * @param recursive `true` if the contributions should be collected from the parent containers as well. Otherwise, `false`. + * It is `false` by default. + */ + getContributions(recursive?: boolean): T[]; +} + +class ContainerBasedContributionProvider implements IContributionProvider { + protected services: T[] | undefined; + + constructor( + protected readonly serviceIdentifier: interfaces.ServiceIdentifier, + protected readonly container: interfaces.Container + ) {} + + getContributions(recursive?: boolean): T[] { + if (this.services === undefined) { + const currentServices: T[] = []; + let currentContainer: interfaces.Container | undefined = this.container; + while (currentContainer !== undefined) { + if (currentContainer.isBound(this.serviceIdentifier)) { + try { + currentServices.push(...currentContainer.getAll(this.serviceIdentifier)); + } catch (error) { + console.error(error); + } + } + currentContainer = recursive === true && currentContainer.parent ? currentContainer.parent : undefined; + } + this.services = currentServices; + } + return this.services; + } +} + +export function bindContributionProvider(context: Pick | interfaces.Bind, id: symbol): void { + const bind = typeof context === 'object' ? context.bind.bind(context) : context; + bind(TYPES.IContributionProvider) + .toDynamicValue(ctx => new ContainerBasedContributionProvider(id, ctx.container)) + .inSingletonScope() + .whenTargetNamed(id); +} diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 8c763eb2..2bb7cbec 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { TYPES as SprottyTYPES } from 'sprotty'; + /** * Reexport of the TYPES namespace of sprotty augments with additional GLSP specific service * identifiers. @@ -46,5 +47,6 @@ export const TYPES = { ILocalElementNavigator: Symbol('ILocalElementNavigator'), IDiagramOptions: Symbol('IDiagramOptions'), IDiagramStartup: Symbol('IDiagramStartup'), - IToolManager: Symbol('IToolManager') + IToolManager: Symbol('IToolManager'), + IContributionProvider: Symbol('IContributionProvider') };