Documentation
¶
Overview ¶
Package undo is a render-agnostic undo/redo command stack for the go-widgets MVVM layer. It has no dependency on any backend: it builds only on the dependency-free primitives of the parent github.com/go-widgets/mvvm package (Observable and Command), so the same stack drives pixel (toolkit) and terminal-cell (tui) views alike.
Model ¶
A Command is a reversible action: Do applies it (and re-applies it on redo), Undo reverts it, and Label names it for the UI. A Stack records the commands the user performs and moves a cursor across them: Stack.Undo steps the cursor back and reverts, Stack.Redo steps it forward and re-applies. Pushing a fresh command after some undos discards the redo tail, exactly like every editor.
Coalescing ¶
Contiguous commands of the same kind can collapse into a single undo step — so a run of keystrokes or a drag gesture undoes in one go rather than one character or pixel at a time. A command opts in by implementing Coalescer; NewCoalescing provides a ready-made key-matched implementation.
Binding to the UI ¶
A Stack exposes its state as MVVM primitives so an "Undo"/"Redo" button or menu item binds with no glue: Stack.UndoCommand/Stack.RedoCommand are [mvvm.Command]s (their CanExecute tracks CanUndo/CanRedo), and Stack.CanUndoBinding, Stack.UndoTextBinding, and their redo twins are [mvvm.Observable]s carrying the enabled flag and the live "Undo <label>" caption. Wire them with the parent package's BindCommand / OneWay adapters.
Threading ¶
Like the rest of the MVVM layer, a Stack is not safe for concurrent use — drive it from the UI goroutine.
Index ¶
- type Coalescer
- type Command
- type Option
- type Stack
- func (s *Stack) CanRedo() bool
- func (s *Stack) CanRedoBinding() *mvvm.Observable[bool]
- func (s *Stack) CanUndo() bool
- func (s *Stack) CanUndoBinding() *mvvm.Observable[bool]
- func (s *Stack) Clear()
- func (s *Stack) Cursor() int
- func (s *Stack) Len() int
- func (s *Stack) Push(cmd Command)
- func (s *Stack) Redo() bool
- func (s *Stack) RedoCommand() *mvvm.Command
- func (s *Stack) RedoLabel() string
- func (s *Stack) RedoTextBinding() *mvvm.Observable[string]
- func (s *Stack) Undo() bool
- func (s *Stack) UndoCommand() *mvvm.Command
- func (s *Stack) UndoLabel() string
- func (s *Stack) UndoTextBinding() *mvvm.Observable[string]
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Coalescer ¶
Coalescer is the optional interface a Command implements to merge with the command immediately preceding it on the stack. When Stack.Push is about to record next and the current top command implements Coalescer, it calls top.CoalesceWith(next): returning (merged, true) replaces the top in place so the two collapse into a single undo step, while (nil, false) records next as a new step. This is how a run of same-kind commands (keystrokes, drag deltas) undoes in one action.
type Command ¶
type Command interface {
Do()
Undo()
Label() string
}
Command is a single reversible action recorded on a Stack.
Do applies the action; it is called once when the command is pushed and again on every redo. Undo reverts it, restoring the exact state that existed before Do ran. Label names the action for the UI (e.g. "Typing", "Delete row"); it may be empty. Do and Undo must be exact inverses so that any Do/Undo or Undo/Redo pair round-trips.
func NewCoalescing ¶
NewCoalescing returns a Command like NewCommand that also coalesces: when it is pushed immediately after another NewCoalescing command sharing the same non-empty key, the two collapse into one undo step whose Undo reverts both newest-first and whose Do re-applies both oldest-first. An empty key never coalesces (behaving like New). Coalescing re-applies on each further push, so an entire run folds into a single step.
Example ¶
ExampleNewCoalescing shows a run of keystrokes collapsing into one undo step.
package main
import (
"fmt"
"github.com/go-widgets/mvvm/undo"
)
func main() {
doc := ""
s := undo.New()
key := func(r string) undo.Command {
return undo.NewCoalescing("type", "Typing",
func() { doc += r },
func() { doc = doc[:len(doc)-len(r)] })
}
for _, r := range []string{"h", "i", "!"} {
s.Push(key(r))
}
fmt.Printf("%q steps=%d\n", doc, s.Len())
s.Undo() // one undo reverts the whole run
fmt.Printf("%q\n", doc)
}
Output: "hi!" steps=1 ""
func NewCommand ¶
NewCommand returns a non-coalescing Command that runs do on Do (and redo), runs undo on Undo, and reports label. do and undo must be non-nil and exact inverses.
type Option ¶
type Option func(*Stack)
Option configures a Stack at construction.
func WithCoalescing ¶
WithCoalescing enables or disables coalescing of contiguous same-kind commands (see Coalescer). Coalescing is on by default.
type Stack ¶
type Stack struct {
// contains filtered or unexported fields
}
Stack is a render-agnostic undo/redo command stack. It holds the sequence of commands the user has performed and a cursor marking how many are currently applied: everything before the cursor can be undone, everything from the cursor on can be redone. Push after an undo discards the redo tail.
A Stack also publishes its state as MVVM primitives (see Stack.UndoCommand, Stack.CanUndoBinding, Stack.UndoTextBinding and their redo twins) so an Undo/Redo button or menu binds directly with no extra wiring.
The zero Stack is not usable; construct one with New. A Stack is not safe for concurrent use.
Example ¶
ExampleStack shows an app recording edits, then undoing and redoing them. The document below stands in for any model; each command captures the exact inverse so state round-trips.
package main
import (
"fmt"
"github.com/go-widgets/mvvm/undo"
)
func main() {
doc := "" // the model
s := undo.New()
write := func(text string) undo.Command {
return undo.NewCommand("Write "+text,
func() { doc += text },
func() { doc = doc[:len(doc)-len(text)] })
}
s.Push(write("hello"))
s.Push(write(" world"))
fmt.Printf("%q — %s\n", doc, s.UndoTextBinding().Get())
s.Undo()
fmt.Printf("%q — %s\n", doc, s.UndoTextBinding().Get())
s.Redo()
fmt.Printf("%q\n", doc)
}
Output: "hello world" — Undo Write world "hello" — Undo Write hello "hello world"
func New ¶
New builds an empty Stack. By default it is unlimited and coalescing is on; pass WithLimit / WithCoalescing to change either.
func (*Stack) CanRedoBinding ¶
func (s *Stack) CanRedoBinding() *mvvm.Observable[bool]
CanRedoBinding returns an mvvm.Observable carrying whether a redo is available.
func (*Stack) CanUndoBinding ¶
func (s *Stack) CanUndoBinding() *mvvm.Observable[bool]
CanUndoBinding returns an mvvm.Observable carrying whether an undo is available — bind it to a widget's enabled/visible state.
func (*Stack) Clear ¶
func (s *Stack) Clear()
Clear empties the stack, discarding all undo and redo history. The current application state is left as-is.
func (*Stack) Cursor ¶
Cursor reports how many commands are currently applied — the number that can be undone.
func (*Stack) Push ¶
Push applies cmd (calling cmd.Do), discards any redo tail, and records it as the newest undoable step. When coalescing is on and the previous top command merges with cmd (see Coalescer), the two collapse into a single step instead of adding one. Honours the configured limit.
func (*Stack) Redo ¶
Redo re-applies the next command and steps the cursor forward, returning true. It is a no-op returning false when there is nothing to redo.
func (*Stack) RedoCommand ¶
RedoCommand returns an mvvm.Command that redoes one step; its CanExecute tracks CanRedo.
func (*Stack) RedoLabel ¶
RedoLabel returns the Label of the command that Redo would re-apply, or "" when there is nothing to redo.
func (*Stack) RedoTextBinding ¶
func (s *Stack) RedoTextBinding() *mvvm.Observable[string]
RedoTextBinding returns an mvvm.Observable carrying the live redo caption, "Redo" or "Redo <label>".
func (*Stack) Undo ¶
Undo reverts the most recently applied command and steps the cursor back, returning true. It is a no-op returning false when there is nothing to undo.
func (*Stack) UndoCommand ¶
UndoCommand returns an mvvm.Command that undoes one step; its CanExecute tracks CanUndo, so a bound button greys out when there is nothing to undo.
func (*Stack) UndoLabel ¶
UndoLabel returns the Label of the command that Undo would revert, or "" when there is nothing to undo.
func (*Stack) UndoTextBinding ¶
func (s *Stack) UndoTextBinding() *mvvm.Observable[string]
UndoTextBinding returns an mvvm.Observable carrying the live button caption: "Undo" when nothing is undoable or the command is unlabelled, else "Undo <label>".