246 comments

[ 3.4 ms ] story [ 274 ms ] thread
Sounds similar to vim’s time travel command

:earlier 30m

Would be useful if this had a comparison with Emacs, which also resolves the GURQ.
Emacs already implements exactly what they are proposing: an undo operation is just another action added to the chain of states, which can itself be undone.
This is exactly how undo works in Emacs out of the box. Personally, I prefer to install the undo-tree package and manually browse through the tree of undo paths. I'm not sure why this author finds a tree unacceptable for this purpose.
Because it requires more than two commands
Additionally, emacs will filter undo/redo’s to changes within a region. If you select a region (no new command to learn) and use undo/redo, it will only perform those that affect text completely within the region. This is a superpower that delights me each time use it.
I did not know this, despite having used emacs for > 20 years - thank you!
Yes! Having regional undo is super useful sometimes. I switched from emacs to vim a long time ago, but still miss this feature (although not enough to go searching to find an equivalent vim plug in I guess). Lazy web?
Well if you want to return to Emacs, I can attest as a former long-time vim user that the emulation layers for vim modes in Emacs are fantastic! They're really the only good part of vim, as the configuration language was always a bit less desirable than elisp, imo.
I've tried in past but always found the vim modes to be a bit lacking. My last attempt was probably 6 or 7 years ago though so maybe worth another shot!
Evil mode is the modern standard I believe. Not a user myself, but I hear good things.
I ported my vim configuration to Evil Mode seven years ago and haven’t looked back. Evil Mode is the best done vi emulation layer I’ve seen and I think it even improves on vim in at least one area (the behavior of `I` in visual line mode).
There is a very opinionated and batteries-included "Vim-y" Emacs called Doom [0]. A decent way to approach this if you don't want to start writing elisp outta the gate to have fancy editor features is to learn enough of the Doom config to tweak it to your liking, then add some elisp customization in as needed. I personally use this approach, usually cribbing config and elisp tweaks from the top contributors' configs.

The Evil layer that Doom and most everyone seems to use for Vim modes works really well, and has a lot of ways to tweak things (e.g. changing `j` to `gj` for going through line breaks in normal mode; I forgot what that setting is called...).

There is something to be said by bootstrapping your config entirely from scratch instead of using a "config framework" like Doom, but that can be too daunting and end up preventing one from trying things out.

[0] https://github.com/doomemacs/doomemacs

I switched from Emacs to IDEA because IDE features are frankly unrivalled but I still think Emacs does the text editing part plainly better
Do you know a way to keep the region selected after undoing so as to continually undo within the region?
That’s the default behavior; perhaps you have customized something that changed it.
That region based undo is fantastic and has saved my bacon so many times! I really wish more apps had it, though I shudder to think of the implementation complexity.
And it is exactly why I find Emacs undo so hard to work with. But maybe I will get it now I've read this article.
You can, if you like, install the 'vundo' package, which gives you a tree-based overview of your undo history, making it easy to switch between branches and go backward and forward. It's basically like the undo-tree view for the default undo mechanism.
Two suggestions that might make it easier for you. There's a package called `undo-fu` that puts a thin layer on top of the undo system to keep the behavior strictly linear.

And built in, there are the `undo-only` and `undo-redo` commands. Unlike the base `undo` they will only walk in one direction.

Note that Emacs undo is not tree based, but works as described in the article. The tree based undo is a separate package one has to install.
See `:h undo-persistance` for that feature. In particular you want to do `set undofile` in your vim config
I'm not sure how you deduced that I'm actually a vi user, but it's interesting to see that vim has similar support but using separate linear undo and redo commands g- and g+ . undo-persistance and undofile seem to be orthogonal to that though, I'm even less sure why you're bringing that up.
He literally says why at the top of the post. People find it too complicated.
I confess I didn't get that the post was about emacs. Reading the intro, it sounds more like it assumes nobody ever implemented something. And then goes on to describe what sounds a lot like how emacs works.
That’s the impression I got as well, since it never mentions Emacs at all and then describes exactly how Emacs works.
No, he says most people do not actually implement the interface to make it make sense. So they have to make ctrl-z and ctrl-shift-z do the logical linear thing which requires all sorts of craziness which is what is complicated to do correctly (e.g. without breaking user expectations).
No they don’t. The method they are praising is already the base implementation for emacs.
Sounds like this solution would best be implemented with an undo-tree, but also a linear history mapping with pointers to that tree - the linear history would just log movement within the tree over time e.g. undo-redo would just be a backwards-forwards movement.
That's exactly what you get with undo-tree. The regular undo/redo commands continue working as normal, but you can also manually browse the tree if things get too nonlinear for you.
The one I saw had you browsing the tree, rather than a linear history representation of a tree.
Yeah, I spent a long while (years) using pretty much exactly the solution he describes, as implemented in emacs. It's not as good as a tree, because undo/redo cycles make it very annoying to traverse through the history. I greatly prefer undo-tree.
Make sure to check the value of `undo-limit`, as the low default value greatly (and needlessly, on modern machines) nerfs undo. It also applies to extensions like undo-tree and vundo I believe.

> undo-limit is a variable defined in ‘C source code’.

> Its value is 10000000

> Original value was 160000

Speaking of extensions, I find undo-tree pretty buggy. I might be one of a dozen people who actually love the default undo/redo mechanism.

I implemented something similar for a interactive whiteboard project once. The undo operation would simply be appended as another action that itself could be undone by re-doing it.

However, as you played with it, it became confusing because it ran counter to ingrained expected behavior of an undo/redo dual stack implementation. Eventually I implemented the classic approach.

> No, there is no undo "tree"[...]

This is still a tree, it's just implemented in an array.

This is still an acyclic node graph, it's just implemented as a tree.
Wll, it's a tree that is collapsed into an array - which makes it an array to all intents and purposes.

The point is that the "tree-ness" is never exposed to the user. This is the problem OP is claiming to have solved - the cognitive burden that the tree solution introduces.

The point is that the "tree" is not exposed to the user. I feel the author makes this extremely clear.
Yes a linked list is a degenerate tree, but was this worth pointing out?
Sounds like the Braid version of undo-redo.
Could you elaborate or provide a link? I haven't heard of the "Braid version of undo-redo" before.
Braid is a video game with time travel as a core mechanic
Braid is a game about time travel.

This is not like Braid at all, since the game works by "overwriting" your past actions (with a few other mechanics).

The Braid editor would be like "Normal" undo except a ghost cursor continues to make the edits you just undid. Would be a hilarious April fool's feature.

Even if you think you don't like games, Braid was really different
Alternatively, use Vim undotree:

https://github.com/mbbill/undotree

Or the earlier / later commands which function exactly as the article describes and are part of vanilla vim. You can even give them a time argument, for example :earlier 10m to go back ten minutes.
I found the article interesting because it's an approach that I could apply to tools other than text editors - it explains the general concept. I wouldn't have discovered it if someone just posted a link to a Vim add-on.
(comment deleted)
FYI if you use Jetbrains/PyCharm, it also provides this (and has saved me many times)

Just right click your file and view "Local History"

Yup. And it's full-text searchable, which I've found people generally don't realize. Fantastic feature.
Many times I want to undo to a previous state, but then apply again a few of the changes I just reverted, basically cherry-picking from the future (from the redo stack).

It would be interesting to be able to see all edits as individual patches/diffs that you can commit/discard individually. Like a mini automatic git inside the editor, automatic in the sense that every "edit" creates a commit, undo/redo move HEAD.

This would be entirely optional, you can continue using Undo/Redo as usual, but if you need to do a more complex history operation you can open this "git" view and operate on the edit tree directly.

Photoshop has this nice undo panel where you can see individual edits and click your way to the desired point in history:

https://www.bwillcreative.com/wp-content/uploads/2020/08/how...

One can imagine such a view in a text editor, where each entry shows a diff of what that step did.

Use local history in any JetBrains IDE.
And with the diff panel you can easily cherry pick selected changes to apply.
Seriously. This is one of those features I forget other people don't have access to. Anything less is just uninspired.
VS Code has this feature too, it's just not known.
I am not a JetBrains user, but it looks like that only applies to saved versions? Undo normally holds every typed change. https://www.jetbrains.com/help/idea/local-history.html#resto...
You’re usually autosaving. So it’s not every character, but maybe every minute and lots of other logical triggers like running the file. I’ve found it works very well.
In Emacs, if you select a region of text and invoke undo, it applies only to the selected text. Very useful sometimes.
Agreed.

When I write "foobar", delete the last three characters (so that now I have "foo", then I write a "t", I would like to be able to undo the previous deletion (giving me "footbar"), not just adding the last character.

Seems to me that would work especially well in editors that have the concept of verbs + objects, like vim's "delete until the end of the line" or "replace current word with...", but maybe it would work with all kinds of editors?

Should it be “footbar”? Or “foobart”? I could see a case being made for either.

  foo<<<bar===t>>>
I’m kidding, but only a bit: the (in hindsight, reasonable) lesson of the Pijul paper[1] is that if you want to do the most general merge and (therefore) avoid any arbitrary choices, you’re forced to extend your model from consistent files as sequences to files potentially containing conflicts as DAGs (or something even more complicated if you have data more complex than a single flat sequence or edits other than insertions). Or you can very carefully make a consistent set of arbitrary choices, like implementations of operational transformation usually do.

[1] https://doi.org/10.1016/j.entcs.2013.09.018

Depends on where the cursor is when you press redo, because it was originally inserted at the cursor position.
I would definitely not want the behaviour of undo/redo to depend on the current position of the cursor; that’s what paste is for.
This is what I thought the undo redo quandary would be. The fact that you can undo an undo seems like tablestakes to me.
If the changes "conflict" it might be best to use a "smart" editor that can somewhat figure out changes on a higher level.
In Atlassian Sourcetree you can stage the full current file, undo live changes in your editor, make different changes to your live document, and then reapply chunks of your prior changes from staging to live document (actually looks like undoing unstaged chunks).
You can do this with a tree-based persistent/functional data-structure, too.

For instance the single writer (per resource) in the evolutionary, append-only database system[1], I'm working on in my spare time, can simply revert the whole resource (resource is like a table in the relational DB jargon) to a past revision. Once a new commit is issued a new revision gets appended and the whole history in-between is preserved. Thus, you can, for instance, retrieve individual changes and cherry pick these from the revisions in-between.

[1] https://github.com/sirixdb

> No, there is no undo "tree", nor any complicated graphical user interface to go with. ... You've got your undos, your redos, and that's it. The underlying data structure is strictly linear, but all edit states are preserved and reachable ...

This is exactly how Emacs works!

Emacs has worked like this since its beginning in 1980s. It was even documented in the first manual (1981):

This might seem to pile one disaster on another, but it doesn't, because vou can always Undo the Undo if it didn’t help. (page 137 of manual)

I think there needs to be some sort of computing and software history lessons. It can offer great value in the current world of software development. It will save you from the trouble of rediscovering techniques that are already in use in classic battle-tested software.

(And really? You need to invent a cheesy acronym for this? If that's what it takes to sell the obvious these days, how about SLUR - Simple Linear Undo Redo?)

Of course emacs implements it as just a long linked list.
It doesn't need to be anything else.
Vim also has an undo tree, although you need a plugin to work with it easily (called "Undotree").
Yes but the article is specially calling out that using an undo tree is something no one wants.
And that you still want a linear path through your undos and redos...and that's something that Emacs does.
It's more of an implementation detail that is available to users. The article's simplification is also available via :earlier and :later, with the added bonus that they can take _time_ as a parameter as well as a simple revision count. “:earlier 1h” has been handy more than once.
Kakoune, like the more logical Vim it is, has both in a sensible arrangement: an undo tree with a current branch, which you can navigate up and down (u/U), and a linear history of the path you took through that tree, which you can navigate backwards and forwards (<a-u>/<a-U>). Unlike a single undo branch, you’re never afraid you’re going to lose your work when you need to go rescue a piece from a previous version; unlike a plain linear history like TFA proposes and Emacs uses, you’re not punished with a quadratic number of undo/redo pairs if you need a point behind several parallel do-overs of the same part. Unfortunately, without any sort of visible representation of all that I frequently find myself getting lost in the whole thing.

(Now that I’m thinking about it, Git’s commit tree and reflog play a similar pair of complementary roles locally.)

> This is exactly how Emacs works!

...and has been since forever. Thus I don't understand what the fuss is?!

(comment deleted)
In Anathem there were academics who spent their entire lives learning the history of academic scholarship for the express purpose of reminding everyone how often things are reinvented and preventing people from getting too excited when they have discovered something that was already known. I’m drawing a blank on their name, but it was one of the things about the book that I really enjoyed. It really helped to sell the setting, where the scientific method has been known and used continuously for 5,000 years or more.
The best form of history, undo and redo I've used is on hte various Jetbrains editors. It's a feature called Local History and is insanely useful.

1. It's always on. It never requires an explicit save or commit;

2. It automatically saves any changes and timestamps them;

3. You can view the state of a file or a tree at any moment in time and then pull out that file (or tree).

4. If you do that, it becomes part of the temporal history so you can always go back.

I find this completely natural because usually when it's necessary you think "Oh I need to unwind most of what I did in the last hour" or "I changed something I shouldn't have yesterday". You're not thinking about a line (or a tree) of changes. You're thinking about times and possibly ordering ("I changed X right before I changed Y").

VS Code has that feature too, but only for individual files.
I wish VS Code would expand on that feature, coming from IntelliJ. It's really annoying that you need to type the name of the file you want to see the local history of, even when it's the currently active file in the editor. There should be a sidebar widget for it that's active for whatever the current file is.

The cool thing with IntelliJ is if you accidentally deleted a file you can just touch a blank version of the file and the history comes back. Not sure if VS Code does that yet.

There is a sidebar widget. But it starts merged in the File Explorer view, and you need to drag it over from there to the sidebar to make it independent - https://stackoverflow.com/a/71522634

The widget is called "Timeline".

> The cool thing with IntelliJ is if you accidentally deleted a file you can just touch a blank version of the file and the history comes back. Not sure if VS Code does that yet.

VS Code does keep the history after file deletion, but I'm not sure how to access it from inside.

I had to manually rummage through the storage directory to find a file I accidentally deleted, but it was an file which was never saved (think Untitled), so it didn't had a path that I could try creating a blank file at.

Fantastic! Thank you so much.

I think it's a fairly common situation that people using programming tools (VS Code, git, vim etc) can go for years without realizing there is a built-in feature that can solve exactly a problem they always struggled to solve. I'm not sure how to surface that sort of functionality in the tool, but I'm glad HN is here to point it out. One similarly cool thing I learned in a recent comment is that if you press dot (.) on Github web interface, it will launch an in-browser VS Code instance to edit the file you are viewing. Nifty!

It helps to read the VS Code monthly update release notes, it takes about 3 min, but you'll be aware of all new features they introduce.
You are a faster reader than me. The October update has around 4,700 words plus plenty of images to digest. I understand you likely mean to review the highlights, but a non-trivial amount of information is in each months update. And if you haven't been keeping up for the past 6 years, it isn't easy to catch up now. Not to mention the breadth of the marketplace.
Or you can just look at the history of the folder.
Yep, I use Apple Time Machine for much the same purpose. Once you've set it up, it's always on, you can just go back in time (the granularity is once per hour, but that's been enough every time I've needed to use it) and restore (or make a copy of) any file or folder. Saved me quite a few times when Git has let me down.
> "Oh I need to unwind most of what I did in the last hour" or "I changed something I shouldn't have yesterday"

Vim's :earlier and :later can take a time parameter (i.e., 30m, 2h, 1d) which does exactly that.

Local History is hidden in the File Menu and does not have a shortcut. It is one of the most useful features in the Jetbrains editors, so I set Alt+Z as a keyboard shortcut (because Ctrl+Z is undo), which after a few years of using it still seems like a good idea.

One point that is missing from your description is that it always shows the previous state in the diff view, which also feels really natural to use (I only sometimes whish the diff markers were more distinguishable from the inspection and ToDo markers).

My way is a a ugly undo - copy - redo - paste.

Not efficient, sure, but I'm used to it and it solves my problem most of the time

Honestly IMO it's a bit better--often times I find myself wanting neither _this_ nor _that_, but some of each. I get to a point in a function where I realize changes I made elsewhere are no longer needed, but I want to keep my function the same. So I usually just copy the function (and paste it someplace so I don't for real lose it), undo everything, then just splice the function back in how I want it.
The main issue with this I find is a stray keystroke before you redo wiping your redo stack .
I solve this by making a git commit before diving into the undo.
Semi-related: What I really wish all my editors and IDEs would have is some kind of "delete history" view into my deleted text (especially blocks of text).

I'll often delete something, then work for a while, then realize I need that thing back again so I have to VERY carefully undo everything after the delete until I get to a point in history before it, copy the deleted text, then re-do everything back to my original state while making sure not to accidentally execute any inputs which would burn down the redo stack.

A lot of IDEs (I know Eclipse and VSCode both do this) will create a copy of the file you're working on each time you hit save. You can then navigate back through the local history of the file.

I'm conditioned to hit Ctrl+S with almost every change, so this gives me a very detailed history of my revisions.

Vim stores all deleted text automatically, up to a max of 10, I think.

Probably very possible to extend that with some advanced vimscripting.

In this thread: people discussing all the excellent features they wish their text editor had that emacs (and/or vim) has had for literally decades.
And/or JetBrains.

Makes me wonder honestly what tool in widespread use doesn’t have this? Notepad?

Vim supports practically unlimited undo with branching history. You can visualize it and hop to any undo with a plugin.

https://github.com/simnalamburt/vim-mundo

Yes, but GP wanted something that would just keep all deleted text.

Sort of, anytime you delete something, that text gets stored somewhere in case, 2 hours later, you realised you wanted it.

Yes, vim’s “set undofile” will create a persistent undo file that stores all changes to the file indefinitely. You can undo changes made months ago over reboots. If you move the undo file between machines, you can undo on different computers.

Undoing a change from 2 hours ago in the same editing session is the trivial use case for undofile.

But undoing is not what the original commenter wanted.
When set undofile is combined with :earlier and :later being used in place of undo and redo I think vim behaves as the author describes.
Jetbrains IDEs can do this using the local history feature. Browse through time on a separate tab, copy what you need, and paste it back into the present.
Whats even more amazing is that you can right click the package/folder and get local history and see entire files that you previously deleted.

Took me a while to discover this but has saved me almost as many times as local history in a file. Also makes me feel much better when refactoring and deleting tons of old code.

You can use “cut” instead of “delete” and clipboard with history tracking, for example Alfred on macOS.

Then you cut, do some changes, and later on realize you wanted something from the cut code, bring out the clipboard history and fish out what you need.

Oh cool-- are you saying Alfred will let me override the delete key in the case of highlighted text so it always executes a cut instead?
I think they’re just referring to a clipboard history offered by Alfred, not remapping the delete key
Yes, but having thousands of snippets in a bucket can be quite confusing too, where do the pieces go back and so on.

For this reason I comment out code then keep it until I’m sure I no longer need it. This works in sessions, and do the final delete, clean up etc at the end of the session before committing the changes to the repo. I see devs keeping commented out code in repos but that adds too much noise, makes the code a real mess..

Maccy is a great free and open source clipboard history tool with a nice interface (Alfred requires the power pack purchase for this feature)
Emacs does this by default. Anything you delete ends up in the "kill ring", and you can cycle through that when pasting ("yanking") something. Packages like Consult[1] provide version of the yank-pop command that, instead of cycling through the kill ring, make it searchable.

[1]: https://github.com/minad/consult

Eclipse has “local history” for each file save operation.

You can even do a graphical diff against each candidate timestamp in a gui wizard

As others have mentioned JetBrains cloned this feature too

Just a list of tools that have this, in case you use any of them and didn’t know: Vim - has this with undo tree Emacs - has this as well Jetbrains - IDE’s have this with local history they show changes made. VScode has this I’m pretty sure by default otherwise there’s a plug-in for it since I remember doing it. Also just want to add something really cool about vim and undo/redos that relates to this post. With vim your able to “block” or “bunch” your changes which allows you to control what an undo will do. For example, going into insert mode and typing a sentence than exiting insert mode would be one “bunch” and undo would undo that entire sentence. If you wanted to you can control it by leaving insert mode after a adding a single word or making a single edit and that will be what the undo will revert. Probably rambling a bit here, but the concept of controlling what will be undone/redone is a really neat feature of vim.
(comment deleted)
(comment deleted)
In this methodology, without exposing the tree, how does the user select between the two branches created after the butterfly is squashed?

It seems like Google Docs does it in an intuitive way where undo / redo works normally, but there’s another time ordered history of the document to select from.

Is this what the author’s approach is advocating?

In the author’s algorithm there is no two branches. The following diagram is an undo tree where we made change B, went back to A, then made C

      C ——— now
     /
 —— A
     \
      B
But time doesn’t branch, so our history shouldn’t /have to/ branch. What if our undo was itself part of the linear history? Then we could build up the history like this:

  A
  A —— B
  A —— B —- undo_B
  A —— B —— undo_B —— C
For complex undo/redo scenarios this could get out of hand.

  A — B — undo_B — C — undo_C — undo_undo_B —undo_B — undo_A…
But the author (I think correctly) states that for the average user this behavior is intuitive and desired.

They also added the optimization that undoing and then redoing a set of changes doesn’t make it into the history, as it’s basically a no-op.

Nope, the author's solution is to consider the original undo (the "time travelling") to be an undo-able change in itself. No branching history. So you end up with:

  state 1
   * change A
  state 2
   * change B
  state 3
   * undo B
  state 2
   * change C (the "butterfly squashing")
  state 3a
   * undo C
  state 2
   * undo <undo B>
  state 3 (!)
   * undo B
  state 2
   * undo A
  state 1
The traditional editor would differ by putting you right at state 1 at (!), blocking your access to state 3.
Feels like I'm missing something...

If I press ctrl-z ctrl-z, I expect the last 2 things typed to be undone. Based on what he's saying, the first ctrl-z undoes one step, and the second ctrl-z undoes the undo i.e. puts me back where I started, with no way to get back further.

Is there a special case for multiple undo's in a row? If so it seems unclear where to draw the line. If not it sounds nonfunctional.

True, at what point do you include the undo itself in the undo history or do you skip it entirely?

Never considered that.

I feel things work most intuitively if the undo/redo functions simply navigate the history, but do not themselves form part of the history.
That's how every other undo works. The whole GRUQ is caused by undos not themselves forming a part of history...
That's it. You can think of an undo operation U at time (t) as a new simple command that rolls back the content to a previous version (t-x). If you undo that U operation, it's a new command U' that restores content again to version (t).

The history sequence would then be:

(t) --U-> (t-x) --U'-> (t)

And if you add (A) some new content (c) when in state (t-x), it becomes command , which can itself be undone with commands U'' (to remove A), U''' (to remove U):

(t) --U-> (t-x) --A-> (t+c) --U''--> (t-x) --U'''--> (t)

>every other undo

Ahhhhh, some time you should try a little app called Adobe Photoshop.

That means silently killing off potentially large amounts of editing history every time you edit after an undo. That's the problem this article is about.
I was thinking maybe you ignore it for the first 10 seconds or even better until the cursor changes to a new position, thqt way quick rewinds work in the moment. But later on you can restore the state with undos factored in.
The way this approach is implemented in Emacs is that it depends on whether your previous action was an undo.

    A: A
    A B: AB
    A B C: ABC
    A B C undo: AB
    A B C undo undo: A
    A B C undo undo D: AD
    A B C undo undo D undo: A
    A B C undo undo D undo undo: AB
Interesting. So basically the last chain of undo doesn't enter the history until you do something else.

I often use undo as a faster delete when making edits, so I think I would still be annoyed by this (why am I seeing this crap I undid reappearing?) Now after a long undo chain I'm afraid to type for a new reason: it will pollute my undo stack. But it might be reasonable if my brain was used to it. Can't knock something I've never tried.

How is undo a faster delete?
If you're only adding content, undo removes the most recent content inserted. But that's a very limited case of everything that undo can achieve.
Like the GP, I also use undo as a faster delete. The key is that undo in Emacs doesn't just go one keystroke at a time. By default it will basically quash a whole chunk of similar operations and undo/redo them together:

> amalgamating-undo-limit is a variable defined in ‘simple.el’.

> Its value is 20

> The maximum number of changes to possibly amalgamate when undoing changes. The ‘undo’ command will normally consider "similar" changes (like inserting characters) to be part of the same change. This is called "amalgamating" the changes. This variable says what the maximum number of changes considered is when amalgamating. A value of 1 means that nothing is amalgamated.

There's also a hidden 10s idle timer that will insert a boundary to break up the amalgamation. So if you type, pause and think, and then start typing again, the chunked undo/redo will always stop at the point that you started typing again. Taken together, it's usually quite effective at guessing how much to delete.

Note that if you delete something and press Undo, it restores the content that was there before removing it.

The "branching undo" in the article extends the concept to being able to recover things that were at some point in your "redo" pile, but which in other editors would been have lost when you typed something else while in that state.

As the article explains, the simplest mental model is "rewind in time to the point I was 5 minutes, 10, 15, 20 minutes in the past". If you've been undoing and redoing things, going through that point in the past will retrieve those removals and retrievals.

> If so it seems unclear where to draw the line. If not it sounds nonfunctional.

There is no line. It is just one long linear list. An undo operation is an edit to the text after all. So the edit made by the undo operation also goes as a text edit operation in the undo list.

This is the way undo has always been in Emacs. I wouldn't say it is nonfunctional. I use this in Emacs undo, redo (which is undo-of-undo) and it feels okay most of the time. It is good to know that Emacs will never lose any edit even if I have performed a confusing series of undos and redos. But it can get confusing pretty soon if we are undoing and redoing on the same edit too many times.

That is why many people don't like linear undo history and install an undo-tree plugin which makes undos easy to navigate in a tree. Yet another reason to design some computer/software history lessons so that devs can learn from the existing techniques, their adoption, benefits and complaints.

Yeah for something that is supposedly intuitive this is the worst explanation I have ever read. I think what he's saying is that:

It works exactly like normal undo/redo, but if you make an edit that would normally wipe your "redo stack", then instead that redo stack is moved into the undo stack.

So:

  Type "hello"
  Type "world"
  Type "!"
  Undo
  Undo
  (Editor is "hello", with "world" and "!" on the redo stack)
  Type "dave"
  (This would normally wipe the redo stack but instead it moves it to the undo stack)
  Undo
  (Editor is "hello")
  Undo
  (Editor is "helloworld")
  Undo
  (Editor is "helloworld!")
  Undo
  (Editor is "helloworld")
  Undo
  (Editor is "hello")
I like the idea. It's a little weird that typing changes the undo stack but it already can change the redo stack so I don't think it matters.

It needs an understandable explanation and ideally a web demo to gain traction though.

I think the best strategy to make it intuitive would be to flatten the redo stack as a single command, so that the pile of successive 'undo's gets redone as a single step. In your example:

  Type "hello"
  Type "world"
  Type "!"
  Undo
  Undo
  (Editor is "hello", with "world!" on the redo stack)

  Type "dave"
  (Editor is "hellodave")
  (This would normally wipe the redo stack but instead it moves it to the undo stack)

  Undo
  (Editor is "hello")
  Undo
  (Editor is "helloworld!")
  Undo
  (Editor is "helloworld")
  Undo
  (Editor is "hello")
I get what you are saying, but I think it is confusing to refer to a 'redo' stack at all. There is only ever one stack, and you are always at the end of it. To go back through time you copy the states onto the stack (stack isn't even the right word, I guess but I'll stick with it)

Consider typing the alphabet

Stack:

  1.a
  2.ab
  3.abc
  4.abcd
Now you 'undo' back to place 2 and type 'x'. But while you are conceptually travelling back in time, what the stack really looks like now is:

  1.a
  2.ab
  3.abc
  4.abcd
  5.abc
  6.ab
  7.abx
A conventional undo stack would leave you stuck at a new place 3: abx, all redos gone. Undos from that point can only take you back to place 1 or 0. (Just tested this out on OS X TextEdit, it does this). With the linear stack, there is no redo, only undo.

I think that is what the article means at least...

Yeah that's the same thing and is probably how you'd actually implement it, but conceptually you can think of there being an undo and redo stack.
Photoshop has nonlinear history. When this option is tunred on, you can undo several times, make a change, and not lose all the states you've undid - isn't this what he's talking about?
Yeah maybe, I'd have to play around with both to see which is less annoying.
(comment deleted)
In Emacs, undo makes an undo pointer go down in the undo stack. Pressing undo again goes back another step. If you do any other regular edit, the pointer starts over at the top of the undo stack. Undo puts its own edits on top of the stack like any other command.

So if you "undo, undo" you undo two things. If you "undo, edit, undo", you're keeping the first undo but reverse the edit. If you "undo, edit, undo, undo", you're back to where you started (except your undo stack has now grown).

No, Ctrl-Z Ctrl-Z still undoes the last two edits.

Basically Ctrl-Z is now "rewind" and Ctrl-Y is now "fast forward". Instead of a stack you have an append-only list. Even Ctrl-Z appends to the list. (I'm eliding the optimizations he mentions.) So if you Ctrl-Z then edit, you can still get back to the state before your Ctrl-Z.

The article says:

The act of undoing need only become part of our linear history if we squash the proverbial butterfly and thus alter "the past"; in that case the act of undoing itself is instantly recorded as a series of changes, by replaying the redo stack onto the undo stack twice: 1) Forwards, for the original changes 2) and then backwards, to record the history of our undoing.

By squash the proverbial butterfly and thus alter "the past" they mean make an edit after undoing.

There's an interesting parallel here: the system proposed in the post is concatenative, and the undo tree style is, well, a tree. Sort of a Forth v. Lisp thing.

Here the big disadvantage of Forth, the implicit stack state, is less obviously balanced by something good. The UX of undo trees could certainly use improvement, it would be great if undo/redo behaved as expected, but any pivot point would automatically pop up the tree view. Then there's cherry-picking, but I have a solution to that.

This is in fact the thing I want most from an editor: select a region and then undo/redo changes only in that region. Search and replace tends to work this way, but I've never seen it in an undo/redo system.

How many times have you modified a function, broke it accidentally, and just didn't notice and made more changes? So the tests go red, you run a diff, and you see exactly what needs fixing: but you copy and paste out of the diff, because you can't just undo within the offending function.

I disagree that undo and redo are the only two actions needed to support this behavior. For example, if I’m performing the following three actions:

- insert A

- insert B

- insert C

And then I undo, I would expect to be be back at having AB. If I then undo, I would be undoing my undo and have ABC. Then undoing again would get me back to AB. And I remain in this 2-cycle.

Instead I propose the best solution is to be able to “step out” of the editing experience and see your history in a separate UI, and be able to choose a specific past version to revert to. This revert would be an atomic operation that could then be undone.

This is how most versioned consumer systems I know of work today (e.g. Google Docs, Figma, MS Office) and it feels intuitive to me as a user.

Relatedly, have you ever thought about how undo works with text?

Open your mail client, compose a message, type a few words. What do you expect cmd-z to do? How much of the words will it undo? All the line? Try it.

Then after undo, type a bit more. Hit enter and cursor back. Type again.

Undoing typing is no trivial matter.

This doesn't solve the problem of wanting to redo from another "do history branch" while preserving the changes you've done in your current history branch. Using the notation of the article, you have done:

A -> B -> C -> rewound to B -> D -> E

Now at that E point, you'd want to have C too. (I'm assumikng there is no conflict between C and E.) There is no point in the linear history where you have both E and C. Sure you can go back to C, or go back to E. Or go back to C then decide to go back to E again. But you can't have both.

In vim, I use the fact that the yank buffers are not part of the undo/redo history, only changes to the file are. So I undo to the point I had the change I want back, yank them in a named buffer, redo and put the yanked buffer. You cannot go to an alternate history, but at least you can get back stuff you've deleted.

In git, what I describe is pretty-close to cherry-pick from a commit in a branch you deleted that you find using the ref-log.