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

Soft undo/redo (de)selection #1596

Open
eugenesvk opened this issue Jan 28, 2022 · 17 comments · May be fixed by #9145
Open

Soft undo/redo (de)selection #1596

eugenesvk opened this issue Jan 28, 2022 · 17 comments · May be fixed by #9145
Assignees
Labels
A-helix-term Area: Helix term improvements C-enhancement Category: Improvements

Comments

@eugenesvk
Copy link

Let's say I carefuly selected a few words between semicolons with a couple of useful find_next_char commands, and then accidentally move a cursor.

Would be great if there were a pair of soft_undo and soft_redo (similar to Sublime Text's) that would allow me to easily revert selection changes just like regular undo/redo operate on text change

@eugenesvk eugenesvk added the C-enhancement Category: Improvements label Jan 28, 2022
@kirawi kirawi added the A-helix-term Area: Helix term improvements label Jan 29, 2022
@samholmes
Copy link

I with this 100%. But what would the default key binding be for soft-undo/redo? Maybe alt+u and alt+U or cmd+u and cmd+alt+u?

@kirawi
Copy link
Member

kirawi commented Sep 11, 2022

As @EpocSquadron pointed out, relevant Kakoune PR: mawww/kakoune#4725

@txtyash
Copy link
Contributor

txtyash commented Sep 12, 2022

I'm looking for something similar to vim's gv

@samholmes
Copy link

I didn't know about gv. That will only get you to the last selection. The jump-list feature is the closest we have to a soft undo. Maybe we can configure motions to automatically add/remove to/from the jump-list.

@goyalyashpal
Copy link
Contributor

goyalyashpal commented Feb 2, 2023

This will also enable more frequent use of the / register for search... or say, easy recovery from accidental use via n (next) thereof.

ref: #5784

@mjs
Copy link

mjs commented Mar 10, 2023

I’d like to have this feature and am interested in implementing it myself. I fairly regularly screw up a selection due to Vim muscle memory.

One question I have is what should happen when the buffer has been changed such that it isn’t really possible to undo a selection?

A simple example: enter some text in a buffer, then %d. What should a soft undo do at this point given that the previous selection is no longer possible?

Perhaps soft undo should only be possible back to the last modification? I’ll take a look at Kakoune and Sublime and see what they do.

@kirawi
Copy link
Member

kirawi commented Mar 10, 2023

According to the PR description, it looks like they try to map the selection to the new buffer.

@samholmes
Copy link

I think in VSCode, soft-undo will do a hard-undo when necessary.

@mjs
Copy link

mjs commented Mar 12, 2023

I've taken a look at what other editors do for their soft undo/redo feature.

VSCode

The feature is called "Cursor undo" and it only works back to the last change. This is simple, pragmatic approach and works for the common "I just accidentally messed up my complicated selection" use case.

Kakoune

Kakoune's soft undo and redo is accessed via C-h and C-k.

This will attempt to restore selections as best as possible even when text which had been selected no longer exists in the buffer. While this is impressive, it can result in weird selections when soft-undoing where parts of the buffer end up getting selected that are totally unrelated to the original selection. I'm not convinced that this is all that useful and can get a bit confusing.

On the flip side, Kakoune's soft undo does allow you to get back to selections for lines that haven't been removed even when other parts of the buffer were removed in between.

Note that Kakoune's undo stack and soft undo stack are completely independent.

Sublime Text

Sublime Text's soft undo and redo are accessed via C-u and C-S-U.

Soft undo/redo will step through all cursor movements, selections and changes. Normal undo (c-z) will just step back through changes. I find this very natural and easy to understand, but without the limited scope of VSCode's implementation.

My only criticism here is that the name "soft undo" isn't all that accurate as it will result in undoing of actual changes to the buffer, not just selections.

I would bet that changes and cursor movements/selections are stored in the same list with the normal undo/redo just skipping over the cursor/selection changes.

Proposal for Helix

I think we should go for the same approach as Sublime Text but call it "granular undo" or "micro undo".

Or if storing a full selection history is considered too heavyweight let's go with VSCode's approach as I imagine this will cover 90% of use cases for this feature.

Thoughts?

@eugenesvk
Copy link
Author

Thanks for a nice summary of other editors' behaviors!

I'd say that Sublime's approach is a mistake precisely because it mixes destructive and non-destructive operations. That's the reason I have to be careful using it since there is always this risk of changing the buffer. To me it's a categorically different kind of operation with no UI benefit of the combo.

The Kakoune's attempt to restore selections despite the edits is impressive indeed, also share your concern it might be confusing, but haven't used it enough to know whether this concern outweighs the benefits

@mjs
Copy link

mjs commented Mar 13, 2023

I'd say that Sublime's approach is a mistake precisely because it mixes destructive and non-destructive operations. That's the reason I have to be careful using it since there is always this risk of changing the buffer. To me it's a categorically different kind of operation with no UI benefit of the combo.

The benefit of being up to step through both selections and changes is that you can get back to any previous selection and editing state. As mentioned, I wouldn't call this "soft undo" because it wouldn't be that but IMHO it's useful and easy to understand.

The Kakoune's attempt to restore selections despite the edits is impressive indeed, also share your concern it might be confusing, but haven't used it enough to know whether this concern outweighs the benefits

If the Kakoune approach is too much, would you then advocate for the simple approach taken by VSCode (undo selections back to the last buffer change)?

I guess we could always start with that and expand the approach later if it's deemed important.

@eugenesvk
Copy link
Author

The benefit of being up to step through both selections and changes is that you can get back to any previous selection and editing state.

But you don't need to only have the combo command to do that, this benefit can be achieved by using undo (this would ignore all seletions in the undo stack and get you anywhere in the buffer history edits) and soft undo (this would only act on the selections stored between hard undos) commands.

As mentioned, I wouldn't call this "soft undo" because it wouldn't be that but IMHO it's useful and easy to understand.

You could even have one "combo" universal undo command which traverses hard+soft undo stack, I'd just clarify that I meant to say that having only that is a mistake since the separation between edits and selection is too important to lose.

I guess we could always start with that and expand the approach later if it's deemed important

Agree, I also think that it'd cover most of the use cases for this feature

@mjs
Copy link

mjs commented Mar 27, 2023

I've been thinking about this some more and reading Helix's code, and it occurs to me that there's an extra complication here which I hadn't appreciated until now.

  1. Changes to text are attached to Documents - the undo/redo history is shared by all Views.
  2. Selections are tied to the Document+View. That is, different Views on the same Document have independent selections.

This can be seen when using splits for the same file. Selections in one view aren't visible in the other, but changes to the document are seen in the other.

This means that interleaving the selection history in the undo stack doesn't make sense. Any selection history has to be per Document+View. This also makes it more complicated to have a soft undo history which only goes back to the last change to the document (discussed above).

With all this in mind, the following approaches seem like sensible ways forward:

  1. Implement a per view "restore last selection" feature. This is relatively easy to do and would be similar to Vim's gv binding. This would likely cover most people's needs for this feature.
  2. Implement a per Document+View selection history. This would be like a fine-grained jumplist for the current Document and View. Previous selections would be restored as best as possible in the face of inserted and deleted text. Helix's jumplist already does this so we already have the code to deal with these cases. This would be quite similar to what Kakoune does now. Unlike Kakoune which keeps an infinite selection history, we could cap it at some value (the jumplist is currently limited to 30).

Thoughts?

Other observations I had while researching tonight:

  • Vim/Neovim don't have the concept of selections per view. All panes for a buffer share the same selection.
  • Sublime Text skirts around some of this complexity by not letting you have multiple views on one document! This allows it to interleave soft and hard changes into the same stack.

@eugenesvk
Copy link
Author

Sublime Text skirts around some of this complexity by not letting you have multiple views on one document! This allows it to interleave soft and hard changes into the same stack.

You can have it in ST, open a file in a different project, then move the tab to the first project - you'll have to tabs with views of the same file. But they do have different undo stacks (both soft and hard), you can't undo edits made in tab1 that happened before you opened the same document in tab2 (and soft undos aren't shared at all)

re. Doc+View: good idea, but just to clarify: this will be stored per view, so selection changes in View1 have no effect on selection changes in View2 (and their respective soft undo stacks), only the edits have an effect in any view?

@mjs
Copy link

mjs commented Mar 28, 2023

You can have it in ST, open a file in a different project, then move the tab to the first project - you'll have to tabs with views of the same file. But they do have different undo stacks (both soft and hard), you can't undo edits made in tab1 that happened before you opened the same document in tab2 (and soft undos aren't shared at all)

ok, interesting. That’s still consistent with how I imagine it works internally (and that sounds like a slightly odd UX).

re. Doc+View: good idea, but just to clarify: this will be stored per view, so selection changes in View1 have no effect on selection changes in View2 (and their respective soft undo stacks), only the edits have an effect in any view?

Yes, exactly this. Selections are currently stored on Documents in a HashMap indexed by View. I was thinking that selection history would be stored in the same way such that each View has an independent selection history for each Document. In fact, I think ultimately there should be just one HashMap that points to the selection history for each view along where the history type also tracks the current selection.

I think the way forward is clear now. I just need to make some time make the change.

@gertqin
Copy link

gertqin commented Nov 2, 2023

Not sure how far you got with this @mjs, but I have made a simple implementation of the VSCode approach (undo/redo selection until last change) here:
master...gertqin:helix:undo-redo-selections
and it works very well for me (screwing up carefully crafted selections is no longer painful 🙂).

Let me know if you think I should open a PR with it.

@kirawi kirawi linked a pull request Dec 23, 2023 that will close this issue
@rockboynton
Copy link

@gertqin those code changes seem like a very nice concise implementation of soft undo to me! Please open a PR when you get a chance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-helix-term Area: Helix term improvements C-enhancement Category: Improvements
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants