plushie/undo
Undo/redo support for reversible operations.
Each command has an apply function (model -> model) and an undo
function (model -> model). Push a command to execute it and add it to
the undo stack; undo/redo move commands between stacks while updating
the model.
The undo stack is bounded by max_size (default 100). When a push
exceeds the limit, the oldest entries are dropped. The redo stack
is unbounded (it can only shrink or be cleared, never grow past
the undo stack size).
Commands with the same coalesce_key that arrive within
coalesce_window_ms of each other are merged into a single undo
entry. The merged entry composes apply functions in push order and
undo functions in reverse order, so one undo reverses all coalesced
changes and redo reapplies them in the same order.
Treat UndoStack as the source of truth for the value it wraps.
For normal Gleam values the stack is opaque and cannot be edited
directly, so all undoable changes should flow through push,
undo, and redo. If an app keeps a cached copy beside the stack
for view convenience, update that copy from current after every
stack operation and never edit it independently.
Types
A reversible command.
pub type UndoCommand(model) {
UndoCommand(
apply: fn(model) -> model,
undo: fn(model) -> model,
label: String,
coalesce_key: option.Option(String),
coalesce_window_ms: option.Option(Int),
)
}
Constructors
-
UndoCommand( apply: fn(model) -> model, undo: fn(model) -> model, label: String, coalesce_key: option.Option(String), coalesce_window_ms: option.Option(Int), )
Values
pub fn new(model: model) -> UndoStack(model)
Create a new undo stack with initial model. The undo stack is
bounded by max_size (default 100). When exceeded, the oldest
entries are dropped silently.
pub fn new_with_max_size(
model: model,
max_size: Int,
) -> UndoStack(model)
Create a new undo stack with a custom maximum size. The max_size must be a positive integer.
pub fn push(
stack: UndoStack(model),
cmd: UndoCommand(model),
) -> UndoStack(model)
Push a command: execute it, push to undo stack, clear redo stack.
If the command carries a coalesce_key that matches the top of the
undo stack and the time delta is within coalesce_window_ms, the
entry is merged rather than pushed. The merged entry reapplies
coalesced commands in their original order and undoes them in
reverse order.
When the undo stack exceeds max_size, the oldest entries are dropped.
pub fn push_with_coalesce(
stack: UndoStack(model),
value: model,
kind: String,
window_ms: Int,
) -> UndoStack(model)
Push a new model snapshot with coalescing metadata.
Convenience wrapper over push for the snapshot style: instead of
supplying apply/undo functions, the caller hands in the new model
directly. kind is the coalesce key, and pushes of the same kind
within window_ms milliseconds merge into a single undo entry.
Canonical use: coalescing consecutive keystrokes in a text editor.
pub fn redo(stack: UndoStack(model)) -> UndoStack(model)
Redo the last undone command. Returns unchanged if nothing to redo.
pub fn redo_history(stack: UndoStack(model)) -> List(String)
Get redo history labels (most recent first).
pub fn undo(stack: UndoStack(model)) -> UndoStack(model)
Undo the last command. Returns unchanged if nothing to undo.
pub fn undo_history(stack: UndoStack(model)) -> List(String)
Get undo history labels (most recent first).