Plushie.Undo (Plushie v0.6.0)

Copy Markdown View Source

Undo/redo stack for reversible commands. Pure data structure, no processes.

Each command provides an apply function and an undo function. The stack tracks entries as {apply_fn, undo_fn} pairs so that undo moves an entry to the redo stack (calling undo_fn) and redo moves it back (calling apply_fn).

Coalescing

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 keeps the original undo function (so one undo reverses all coalesced changes) and composes the apply functions.

Example

iex> u = Plushie.Undo.new(0)
iex> cmd = %{apply: &(&1 + 1), undo: &(&1 - 1)}
iex> u = Plushie.Undo.apply(u, cmd)
iex> Plushie.Undo.current(u)
1
iex> u = Plushie.Undo.undo(u)
iex> Plushie.Undo.current(u)
0

Summary

Functions

Apply a command, updating the current model and pushing an entry onto the undo stack. Clears the redo stack.

Return true if there are entries on the redo stack.

Return true if there are entries on the undo stack.

Return the current model.

Return the labels from the undo stack, most recent first.

Create a new undo stack with model as the initial state.

Redo the last undone command. Returns unchanged if the redo stack is empty.

Undo the last command. Returns unchanged if the undo stack is empty.

Types

command()

@type command() :: %{
  :apply => (term() -> term()),
  :undo => (term() -> term()),
  optional(:label) => String.t(),
  optional(:coalesce) => term(),
  optional(:coalesce_window_ms) => non_neg_integer()
}

entry()

@type entry() :: %{
  apply_fn: (term() -> term()),
  undo_fn: (term() -> term()),
  label: String.t() | nil,
  coalesce: term() | nil,
  timestamp: integer()
}

t()

@type t() :: %Plushie.Undo{
  current: term(),
  redo_stack: [entry()],
  undo_stack: [entry()]
}

Functions

apply(u, command)

@spec apply(undo :: t(), command :: command()) :: t()

Apply a command, updating the current model and pushing an entry onto the undo stack. Clears the 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.

can_redo?(undo)

@spec can_redo?(undo :: t()) :: boolean()

Return true if there are entries on the redo stack.

can_undo?(undo)

@spec can_undo?(undo :: t()) :: boolean()

Return true if there are entries on the undo stack.

current(undo)

@spec current(undo :: t()) :: term()

Return the current model.

history(undo)

@spec history(undo :: t()) :: [String.t() | nil]

Return the labels from the undo stack, most recent first.

new(model)

@spec new(model :: term()) :: t()

Create a new undo stack with model as the initial state.

redo(u)

@spec redo(undo :: t()) :: t()

Redo the last undone command. Returns unchanged if the redo stack is empty.

undo(u)

@spec undo(undo :: t()) :: t()

Undo the last command. Returns unchanged if the undo stack is empty.