Asyncronous dispatching of state effects

I’m writing a plugin to show signature help information on specific key presses. However, receiving the signature help information could be asynchronous.

/// The state effect that updates the set of active diagnostics. Can
/// be useful when writing an extension that needs to track these.
export const setSignatureHelpEffect = StateEffect.define<Tooltip[]>()

const signatureHelpState = StateField.define<Tooltip[]>({
  create() {
    return []
  },

  update(value, tr) {
    for (const effect of tr.effects) {
      if (effect.is(setSignatureHelpEffect)) {
        return effect.value
      }
    }
    return value
  },

  provide: (f) => showTooltip.computeN([f], (state) => state.field(f)),
})

const signatureHelpPlugin = ViewPlugin.fromClass(
  class {
    constructor(readonly view: EditorView) {}

    update(update: ViewUpdate) {
      const { signatureHelpTriggerCharacters } = update.state.facet(signatureHelpConfig)
      for (const tr of update.transactions) {
        if (tr.isUserEvent('input.type')) {
          // get typed character
          const currentPosition = tr.state.selection.main.head
          const typedCharacter = tr.state.doc.sliceString(currentPosition - 1, currentPosition)
          if (signatureHelpTriggerCharacters.indexOf(typedCharacter as SignatureHelpTriggerCharacter) > -1) {
            this.showSignatureHelp({
              position: currentPosition,
              options: {
                triggerReason: {
                  kind: 'characterTyped',
                  triggerCharacter: typedCharacter as SignatureHelpTriggerCharacter,
                },
              },
            })
          } else {
            this.removeSignatureHelp()
          }
        } else if (tr.isUserEvent('delete') || tr.isUserEvent('move') || tr.isUserEvent('select')) {
          this.removeSignatureHelp()
        }
      }
    }

    /**
     * Gets the signature help from the sources provided and dispatches an action to
     * set it.
     *
     * @param context
     */
    async showSignatureHelp(context?: SignatureHelpContext) {
      if (!context) {
        this.view.dispatch({ effects: [setSignatureHelpEffect.of([])] })
        return
      }
      /**
       * TODO: handle multiple signature help sources
       */
      const sources = this.view.state.facet(signatureHelpConfig).sources
      const firstSource = sources[0]

      if (firstSource) {
        const item = await firstSource(context)
        this.view.dispatch(setSignatureHelp(item, context))
      }
    }

    async removeSignatureHelp() {
      // remove signature help
      this.view.dispatch({ effects: [setSignatureHelpEffect.of([])] })
    }
  },
)

This is working great for displaying the tooltip when i need it to, however is never removing the tooltip. I run into this error:

Calls to EditorView.update are not allowed while an update is in progress

which seems to make sense as i’m dispatching async updates. My question is what is the correct paradigm for dispatching an update that can potentially take some time to be received? Is there an example we can point to?

Fixed by wrapping the function calls in a setTimeout – the lint example was helpful here

1 Like