At Replit, we have this pattern which is used in a lot of our plugins:
class MyPluginImpl implements PluginValue {
postUpdate(update: ViewUpdate) {
// safe to do dispatches here
if (whateverCondition) {
update.view.dispatch({ /* ... */ })
}
}
}
const myPlugin = ViewPlugin.fromClass(MyPluginImpl, {
provide: (v) => EditorView.updateListener.of((update) => {
update.view.plugin(v)?.postUpdate(update)
}
})
postUpdate
is helpful in a lot of situations. It enables you to make update listeners that have internal state, without using something like closures.
For us, itâs often needed because an extension needs to manage some part of the editorâs state, but that management logic just canât go into something like a StateField
. Handling asynchronous state is usually the reason why - often an update to the editor means that some asynchronous process that was running needs to be aborted, started, or restarted. That process may have some information about it stored in EditorState
, e.g. whether or not itâs running/loading, so we may need to do a dispatch when we start/abort/restart the process. This is our most common use case, e.g. our inline AI suggestions use this. Most of the suggestion state is in a StateField
, but most of the logic for updating that field lives in a view plugin because thatâs the only reasonable place to do asynchronous requests.
Another use case for us is updating things outside the editor, which then may have reactive effects which cause editor dispatches. It is desirable to avoid this entirely, but for our React-inside-of-CodeMirror library, we definitely needs this, or at least workarounds similar to this.
We could do something like queueMicrotask(() => view.dispatch({ ... })
instead of using an update listener, but this has issues with making view updates sort of slightly asynchronous. As-in, if you called view.dispatch
, and then immediately accessed view.state
afterwards, that state wonât yet reflect anything queued up by queueMicrotask
yet. Using update listeners avoids this, by basically letting a single dispatch turn into multiple dispatches.
So, our request here is pretty simple: add a method very similar to update
that runs after update
and only when it is safe to do dispatches to the view, just like update listeners. The only real drawback I can think of to adding this method is maybe confusion about what method to use when learning the API, and the possibility of infinite update loops. The latter is already possible with update listeners, so IMO itâs fine.