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

Add Entry undo/redo functionality #4251

Merged
merged 17 commits into from
Oct 2, 2023
Merged

Add Entry undo/redo functionality #4251

merged 17 commits into from
Oct 2, 2023

Conversation

xypwn
Copy link

@xypwn xypwn commented Sep 14, 2023

Description:

Implements #436

Checklist:

  • Tests included.
  • Lint and formatter run with no errors.
  • Tests all pass.

Notes:

I wrote this independently of #2539.

My implementation is fairly extensible, although currently tied to Entry by name.
It works by keeping track of the changes made by the user, NOT by making snapshots. This makes it more efficient and flexible than the other implementation.

Basic Architecture:

interface entryUndoAction: Represents a single user interaction. It contains the functions Undo and Redo, which modify the current Entry content to undo/redo that user interaction.

interface entryMergeableUndoAction: Extends entryUndoAction. Contains the TryMerge method, which allows for merging two actions into one. This is useful because, for example when typing a word, we want to undo that whole word at once, and not have to spam Ctrl+Z for every character.

struct entryInsertAction: Implements entryMergeableUndoAction and represents a string insertion or deletion. It only saves the position and string of the edit, i.e. no snapshots, as mentioned previously. It can merge with itself, but doesn't merge across word boundaries.

struct entryUndoStack: This is the main interface for dealing with the undo/redo system. It has the following methods:

  • Undo/Redo: Executes the current undo/redo operation on the current string and returns the new string as well as the executed operation.
  • Add/MergeOrAdd: Records the given action, allowing for Undo/Redo to be called afterwards. MergeOrAdd also tries to merge the given action with the undo action already on top of the stack; if it can't merge, it just reverts to call Add.
  • Clear clears all records in the undo stack. This is done after SetText() is called, for example, also meaning that SetText() modifications aren't recorded.

Feel free to drop in any suggestions :)

@coveralls
Copy link

Coverage Status

coverage: 65.382% (+0.08%) from 65.301% when pulling ddb1bf8 on xypwn:develop into 3ee5a3e on fyne-io:develop.

Copy link
Member

@andydotxyz andydotxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really excellent work, thanks so much.
A couple of items about naming inline but it is clear and well laid out, thanks.

widget/entry.go Outdated Show resolved Hide resolved
widget/entry.go Outdated Show resolved Hide resolved
widget/entry.go Outdated Show resolved Hide resolved
@andydotxyz
Copy link
Member

One possible bug to be improved - if I "undo" and there are no items left it seems to put the cursor back to the top left, scrolling content if appropriate. I don't think that should happen?

@xypwn
Copy link
Author

xypwn commented Sep 19, 2023

Hi, glad you like it! I'm not home right now, but when I am I'll be able address these issues and make the necessary changes.

Regarding the naming, insert was essentially vim terminology. But I think I'll change it to something like "modifyAction" instead, since that makes a lot more sense.

Regarding the bug, that's because the new cursor position is default initialized to 0 and if it isn't an insertAction, it uses that default value instead of just returning. Should be an easy fix.

Also, I think it would make sense to expose Undo and Redo as public functions, since you might want that for text editors, for example.

xypwn added 5 commits September 20, 2023 21:32
When the undo stack was empty, the cursor would just jump to position 0
on undo. With this change, undo/redo only changes the cursor position if
anything was actually undone or redone.
@xypwn
Copy link
Author

xypwn commented Sep 20, 2023

Alright, I made all the changes you requested + I made Undo/Redo public functions

Copy link
Member

@andydotxyz andydotxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks excellent, thanks for all the fixes.
Please annotate the new APIs with the version number as commented inline

widget/entry.go Outdated Show resolved Hide resolved
Copy link
Member

@Jacalz Jacalz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this. I have a few comments inline.

widget/entry.go Outdated Show resolved Hide resolved
widget/entry.go Outdated Show resolved Hide resolved
Comment on lines +558 to +559
undoItem := fyne.NewMenuItem("Undo", e.Undo)
redoItem := fyne.NewMenuItem("Redo", e.Redo)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How common is it to have undo and redo in the context menu? It seems to me like most applications don't expose it there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QT has undo/redo in the context menu in every text box by default. Also all popular web browsers. GTK apps are the only counter example I can think of. I don't think it's unreasonable to expose it in the context menu.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A possible improvement though would be to only show undo/redo if there is actually anything to undo/redo

Copy link
Author

@xypwn xypwn Sep 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A possible improvement though would be to only show undo/redo if there is actually anything to undo/redo

I just implemented that, it was pretty simple. Looks a bit cleaner now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally it would be enable/disable rather than add/remove - having menus be consistent in size and layout makes it easier to navigate them at speed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, it is less cluttered. Also that's how Chromium does it. And if you want to operate at speed, you'll want to use keyboard shortcuts anyway. But your project, your design decisions :)

widget/entry.go Outdated Show resolved Hide resolved
@xypwn
Copy link
Author

xypwn commented Sep 23, 2023

What's it with the CI checks not passing? Is that my fault?

@andydotxyz
Copy link
Member

andydotxyz commented Sep 24, 2023

There seems to be a bug with the windows / go 1.17 that has impacted all builds in the last 3 days. Multiple projects have noted it - we may need to downgrade the patch version for that build until it's fixed.

Not your fault at all!

Copy link
Member

@andydotxyz andydotxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just 2 types missing the Since line sorry I missed that before.

Otherwise I think it's great - @Jacalz ?

shortcut.go Show resolved Hide resolved
Copy link
Member

@andydotxyz andydotxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing thanks

@Jacalz
Copy link
Member

Jacalz commented Sep 30, 2023

I'll try to review tomorrow or on Monday. Been a bit busy lately

Copy link
Member

@Jacalz Jacalz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @xypwn for a well written PR both in terms of code and the extensive description. Your work is much appreciated. Looks good to me.

Also, congratulations on landing your first PR for Fyne 🥳

@Jacalz Jacalz merged commit df6e029 into fyne-io:develop Oct 2, 2023
@xypwn
Copy link
Author

xypwn commented Oct 2, 2023

Let's gooo! Thank you all!

@xypwn xypwn deleted the develop branch October 2, 2023 22:37
@xypwn xypwn restored the develop branch October 2, 2023 22:37
@andydotxyz
Copy link
Member

So excited this is in - thanks a lot :) v2.5.0 looks great already and it's still 3-4 months away!

@aneeskA
Copy link

aneeskA commented May 27, 2024

@andydotxyz Can you please share the release plan for these changes? I cannot find them in the master branch.

@xypwn
Copy link
Author

xypwn commented May 27, 2024

@andydotxyz Can you please share the release plan for these changes? I cannot find them in the master branch.

It will be added in v2.5.0

@andydotxyz
Copy link
Member

@andydotxyz Can you please share the release plan for these changes? I cannot find them in the master branch.

It will be added in v2.5.0

Exactly right. As you can see we are working towards feature freeze now, but you can use it on @develop branch before then.
https://github.com/fyne-io/fyne/wiki/Releases

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