Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 24 commits into from
Jul 8, 2024

Conversation

matthew-carroll
Copy link
Contributor

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 matthew-carroll force-pushed the undo-redo_snapshot-with-commands branch from 4363290 to 50deab3 Compare May 26, 2024 06:08
@matthew-carroll matthew-carroll marked this pull request as ready for review May 27, 2024 00:35
@matthew-carroll
Copy link
Contributor Author

@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
Copy link
Contributor Author

@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.

Copy link
Collaborator

@angelosilvestre angelosilvestre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments. I'm wondering if we should have a way to notify the commands that were undone/redone, for the cases where commands could be used to start actions outside of the editor.

For example, if an app has a reaction that, when an user tag is typed on a task, it runs a command that calls to the backend to assign the user to this task, should the app be able to unassign the user when the command is undone?

Comment on lines +117 to +119
// End the editor transaction for all deltas in this call.
editor.endTransaction();

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a try/finally to still end the transaction even if something crashes in between?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's wait until we see such a problem come up. It's not obvious to me what would cause that, and if it happens, how we can confidently recover.

super_editor/lib/src/core/editor.dart Outdated Show resolved Hide resolved
super_editor/lib/src/core/editor.dart Outdated Show resolved Hide resolved
});
});
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a test to ensure that, when we undo or redo, we sync our editing value with the IME ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What might that test look like?

@matthew-carroll
Copy link
Contributor Author

@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.

@jmatth
Copy link
Contributor

jmatth commented Jun 7, 2024

@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.

Recording.2024-06-07.151220.mp4

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.

@matthew-carroll
Copy link
Contributor Author

@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?

@jmatth
Copy link
Contributor

jmatth commented Jun 8, 2024

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.

@matthew-carroll
Copy link
Contributor Author

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 matthew-carroll merged commit 7ed7d36 into main Jul 8, 2024
12 checks passed
@matthew-carroll matthew-carroll deleted the undo-redo_snapshot-with-commands branch July 8, 2024 23:13
github-actions bot pushed a commit that referenced this pull request Jul 8, 2024
No snapshot support yet. All changes are accumulated for every editable.
@KevinBrendel
Copy link
Contributor

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

@matthew-carroll
Copy link
Contributor Author

@KevinBrendel yes we can, but we need to decide on what strategy(s) to use. Please comment here with your needs and thoughts: #2154

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants