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),
    )

Undo/redo state wrapping a model.

pub opaque type UndoStack(model)

Values

pub fn can_redo(stack: UndoStack(model)) -> Bool

Check if redo is available.

pub fn can_undo(stack: UndoStack(model)) -> Bool

Check if undo is available.

pub fn current(stack: UndoStack(model)) -> model

Get the current model.

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).

Search Document