IntelliJ Platform Plugin SDK Help

Event Listening

There are two ways to subscribe to events from the Workspace Model: via listener or by subscribing to the flow of events.

EntityChange Events

The order of EntityChange events is predefined:

  1. EntityChange.Removed

  2. EntityChange.Replaced

  3. EntityChange.Added

The Added and Removed events are straightforward and generated in case of added or removed entities.

The Replaced event is generated in case if any of the fields of the entity changes the value in the newer version of storage. This event is generated in two cases:

  • "primitive" field change (Int, String, data class, etc.)

  • changes of references to other entities

The change of references may happen indirectly by modifying the referred entity. For example, when removing a child entity, two events are generated: Removed for a child and Replaced for a parent. When adding a new child entity, again two events will be generated: Added for a child and Replaced for a parent.

Examples

Example 1

Assuming the following structure of entities: ABC. Where A is the root entity and B is its child, and C is the child of B.

  • Modify a primitive field of C: [Replaced(C)]

  • Remove C: [Replaced(B), Removed(C)]

  • Remove reference between B and C: [Replaced(B), Replaced(C)]

  • Remove B: [Replaced(A), Removed(B), Removed(C)] – C is cascade removed

Example 2

Assuming the following structure of entities: Initial: AB C After: A CB

Move child B from A to C: [Replaced(A), Replaced(B), Replaced(C)]

Subscription via WorkspaceModelChangeListener

The Workspace Model storage allows us to subscribe to all of its changes using the Listeners mechanism via WorkspaceModelChangeListener.

The listener offers both WorkspaceModelChangeListener.beforeChanged() and WorkspaceModelChangeListener.changed() callbacks. The object passed to these methods as a parameter VersionedStorageChange contains snapshots before and after the modification. It can be filtered by the concrete type of entity and action (Added/Removed/Replaced).

Examples:

Registration of listeners can be done in two ways, depending on requirements:

Programmatic Registration

Listener instance subscription is managed by code, and it starts receiving events only after explicit registration.

val busConnection = project.messageBus.connect(cs) busConnection.subscribe(WorkspaceModelTopics.CHANGED, object : WorkspaceModelChangeListener { override fun changed(event: VersionedStorageChange) { // Get all changes for ModuleEntity val moduleChanges = event.getChanges(ModuleEntity::class.java) // Filtering entities which were removed moduleChanges.filterIsInstance<EntityChange.Removed<ModuleEntity>>() .forEach { removedModuleEntity -> ... } } })

Declarative Registration

Declarative registration is available by registering the Project-level listener in plugin.xml. Listener instances will be automatically instantiated on the first event to the topic.

<idea-plugin> <projectListeners> <listener class="com.my.plugin.MyCustomWorkspaceModelChangeListener" topic="com.intellij.platform.backend.workspace.WorkspaceModelChangeListener"/> </projectListeners> </idea-plugin>

Subscription via Kotlin Flow

As the IntelliJ Platform started adapting Kotlin Coroutines in its APIs and internal code, it is also possible to subscribe to the events via Kotlin Flows exposed via the WorkspaceModel.eventLog property.

In addition to the regular events subscription, flow allows performing incremental computation: calculate information based on the initial storage and modify it according to the updates. This can be useful for incremental updates of Workspace Model-related caches.

If the subscription should be set up after a project is opened, use ProjectActivity. This is also the way to migrate from the existing WorkspaceModelChangeListener-based approach. Keep in mind that it might miss the few first updates of the model, which is expected because the initial version of the storage will be provided.

Since this listener is asynchronous, there is no guarantee that indexes built on top of WorkspaceFileIndex or the project model will be updated.

Incremental Computation

To perform incremental computations, the last known version of the storage and the event of updates is required. This can be obtained by collecting indexed events. Assume we build some index and keep it updated:

workspaceModel.eventLog.collectIndexed { index, event -> if (index == 0) { buildIndex(event.storageAfter) } else { updateIndex(event) } }
Last modified: 20 August 2024