To implement a simple undo redo, you need two stacks, and a copyable representation of the program state. You then push to the undo stack, with straight forwards extensions for redo.top, then push. To pop the stack, apply the difference to stack top, and set it as new stack top.
However this is memory greedy. So it is better to use the following pattern, on stack push, take the difference between stack top and the object being pushed. Then delete stack top, and replace with the difference between the pushed object and stack
This could be made generic, but it involves a user-supplied delta codec which could be hard to write. Another way of doing it is to feed everything through a tostring / fromstring representation.more than few changes, we can simply resort to storing the entire string.
The problem is now reduced to writing the delta codec for two strings. The proper way to do this is to use a string alignment algorithm, but that is likely to be overkill. The majority of changes will be either one character or one indel. If we've got
Storing whole program state is pointlessly memory consuming and diffing whole program state is pointlessly expensive.
More usual is to make all actions to be required to record their reverse actions.
So when action is done then reverse action is stored to undo stack and redo stack is cleaned.
Undo then means that reverse action is done and removed from undo stack and action is stored to redo stack.
Redo then means that action is done and removed from redo stack and reverse action is stored to undo stack.
That takes designing (and testing) your commands/actions/operations more carefully.
But that is anyway not bad at all in the long run.
To implement a simple undo redo, you need two stacks, and a copyable representation of the program state. You then push to the undo stack, with straight forwards extensions for redo.
On Sunday, 3 April 2022 at 13:16:14 UTC+2, Malcolm McLean wrote:change you just record the path together with the value before change.
To implement a simple undo redo, you need two stacks, and a copyable representation of the program state. You then push to the undo stack, with straight forwards extensions for redo.Why two stacks? I just see the need for one. And why "program state", this is usually about a specific set of properties along some tree hierarchy so that each property is identified by a unique path... and then, in a basic implementation, at any
That said, it's hardly the case that any diffing logic can do better than just recording the old value in the case of "primitive" types: you can do better for composite objects/arrays, by checking what properties have actually changed (and extending alittle bit on the notion of changed property path). Anyway this is a further step...
To implement a simple undo redo, you need two stacks, and a copyable representation of the program state. You then push to the undo stack, with straight forwards extensions for redo.top, then push. To pop the stack, apply the difference to stack top, and set it as new stack top.
However this is memory greedy. So it is better to use the following pattern, on stack push, take the difference between stack top and the object being pushed. Then delete stack top, and replace with the difference between the pushed object and stack
This could be made generic, but it involves a user-supplied delta codec which could be hard to write. Another way of doing it is to feed everything through a tostring / fromstring representation.more than few changes, we can simply resort to storing the entire string.
The problem is now reduced to writing the delta codec for two strings. The proper way to do this is to use a string alignment algorithm, but that is likely to be overkill. The majority of changes will be either one character or one indel. If we've got
On Monday, 4 April 2022 at 09:03:31 UTC+1, [email protected] wrote:change you just record the path together with the value before change.
On Sunday, 3 April 2022 at 13:16:14 UTC+2, Malcolm McLean wrote:
To implement a simple undo redo, you need two stacks, and a copyable representation of the program state. You then push to the undo stack, with straight forwards extensions for redo.
Why two stacks? I just see the need for one. And why "program state", this is usually about a specific set of properties along some tree hierarchy so that each property is identified by a unique path... and then, in a basic implementation, at any
You need two stacks because after a series of undos, you have a series of redos.
These can be in the same contiguous region of memory in a simple implementation, but you stilla little bit on the notion of changed property path). Anyway this is a further step...
have two stacks, one growing up and one growing down.
In my system, you need two actual stacks, because we only store the deltas, except for at stack
top, where we store the entire object.
That said, it's hardly the case that any diffing logic can do better than just recording the old value in the case of "primitive" types: you can do better for composite objects/arrays, by checking what properties have actually changed (and extending
I don't quite follow, but I think what you are saying is that program state is held as a graph,
then user operations are expressed as operation on that graph, and you store the operation
in the undo system.
That is how a lot of programs work. But it means the undo/redo system dominates the design
of the program.
And it means it needs to be bespoke. Te idea is to have a generic system, which
relies on just converting the state to and from a universal representation. Strings are the
obvious example of a universal representation.
On Monday, 4 April 2022 at 14:23:58 UTC+2, Julio Di Egidio wrote:<snip>
On Monday, 4 April 2022 at 10:49:05 UTC+2, Malcolm McLean wrote:
is not usually "just everything".That is how a lot of programs work. But it means the undo/redo system dominates the design
of the program.
No no, I would certainly expect the whole thing to be orthogonal to normal operations: don't we just need a "definition file" where we say what maps to what, plus hooks wherever relevant actions happen? And then again, this (what needs to be tracked)
To be clearer, I mean mapping property values within the program to keys ("paths") for tracking: then you'd use the key to add to the undo stack (where you'd know which key you need because this "derives" from the specific action that is originating it), and when restoring state from the stack, you'd again use the key to, viceversa, know which action it refers to whence which property value to change/restore... That's what I end up with.
On Monday, 4 April 2022 at 10:49:05 UTC+2, Malcolm McLean wrote:change you just record the path together with the value before change.
On Monday, 4 April 2022 at 09:03:31 UTC+1, [email protected] wrote:
On Sunday, 3 April 2022 at 13:16:14 UTC+2, Malcolm McLean wrote:
To implement a simple undo redo, you need two stacks, and a copyable representation of the program state. You then push to the undo stack, with straight forwards extensions for redo.
Why two stacks? I just see the need for one. And why "program state", this is usually about a specific set of properties along some tree hierarchy so that each property is identified by a unique path... and then, in a basic implementation, at any
end of the stack, when there is no redo. Then undo/redo is simply walking up and down the stack and restoring whichever value is recorded there (the "value before change"), while adding a new entry becomes 1) drop all entries starting starting from theYou need two stacks because after a series of undos, you have a series of redos.Ah, right, I forgot the redo part. But this can still be done with one stack, plus a "stack pointer", that is you need to maintain an index to point at the currently "active entry": that index would be equal to the stack count, meaning it's past the
BTW, you haven't said what language you are using, but e.g. in .Net Stack<T> implements IList<T>, which makes manipulations easier. Indeed, in case your language does not provide something like that, I'd directly use a List, otherwise, for one thing,dropping trailing entries as in step 1 becomes a series of individual pops...
extending a little bit on the notion of changed property path). Anyway this is a further step...These can be in the same contiguous region of memory in a simple implementation, but you still
have two stacks, one growing up and one growing down.
In my system, you need two actual stacks, because we only store the deltas, except for at stack
top, where we store the entire object.
That said, it's hardly the case that any diffing logic can do better than just recording the old value in the case of "primitive" types: you can do better for composite objects/arrays, by checking what properties have actually changed (and
is not usually "just everything".I don't quite follow, but I think what you are saying is that program state is held as a graph,
No, I am saying why would you track "program state"? It's at best a subset of program state, no? Speaking generally, I'd say there is a set of actions that need to be "tracked", which are typically actions on "data", not general program actions.
then user operations are expressed as operation on that graph, and you store the operation
in the undo system.
I am just thinking about it as hierarchically structured data, but I may very well be missing part of the picture here: I am "reconstructing" this with you, I do not have a ready model I am just describing to you.
That is how a lot of programs work. But it means the undo/redo system dominates the design
of the program.
No no, I would certainly expect the whole thing to be orthogonal to normal operations: don't we just need a "definition file" where we say what maps to what, plus hooks wherever relevant actions happen? And then again, this (what needs to be tracked)
language, so they already share a same type system, in a simple implementation for internal use I'd most probably not even bother about that: although this is the one main extension point I see, up to custom comparers and what not.And it means it needs to be bespoke. Te idea is to have a generic system, which
relies on just converting the state to and from a universal representation. Strings are the
obvious example of a universal representation.
What I have been describing so far is (on the way to) totally generic, rather maybe I am still missing part of the requirements: see my comment above.
On another note (and still relative to my "hierarchical data"), one thing one might want to add is a data type along with the property path/name, but even there, considering that client code and library code are most probably written in the same
In fact, "again use the key to, viceversa, know which -- property value to change/restore", no need to now about actions at all, this is all about property values.
The only reason I can think of to, a this point, also record the name of the action with every entry in the stack, is to be able to show it it to the user or similar...
Anyway, details apart, I'd think that's pretty much it: the only open question on my side is what you mean by "program state" and why...
In fact, a stack that does not implement access by index just wouldn't cut it.
On Monday, 4 April 2022 at 15:38:25 UTC+2, Malcolm McLean wrote:
Most edits change only one letter, so this seems a bit wasteful, I wondered if I could do better.
Either you do it or you don't.
And you haven't engage with my observation that this is *not* about "program state". Talking of how to approach the whole thing "canonically"...
Strictly speaking, the position of the cursor on the grid is also part of the program state. But users don't expect moving the cursorI dislike it but most more advanced editor do have it, usually optional.
to create an undoable action, and I doubt they care whether the cursor is restored after an undo.
Anyway, just my 2c: have fun.
Most edits change only one letter, so this seems a bit wasteful, I wondered if I could do better.
Strictly speaking, the position of the cursor on the grid is also part of the program state. But users don't expect moving the cursor
to create an undoable action, and I doubt they care whether the cursor is restored after an undo.
On Monday, 4 April 2022 at 14:02:24 UTC+1, [email protected] wrote:
In fact, a stack that does not implement access by index just wouldn't cut it.
You can only undo the item on top of the stack.
If you have two stacks, they can be true stacks
This is just a random observation that might trigger something useful in more complex situations...
Emacs has what I find to be a very useful refinement of undo/redo: it
can be limited to a region of the text.
You need two stacks because after a series of undos, you have a series
of redos.
Malcolm McLean <[email protected]> writes:
You need two stacks because after a series of undos, you have a seriesThis is just a random observation that might trigger something useful in
of redos.
more complex situations...
Emacs has what I find to be a very useful refinement of undo/redo: it
can be limited to a region of the text.
Say I delete most of the quoted material in a reply (Emacs is my Usenet client), and then make a few remarks at the end of the post, maybe doing
some undo or redo operations. If I now realise I need some of the stuff
I delete for context, I can highlight the top of my reply and undo in
the selected region only. The big delete will be undone, and I can then
do the more selective editing I now know I need.
| Sysop: | Keyop |
|---|---|
| Location: | Huddersfield, West Yorkshire, UK |
| Users: | 715 |
| Nodes: | 16 (2 / 14) |
| Uptime: | 153:31:40 |
| Calls: | 12,091 |
| Calls today: | 4 |
| Files: | 15,000 |
| Messages: | 6,517,669 |