command_history 0.4.0
command_history: ^0.4.0 copied to clipboard
Generic immutable undo/redo ring for Dart. Type-safe CommandHistory<S> with Command<S> — maxSize cap, undo/redo labels, value equality, no Flutter dependency.
0.4.0 #
- Command merging —
Command<S>gainsmergeWith(Command<S> next) → Command<S>?(defaultnull). Override it to coalesce consecutive commands of the same type into a single undoable step (e.g. per-keystroke typing). Whenexecuteis called and the previous command returns a non-null merge result, the top of the undo stack is extended in-place instead of growing. Undo still reverses the entire merged batch in one step. - Named checkpoints — full history snapshots stored by name:
checkpoint(String name)— save the current history under a key (silently overwrites).restoreCheckpoint(String name)— jump back to the exact state, stacks, and labels at snapshot time (asserts that the name exists).deleteCheckpoint(String name)/clearCheckpoints()— remove one or all checkpoints.hasCheckpoint(String name)— boolean existence check.checkpointsgetter — unmodifiableMap<String, CommandHistory<S>>.- Checkpoints survive
execute,undo,redo, andclearHistory. They are included in==andhashCodeso checkpoint changes trigger Riverpod/BLoC rebuilds.
CommandHistory.fromStates(List<S> states, {int? maxSize})factory — reconstruct history from a list of persisted state values: the last element becomesstate, earlier elements form the undo stack oldest → newest. Useful when states were saved to disk but not the commands.CommandHistory.replay(S initial, List<Command<S>> commands, {int? maxSize})factory — rebuild history by replaying a list of commands from an initial state. Equivalent to chainingexecutecalls; merging rules apply between consecutive commands.executeIf(Command<S> command, {required bool condition})— executes only whenconditionistrue; returnsthisotherwise. Eliminates per-call-siteifguards.executeMany(List<Command<S>> commands)— executes a list of commands in order, each as its own undo step. Merging rules still apply between consecutive entries.prune(bool Function(S state) test)— removes entries from the undo stack for whichtestreturnstrue, leaving the current state and redo stack intact. Parallel label entries are removed alongside their state entries.- Tests: 136 → 200.
0.3.0 #
FunctionCommand<S>: build a command from a plain function with an optional label — no subclass needed (FunctionCommand((s) => s + 1, label: 'Increment')).CompositeCommand<S>: execute a list of commands as one atomic undo step (CompositeCommand([MoveCmd(), ResizeCmd()], label: 'Resize and Move')). The no-op check applies to the net result, so a fully no-op composite never pushes to the stack.undoLabels/redoLabels(plural): unmodifiableList<String?>getters exposing the full label stacks oldest→newest / most-recent-undo-first, parallel toundoStack/redoStack. Useful for building a history-panel UI.undoN(int n)/redoN(int n): undo or redo n steps at once. Clamps to available depth (does not throw). Assertsn >= 0.withMaxSize(int? newMaxSize): returns a copy of the history with a new cap. If the new cap is smaller than the current undo stack, the oldest entries are trimmed. Passnullto remove the cap. Returnsthiswhen the cap is unchanged.peekUndo/peekRedo:S?getters returning the state thatundo()/redo()would restore, without applying the operation. Returnnullwhen the respective stack is empty.timelinegetter:List<S>of every state in order —[...undoStack, state, ...redoStack]. IndexundoStack.lengthis always the present. Useful for time-travel sliders.stateAt(int index): read any state in [timeline] by absolute index without jumping there.jumpToIndex(int index): jump to any past position in the undo history in one call (0= oldest,undoStack.length= present).collapse(int n, {String? label}): squash the last n undo entries into one step — the surviving label defaults to the most-recently executed command's label. Useful at drag-end events to avoid hundreds of micro-commands in the undo stack.transform(S Function(S) fn): apply a function to every state in the history (current + all undo + all redo entries) at once. Propagates a schema migration or server-sync change across the entire snapshot ring without clearing history.undoWhile(bool Function(S) predicate)/redoWhile(bool Function(S) predicate): keep undoing/redoing as long as the current state satisfies the predicate. Stop when it doesn't or the respective stack is exhausted.- Tests: 58 → 136.
0.2.0 #
Breaking change: Command<S> is now abstract class (was abstract interface class).
Change implements Command<S> to extends Command<S> in your command classes.
- Immutable stacks:
undoStack/redoStackare now exposed viaUnmodifiableListView— any attempt to mutate them throwsUnsupportedError.replaceCurrentwith mappers usesList.unmodifiable()for the same guarantee. - Command labels:
CommandgainsString? get label => null. Override it to driveCommandHistory.undoLabel/CommandHistory.redoLabel(e.g."Undo Paint Stroke"). - Bounded history:
CommandHistory.initial(state, maxSize: N)caps undo depth; oldest entry is dropped on overflow. - Value equality:
==andhashCodecompare state + both stacks element-by-element, enabling use in Riverpod/BLoC state containers without spurious rebuilds and asMapkeys. clearHistory(): resets both stacks while preservingstateandmaxSize— useful after a save-point event.toString():CommandHistory(state: 5, undo: 2, redo: 0)for debugger output.- Tests: 26 → 58.
0.1.0 #
- Initial extraction from sdraw monorepo.
Command<S>: abstract interface for reversible state operations.CommandHistory<S>: generic immutable undo/redo ring.- No Flutter dependency — pure Dart.