Undo/Redo

Collabs does not have built-in undo/redo support, but here are some ideas for how to incorporate it into your own app.

Local Undo

The easiest form of undo is a “local” undo, which undoes local operations in reverse order (typical Ctrl+Z behavior).

In principle, you can implement this similarly to a non-collaborative app: maintain a stack of undo commands to execute; after each local operation, push an “inverse” operation onto the stack; to undo, run the stack’s top command.

Quill does this for us in our rich-text editor demo: it maintains its own undo stack, used for Ctrl+Z.

A local operation’s inverse should undo that operation’s effects from the local user’s perspective. For example:

  1. Set the value of a CVar -> Set the CVar to its previous value.

  2. Add a value to a CValueSet -> Delete that value.

  3. Insert a character into a CText -> Delete the character’s Position. Unlike an index, the Position moves around to account for concurrent operations. You can get the position (after inserting) using CText.getPosition, then delete it with help from indexOfPosition.

  4. Delete a text character -> Insert the same character at the index where its Position would be, with help from getPosition and indexOfPosition(pos, “right”).

  5. Delete a value from a CList -> Originally call CList.archive instead of CList.delete, then undo with CList.restore.

Notes:

  • These undo operations are approximate; they do not give the exact same result as if the operation never happened.

  • Observe that we use Positions instead of indices when lists are involved. This makes sure that undo operations move around to account for concurrent operations, like a cursor.

    (In our rich-text demo, Quill instead does its own internal transformations to adjust indices.)

Group, Selective, and Exact Undo

More complicated forms of undo include:

  • Group undo - Allow users to undo each others’ operations.

  • Selective undo - Allow the user to undo operations anywhere in the history, not just in a stack.

  • Exact undo - Undo gives the exact same result as if the operation never happened.

The paper Supporting Undo and Redo for Replicated Registers in Collaborative Applications (Brattli, Yu 2015) discusses these in a CRDT context.

We do not plan to support these directly in Collabs, but if you are ambitious, you could implement them in a custom Collab.