Open jimpick opened 5 years ago
I actually got frustrated with how this worked in another collaborative editor a little while ago and went around looking for better solutions. Sadly, I didn't find anyone who had a sane undo behavior, and sort of lost my faith in the undo-redo mechanic overall. Some editors I looked at solved the problem in the Alexander the Great style, by disabling undo altogether.
From the data model perspective, it seems clear that you must implement undo as a first class replicated type, or else you will end up in causality hell. I'm not sure what research exists on this subject w/δ-CRDTs, the only papers I've seen focus on undo with operation-based CRDTs.
From a user perspective, the most intuitive thing is probably undoing all user input events (insertions as well as deletions) since the last idle input event or periodic "tick" (whichever is nearer). Codemirror has its own edit stack that mixes real user input and update() events, so we would probably need to extract the history, modify it to remove only the user events, and write it back (while blocking the UI to prevent further updates).
The best hack solution I can think of is to peek at the history, undo any user input events until the first update() coming from the network, and send a corresponding edit event.
Overall, this feels like a treacherous area with a lot of opportunity for corruption and problems in synchronizing "our" state and codemirror history. A good way to simplify this might be replacing the CM history facilities with our own, but that might make us less compatible as a drop-in solution.
I see that automerge tackled that issue on the CRDT level recently:
@jimpick their approach (generate "negated" versions of history events) is similar to my intuition, but their system is OP-based (I think, based on https://github.com/automerge/automerge/blob/master/backend/op_set.js), which makes that easier
ProseMirror (a relative of CodeMirror, by the same author) also has collaborative editing support. I believe that what they do is keep track of a stack of changes, and then they can rewind back to a checkpoint, and replay the changes (minus what was undid by a particular writer).
Of course, it's not very well defined what users should expect when they hit undo on some text they added, but some other writer modified... (I'm going to guess that all future modifications get tossed)
tossing all your changes since some checkpoint + any causally related 3rd party changes seems to be reasonably intuitive for the user, though I'm not sure how the 3rd party will feel about it -- you would probably need some heuristics to keep the maximum set of tossed changes reasonable
I definitely support disabling undo altogether as a short-term fix
Right now, if the user hits their "Undo" key sequence eg. Command-Z or Ctrl-Z, they might (surprisingly) undo the edits of another user that is concurrently editing the same doc.
There are other multi-user editors that use Codemirror. It might be good to investigate what they are doing for this.