super_editor icon indicating copy to clipboard operation
super_editor copied to clipboard

Undo/Redo (with snapshots + one-way commands)

Open matthew-carroll opened this issue 1 year ago • 1 comments

Undo/Redo (with snapshots + one-way commands)

This is an undo/redo concept that's intended to avoid some of the complexities of the reversible command undo/redo version (#1881) by using Editable state snapshots, combined with a history of one-way EditCommands.

With this approach, when the user wants to undo a change, the Editables are completely reverted to their most recent "snapshot" and then all historical EditCommands are replayed, except the most recent EditCommand, thus achieving undo.

This approach requires that Editables be capable of providing some kind of immutable snapshot. It might be a Dart data structure, it might be JSON, etc. The format doesn't matter. Each Editable needs to be able to provide some kind of snapshot, and then restore itself with that snapshot. This approach also re-runs a number of commands when undo'ing the previous command. This extra compute cost allows us to avoid the complexity of implementing reversible commands.

matthew-carroll avatar Mar 20 '24 03:03 matthew-carroll

@miguelcmedeiros @brian-superlist @knopp - Can you guys give this PR a try in Superlist to see if everything still works on your end? I don't expect that you'll use the new undo feature, but I'm doing surgery on the editor system to implement undo/redo.

There's still more work to do in this PR, but I think at this moment we're at a good place to start finding where it breaks things.

matthew-carroll avatar May 27 '24 00:05 matthew-carroll

@Jethro87 @jmatth - Please let me know if you have any thoughts about the approach in this PR. Things are starting to solidify for undo/redo and related behaviors. The tests added in this PR should provide a high level look at the new behaviors.

matthew-carroll avatar May 31 '24 00:05 matthew-carroll

@angelosilvestre I'll check your comments, by this PR is still at about the 75% mark. I just tagged you so you could start to see what's going on.

matthew-carroll avatar Jun 01 '24 01:06 matthew-carroll

@matthew-carroll I haven't gone through every line of this PR but here are my thoughts from the code I have read and the behavior I've observed in the example app so far:

Lone selection changes shouldn't be included in the undo stack

Based on some of the comments in this PR I think this might be on your radar already but thought I'd call it out anyway. In the current implementation moving the seletion gets included in the history, which is not true of other text editors I've tested against and I believe not what end users would expect. Consider the attached video: during undo/redo the selection moves between the c and f nodes without any actual changes taking place. This is something I've struggled to get right in my current custom history system since my attempted solutions have so far all led to edge cases where the selection can get out of sync with the nodes in the document and cause an exception to be thrown.

https://github.com/superlistapp/super_editor/assets/1316184/b2a692b3-aa80-48cf-9e74-9cb714e75987

Support for other undo algorithms would be nice

As-is this PR has a stack based history system baked into the Editor. This is a reasonable default but we would like to eventually support alternate history systems such as undo branches. Ideally the history functionality could be contained it its own interface, with the Editor being provided an object that satisfies that interface when it is initialized. If that's not practical or other funders prefer the history functionality stay in the Editor class, we'd just prefer that the history methods be reasonably easy to override in subclasses.

jmatth avatar Jun 07 '24 19:06 jmatth

@jmatth There's behavior in this PR that merges back to back selection changes. Can you be more specific about which selection changes should and shouldn't be captured? Obviously when the selection changes as the result of a content change, we need to capture that. But please let me know what policies you expect beyond that.

WRT a branching history, do you have any resources where I can learn about that concept?

matthew-carroll avatar Jun 07 '24 21:06 matthew-carroll

For selection changes: I don't think undo/redo should ever produce a change that only modifies the selection, which is what happens from about the 6 second mark onward in my video: I delete content from two separate nodes, then repeatedly use ctr-z and ctrl-shift-z to step backward and forward in the history, which only moves the selection between the two nodes.

It may also make more sense to phrase the requirement in terms of what does need to change for it to register in the history system, such as "an undo/redo operation must always produce a change in the Document", or even more generically "an undo/redo operation must always produce a change in one or more Editables marked as participating in the history system".

For branching history: I've only ever seen it implemented in Vim/Neovim. Instead of storing the history in stacks and trashing the redo stack when the user begins making new changes, vim stores its history as a tree and new changes just create a new branch. Here is the quickest explanation I could find on Youtube and here is plugin I've seen most people use to visualize the tree. To be clear, I'm not asking for this feature to be included, just that the final API keep in mind that history implementations might need to be more complex than simple undo/redo stacks.

jmatth avatar Jun 08 '24 01:06 jmatth

I'm going to merge undo/redo in its current state so that we can begin to accumulate bug reports and other things.

Undo/redo, as it exists right now, accumulates all changes and never collapses change history down to a snapshot. I'll plan to address that next. Since I didn't receive any feedback from stakeholders on this PR, I'm going to assume that everyone thinks that's OK for now.

matthew-carroll avatar Jul 08 '24 23:07 matthew-carroll

Is there a way to limit the size of the undo stack to manage memory usage?

KevinBrendel avatar Jul 09 '24 13:07 KevinBrendel

@KevinBrendel yes we can, but we need to decide on what strategy(s) to use. Please comment here with your needs and thoughts: https://github.com/superlistapp/super_editor/issues/2154

matthew-carroll avatar Jul 09 '24 22:07 matthew-carroll