undoredo
undoredo is an undo-redo Rust library that works by wrapping a collection
inside a recorder decorator
that observes the incoming insertions, removals and pushes while recording the
changes in an reversible incremental diff structure.
This approach makes undoredo easier to use than other undo-redo
libraries. Storing incremental diffs typically results in much
more succint and reliable code than the commonly used Command
pattern, which is what
the popular and venerable undo and
undo_2 crates use. The programmer
is relieved from having to maintain application-specific implementations of
commands, often complicated and prone to elusive runtime bugs, on which the
Command pattern has to operate.
This library is no_std-compatible and has no mandatory dependencies except
for alloc. For ease of use,
undoredo has convenience implementations for standard library collections:
HashMap,
HashSet,
BTreeMap,
BTreeSet,
and for some third-party feature-gated types:
StableVec,
thunderdome::Arena,
rstar::RTree (read
more in the Supported collections section).
Usage
First, add undoredo to your Cargo.toml:
[dependencies]
undoredo = "0.2"
Basic usage
Following is a basic usage example of undoredo over HashMap. You can find
more examples in the examples/ directory.
use HashMap;
use ;
Storing and accessing command metadata along with edits
It is often desirable to store some metadata along with every recorded edit,
usually a representation of the command that originated it. This can be done by
instead committing the edit using the
.cmd_commit()
method.
The bistack of done and undone committed edits, together with their
command metadatas ("cmd") if present, can be accessed as slices from the
.done()
and
.undone()
accessor methods.
use HashMap;
use ;
// Representation of the command that originated the recorded edit.
Undo-redo on sets
Some data structures have set semantics: they operate only on values, without
exposing any usable notion of key or index. undoredo can provide its
functionality to a set by treating it as a ()-valued map whose keys are the
set's values. This is actually also how Rust's standard library
represents
HashSet
and
BTreeSet internally.
As an example, the following code will construct a recorder and an undo-redo
bistack for a BTreeSet:
let mut recorder: = new;
let mut undoredo: = new;
Keeping in mind to pass values as keys, recorder and
undoredo can then be used the same way as with maps above. See
examples/btreeset.rs for a complete example.
Among the supported third-party types, rstar::RTree is an instance of a
data structure with a convenience implementation over set semantics. See
examples/rstar.rs for an example of its usage.
NOTE: Some set-like data structures are actually multisets: they allow
to insert the same value multiple times without overriding the first one. In
fact, rstar::RTree is a multiset. undoredo will work correctly with such
data structures, seeing them as sets, but only if you never make use of their
multiset property: you must never insert a key to such a collection if it is
already there.
Supported collections
Standard library
Rust's standard library maps and sets are supported via built-in convenience implementations:
HashMapbehind thestdfeature (enabled by default),HashSetbehind thestdfeature (enabled by default),BTreeMap(not feature-gated),BTreeSet(not feature-gated).
Third-party types
In addition to the standard library, undoredo has built-in feature-gated
convenience implementations for data structures from certain external crates:
stable_vec::StableVecbehind thestable-vecfeature. (example usage: examples/stable_vec.rs),thunderdome::Arenabehind thethunderdomefeature. (example usage: examples/thunderdome.rs),rstar::RTreebehind therstarfeature. (example usage: examples/rstar.rs).
To use these, enable their corresponding features next to your undoredo
dependency in your Cargo.toml. For example, to enable all third-party type
implementations, write
[dependencies]
undoredo = { version = "0.2", features = ["stable-vec", "thunderdome", "rstar"]}
Technical sidenote: Unlike maps and sets, not all stable vec data structures
allow insertion and removal at arbitrary indexes regardless of whether they
are vacant, occupied or out of bounds. For StableVec, we managed to implement
inserting at out-of-bound indexes by changing the length before insertion using
the
.reserve_for()
method. For thunderdome::Arena, we insert at arbitrary key directly via the
.insert_at()
method. Collections for which we could not achieve this are documented in the
section below.
Unsupported collections
Slab,
SlotMap,
generational-arena
cannot be supported because they lack interfaces for insertion at an arbitrary
key.
Technical sidenote: For Slab, such interface is missing apparently
because
the freelist Slab uses to keep
track of its vacant indexes is only singly-linked, not doubly-linked. Inserting
an element at an arbitrary vacant index would require removing that index from
the freelist. But since there is no backwards link available at a given key,
doing so would require traversing the freelist from the beginning to find the
position of the previous node, which would incur an overly slow O(n) time
cost.
Contributing
We welcome issues and pull requests from anyone both to our canonical repository on Codeberg and to our GitHub mirror.
NOTE: This repository currently contains a Git submodule: src/maplike.
After git clone, remember to run
git submodule update --init
Licence
undoredo is dual-licensed as under either of
at your option.