Problems with redoing an effect

Hi,

I’m trying to implement something like “interactive diff mode”: when user deletes text, I leave the text where it was, but apply red background. This works fine and undo works out of the box, but I’m struggling with redo. I tried to follow inverted effects example, but my invert function does not receive any transactions with effects.

This is my state effect:

const markDeletion = StateEffect.define<{ from: number, to: number }>({
    map: ({ from, to }, change) => ({ from: change.mapPos(from), to: change.mapPos(to) })
});

// and this is inverted version, just in case:
const unmarkDeletion = StateEffect.define<{ from: number, to: number }>({
    map: ({ from, to }, change) => ({ from: change.mapPos(from), to: change.mapPos(to) })
})

StateField is:

export const deletions = StateField.define({
    create(state) {
        return Decoration.none;
    },
    update(deletions, transaction) {
        deletions = deletions.map(transaction.changes);
        for (let e of transaction.effects) {
            if (e.is(markDeletion)) {
                // This applies the Decoration
                deletions = deletions.update({ add: [removedText.range(e.value.from, e.value.to)] })
                continue
            }
        }
        return deletions
    },
    provide: f => EditorView.decorations.from(f)
});

And here’s the function I call to apply the effect on delete UserEffect:

function onDelete(transaction: Transaction) {
    const changes = transaction.changes.invert(transaction.startState.doc);
    const effects: StateEffect<unknown>[] = [];

    changes.iterChangedRanges((fromA, toA, fromB, toB) => {
        effects.push(markDeletion.of({ from: fromB, to: toB }));
    });

    return { changes, effects };
}

I use transaction.changes.invert to get the deleted text back into the document. Now the below function is my handler for inverted effects and it only console.logs effect for the first transaction (when the user removes text), never for undo and never (and I guess this is the problem) for redo:

const invert = invertedEffects.of((transaction) => {
    const inverted: StateEffect<unknown>[] = [];

    transaction.effects
        .forEach((effect) => {
            console.log(effect);
            if (effect.is(markDeletion)) {
               inverted.push(unmarkDeletion.of(effect.value));
               return;
            }
            if (effect.is(unmarkDeletion)) {
               inverted.push(markDeletion.of(effect.value))
               return;
            }
       });
    return inverted
}

Any help is appreciated, as I’m struggling with this problem for few days now. I think that the issue is that I’m reinserting the text, but I’m not sure. Thanks.

invertedEffects functions should be called for every transaction that doesn’t have addToHistory set to false. Maybe your effects being mapped to nothing by going through the change-invertedchange steps? I’d recommend using a transaction filter, rather than a separately created transaction that reverts the previous one, for this. Or, possibly even less problematic, don’t keep deleted text in the document, but store it alongside it and display it as widgets.