-
Notifications
You must be signed in to change notification settings - Fork 727
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
Switch undo storage from a tree to a plain list #4777
Conversation
0ab5dc4
to
486616a
Compare
I've been running this branch locally for two weeks. The linear history is great. Less mental overhead when undoing. |
I'll run with this merged for a while to get a feel for it. |
I used to think so but I realized that selection undo is much closer to the jump list than to content undo. With #4807 I don't really use the inactive branches of undo history so I don't have an opinion on this one. |
Today, selection undo and jump list commands only travel the active branch of the selection history. Let's make it possible to visit branches that were undone and abandoned. I'm not sure how the user interface should look like. For now, let's make the existing shortcuts traverse the history tree in-order. This is equivalent to the undo behavior proposed in mawww#4777 THe upside of this UI is that it's dead simple to use because it needs no additional keys. The potentially annoying thing is that it requires to press <c-i>/<c-o>/<c-h>/<c-k> more often in some scenarios.
I've implemented the equivalent behavior for selection undo and the jump list. EDIT: I'll push newer versions to branch |
Today, selection undo and jump list commands only travel the active branch of the selection history. Let's make it possible to visit branches that were undone and abandoned. I'm not sure how the user interface should look like. For now, let's make the existing shortcuts traverse the history tree in-order. This is equivalent to the undo behavior proposed in mawww#4777 THe upside of this UI is that it's dead simple to use because it needs no additional keys. The potentially annoying thing is that it requires to press <c-i>/<c-o>/<c-h>/<c-k> more often in some scenarios.
Today, selection undo and jump list commands only travel the active branch of the selection history. Let's make it possible to visit branches that were undone and abandoned. I'm not sure how the user interface should look like. For now, let's make the existing shortcuts traverse the history tree in-order. This is equivalent to the undo behavior proposed in mawww#4777 THe upside of this UI is that it's dead simple to use because it needs no additional keys. The potentially annoying thing is that it requires to press <c-i>/<c-o>/<c-h>/<c-k> more often in some scenarios.
Today, selection undo and jump list commands only travel the latest branch of the selection history. If I undo a selection change and then make another change, the undone change cannot be redone - it lives on an old branch of the selection history. Let's make it possible to revisit such selections. There are a couple of ways to implement this. This version allows to toggle between redo branches whenever there is more than one. Hopefully this is not too hard to use. A previous approach implemented a behavior equivalent to mawww#4777, but that turns out to be clunky for selection undo because when I use <c-o>, I typically only want to travel the active branch and not revisit any abandoned branches. Another approach would be to mimic <a-u>. This week, I finally realized how that command works: it simply decrements the index of the current history item. Since the history tree is stored in a vector, this allows to visit any state. However this also means that it includes weird jumps between branches in the history. I somehow thought that it only jumps between the leaf nodes.. Anyway, I find this a bit unintuitive.
Today, selection undo and jump list commands only travel the latest branch of the selection history. If I undo a selection change and then make another change, the undone change cannot be redone - it lives on an old branch of the selection history. Let's make it possible to revisit such selections. There are a couple of ways to implement this. This version allows to toggle between redo branches whenever there is more than one. Hopefully this is not too hard to use. A previous approach implemented a behavior equivalent to mawww#4777, but that turns out to be clunky for selection undo because when I use <c-o>, I typically only want to travel the active branch and not revisit any abandoned branches. Another approach would be to mimic <a-u>. This week, I finally realized how that command works: it simply decrements the index of the current history item. Since the history tree is stored in a vector, this allows to visit any state. However this also means that it includes weird jumps between branches in the history. I somehow thought that it only jumps between the leaf nodes.. Anyway, I find this a bit unintuitive.
Ok for selection undo this behavior is probably not right because I often want to do
with the list behavior, step 7 jumps to D first; so it would required two extra Not sure if this applies to content undo. I find that tree navigation more intuitive than current |
Today, selection undo and jump list commands only travel the latest branch of the selection history. If I undo a selection change and then make another change, the undone change cannot be redone - it lives on an old branch of the selection history. Let's make it possible to revisit such selections. There are a couple of ways to implement this. This version allows to toggle between redo branches whenever there is more than one. Hopefully this is not too hard to use. A previous approach implemented a behavior equivalent to mawww#4777, but that turns out to be clunky for selection undo because when I use <c-o>, I typically only want to travel the active branch and not revisit any abandoned branches. Another approach would be to mimic <a-u>. This week, I finally realized how that command works: it simply decrements the index of the current history item. Since the history tree is stored in a vector, this allows to visit any state. However this also means that it includes weird jumps between branches in the history. I somehow thought that it only jumps between the leaf nodes.. Anyway, I find this a bit unintuitive.
Whenever a new history node is committed after some undo steps, instead of creating a new branch in the undo graph, we first append the inverse modifications starting from the end of the undo list up to the current position before adding the new node. For example let's assume that the undo history is A-B-C, that a single undo has been done (bringing us to state B) and that a new change D is committed. Instead of creating a new branch starting at B, we add the inverse of C (noted ^C) at the end, and D afterwards. This results in the undo history A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to A-B-D but without having lost the C branch of the history. If a new change is committed while no undo has been done, the new history node is simply appended to the list, as was the case previously. This results in a simplification of the user interaction, as two bindings are now sufficient to walk the entire undo history, as opposed to needing extra bindings to switch branches whenever they occur. The <a-u> and <a-U> bindings are now free. It also simplifies the implementation, as the graph traversal and branching code are not needed anymore. The parent and child of a node are now respectively the previous and the next elements in the list, so there is no need to store their ID as part of the node. Only the committing of an undo group is slightly more complex, as inverse history nodes need to be added depending on the current position in the undo list. The following article was the initial motivation for this change: https://github.com/zaboople/klonk/blob/master/TheGURQ.md
486616a
to
e0d33f5
Compare
Heads up: I've been running with this for a while, and I think I'll merge it soon. |
See commit message for details.
I've not tested too extensively, but I figured I would put a PR out already so that others can test if they wish. From my brief experimentation it seems like good change.
I was wondering if it might not be interesting to optimize the 'reverse' node. Instead of computing ^C at the moment that we commit a new group, we could have nodes be some kind of union between a full node with the full content, or a 'reference' node with just an node index + a boolean flag (since a reference might be a ^C, or a ^^C). The inverse would be computed on demand, which I think would save some memory.
edit: after reading a bit about the storage of the undo modification,
StringDataPtr
looks to be reference counted and so there should be no memory duplication in case significant buffer modifications (for example pasting large text).There might still be some benefit in using the approach described above, but not as large I thought.
If this is accepted, I will adapt the documentation later.