Asynchronous Initialization of Plugin State

I’m trying to re-write a syntax highlighter plugin that dynamically imports a language when it’s not statically defined. However, import("...") is asynchronous, and the plugin state init method is synchronous.

In previous discussions about async decorations [1], it was mentioned that asynchronous callbacks should dispatch a transaction with a meta property, that would be intercepted by the plugin state’s apply method.

However, I’m unsure how to implement this in state init. My question is: How can you access the editor view object needed to dispatch a transaction, given that only config and state are given as arguments to state init?

The current code I’m working with is on this github gist.


[1] previous discussions:

One way to do this would be to give your plugin a plugin view which schedules the dynamic loading and dispatches the transaction when appropriate. Or you can somehow arrange to have a dispatch function that knows about wherever it is you store the view passed as an option to your plugin construction logic.

In giving plugin a view object, is the way to allow communication between state.init, state.apply and view.update to use a keyed, transaction meta property?

For example, if we wanted the state.init method to tell the update method to dynamically import a language, which upon finishing, the update method itself tells the state.apply method to recalculate decorations.

I’m still a bit confused how the state.init can communicate with view.update via a transaction meta, since it only has access to state but not view.

Ideally, state.init computes a decorationSet with existing languages, and sets a set of languages to view.update to import, and then view.update sends a set of languages successfully imported to state.apply to append new codeblock decorations.


An alternative may be to set the decorationSet in state.init to empty, and then just have the view.update method iterate through the doc’s codeblocks and see which languages are needed to be imported; then, it sets a transaction meta for state.apply to see.

However, would there be a performance drawback from this approach of iterating through all codeblocks per update, as opposed to that calculation done in state.apply? I’m not sure if state.apply or view.update is more frequently called.

It doesn’t. Rather, it starts in a state that indicates something has to be asynchronously loaded, which the plugin view recognizes. It then does the loading and creates a transaction that allows the state to update further.

In normal use, view update and state apply will be called about the same amount. Iterating through the document is typically not expensive.

1 Like

It doesn’t. Rather, it starts in a state that indicates something has to be asynchronously loaded, which the plugin view recognizes. It then does the loading and creates a transaction that allows the state to update further.

Thanks marijn! :slight_smile: This line helped a lot. The final implementation I ended up with was creating a global set of languages to import that state.init, state.apply, and view.init could all read and write to: view.init would resolve all imports and dispatch a no-op transaction which state.apply would respond to.