-
Notifications
You must be signed in to change notification settings - Fork 71
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 #361
Merged
Merged
Undo #361
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The value of left_prior was wrong
* feat: tree undo
fix: type err
zxch3n
force-pushed
the
zxch3n/loro-560-undoredo
branch
from
May 20, 2024 03:50
7ea4de5
to
3fc7120
Compare
zxch3n
commented
May 20, 2024
Leeeon233
reviewed
May 20, 2024
Leeeon233
approved these changes
May 20, 2024
Merged
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Problem Description
What basic properties need to be satisfied after implementing undo/redo? For a single editor, the properties are clear:
However, in a collaborative environment, this becomes more complex. According to the behaviors of most collaborative applications, undo should be local, meaning users can only undo their actions while retaining others' edits. (If you want to revert to a previous version, including others' edits, we call that revert, which is also supported in this PR).
We initially considered directly supporting undo/redo operations on CRDTs, but what if a user suddenly undoes a very old operation? Since we use the REG algorithm, constructing CRDTs for undo computation would require backtracking to a very old version, making it difficult to reclaim historical operations in the future: you never know if a peer will suddenly undo a very old operation. Therefore, we do not plan to support undo/redo operations directly.
However, we found that the OT algorithm has already considered how to undo peer operations while retaining local operations. But as we know, OT is centralized, while CRDTs are decentralized. Would this break the properties of CRDTs? Actually, it won't because we treat the "local" copy as the so-called centralized role in OT, using it only to compute the operations for undo and redo. We don't use OT to solve the conflicts of concurrent edits.
Brief Introduction to the Undo/Redo Algorithm on OT
Our final solution is similar to how we build undo with OT. The basic principles are as follows:
Define an
apply(doc: Doc, e: Diff) → Doc
function, which applies the changes ofe
todoc
.Define a
compose(a: Diff, b: Diff) → Diff
function, so thatapply(doc, compose(a, b))
equalsapply(apply(doc, a), b)
.Define an
inverse(a: Diff) → Diff
function, which satisfiesdoc = apply(apply(doc, diff), inverse(diff))
.Define a
transform(a: Diff, b: Diff) → Diff
function, which has the following property:apply(apply(doc, b), transform(a, b))
equalsapply(apply(doc, a), transform(b, a))
, so it can be used in OT to handle concurrent edits: for example, ifa
andb
are two concurrent operations, the transform function can calculate what new operations should be applied to both documents to restore consistency.How to implement Undo Redo using the above primitives?
diff
, add a recordinverse(diff)
to the undo stack.r
. Traverse the undo stack from top to bottom, let the current undo stack'sdiff
bed_i
:d_i := transform(d_i, r)
r := transform(r, d_i)
The specific principle is shown in the figure below.
Limitations
m..k
made by A does not guarantee the result equals the doc with the version vector of { A: m, …rest }.x
is not retained.Reproduce.the.issue.in.Microsoft.Word.mp4
Invalid Experimental Ideas
I previously proposed an idea on Twitter to implement Undo/Redo through a DiffCalculator https://x.com/zx_loro/status/1773900851108798795,
However, this idea does not work because although the algorithm can achieve undo, it fails when performing multiple consecutive undos followed by redos. This is because, under this method, recreating the deleted content causes subsequent undo operations to fail at the intended positions. For example, in "Hello world!", if we first highlight "Hello", then delete the entire text, and then perform two undos, the first undo restores "Hello world!", but the second undo needs to highlight "Hello". However, it is bound to the old "Hello" and not the newly created "Hello", thus failing to complete the operation.