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

Switch undo storage from a tree to a plain list #4777

Merged
merged 1 commit into from
Apr 24, 2023

Conversation

occivink
Copy link
Contributor

@occivink occivink commented Nov 12, 2022

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.

@occivink occivink force-pushed the undo-history-as-plain-list branch 2 times, most recently from 0ab5dc4 to 486616a Compare November 13, 2022 09:28
@TeddyDD
Copy link
Contributor

TeddyDD commented Nov 30, 2022

I've been running this branch locally for two weeks. The linear history is great. Less mental overhead when undoing.

@arrufat
Copy link
Contributor

arrufat commented Nov 30, 2022

If this gets merged, maybe we can pick up the newly freed <a-u> and <a-U> for undoing and redoing selection changes (#4725). What do you think @krobelus?

@mawww
Copy link
Owner

mawww commented Dec 1, 2022

I'll run with this merged for a while to get a feel for it.

@krobelus
Copy link
Contributor

krobelus commented Dec 8, 2022

If this gets merged, maybe we can pick up the newly freed and for undoing and redoing selection

I used to think so but I realized that selection undo is much closer to the jump list than to content undo. With #4807 <c-o> and selection undo might be used together, so maybe ctrl is better. Will try both.


I don't really use the inactive branches of undo history so I don't have an opinion on this one.
On a related note, maybe it's worth allowing the jump list (and selection history) to go back to any previous state, just like with content undo. Probably hard to come up with an intuitive UI. Today when I want to preserve a jump list, I open a new client which works alright.

krobelus added a commit to krobelus/kakoune that referenced this pull request Dec 8, 2022
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.
@krobelus
Copy link
Contributor

krobelus commented Dec 8, 2022

I've implemented the equivalent behavior for selection undo and the jump list.
If anyone wants to try, it's in 03bfbe0 where I've also included this PR.

EDIT: I'll push newer versions to branch nonlinear-selection-undo.

krobelus added a commit to krobelus/kakoune that referenced this pull request Dec 10, 2022
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.
krobelus added a commit to krobelus/kakoune that referenced this pull request Dec 22, 2022
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.
krobelus added a commit to krobelus/kakoune that referenced this pull request Dec 26, 2022
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.
krobelus added a commit to krobelus/kakoune that referenced this pull request Dec 26, 2022
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.
@krobelus
Copy link
Contributor

Ok for selection undo this behavior is probably not right because I often want to do

  1. jump to A
  2. jump to B
  3. jump to C
  4. use <c-o> to jump back to B
  5. jump to D
  6. use <c-o> to jump back to B
  7. use <c-o> to jump back to A

with the list behavior, step 7 jumps to D first; so it would required two extra <c-o>.
For selection history it's important to make excursions cheap. My new approach to navigate the undo tree is in #4816 (still experimental).

Not sure if this applies to content undo. I find that tree navigation more intuitive than current <c-u>.. will see which one is easy to use in practice

krobelus added a commit to krobelus/kakoune that referenced this pull request Dec 30, 2022
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
@occivink occivink force-pushed the undo-history-as-plain-list branch from 486616a to e0d33f5 Compare April 17, 2023 08:36
@mawww
Copy link
Owner

mawww commented Apr 18, 2023

Heads up: I've been running with this for a while, and I think I'll merge it soon.

@mawww mawww merged commit a4918f9 into mawww:master Apr 24, 2023
@Screwtapello Screwtapello mentioned this pull request Apr 24, 2023
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.

5 participants