Skip to content

Inverted Effects

Some extensions use state effects alongside document changes. For undo/redo to work correctly with these effects, the history needs to know how to invert them. The invertedEffects facet lets you register functions that produce the inverse of a transaction's effects.

When to use

Use invertedEffects when your extension dispatches StateEffects that change meaningful state and you want undo/redo to reverse those effects alongside the document changes.

Example: effect-based counter

Suppose you track a counter that increments on certain edits:

private val addToCounter = StateEffect.define<Int>()

private val counterField: StateField<Int> = StateField.define(
    StateFieldSpec(
        create = { 0 },
        update = { value, tr ->
            var result = value
            for (effect in tr.effects) {
                val e = effect.asType(addToCounter)
                if (e != null) result += e.value
            }
            result
        }
    )
)

Now register an inverted-effects provider so that undoing a transaction that added +1 will add -1:

private val counterExtension = counterField +
    invertedEffects.of { tr ->
        val inverted = mutableListOf<StateEffect<*>>()
        for (effect in tr.effects) {
            val e = effect.asType(addToCounter)
            if (e != null) {
                inverted.add(addToCounter.of(-e.value))
            }
        }
        inverted
    }

When the history undoes a transaction that carried addToCounter.of(1), it will now also apply addToCounter.of(-1), keeping the counter in sync with the document state.

How it works

The invertedEffects facet accepts functions of type (Transaction) -> List<StateEffect<*>>. When the history records a transaction, it calls all registered functions to collect the inverse effects. These are stored alongside the inverted document changes and replayed on undo/redo.

val invertedEffects: Facet<
    (Transaction) -> List<StateEffect<*>>,
    List<(Transaction) -> List<StateEffect<*>>>
> = Facet.define()

Key points

  • Register one function per effect type you want to make undoable.
  • The function receives the original transaction and should return effects that reverse the original effects.
  • Only effects that are not already handled by document-change inversion need this treatment. Document changes are inverted automatically.

Based on the CodeMirror Inverted Effects example.