1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
//! An undo/redo library with dynamic dispatch, state handling and automatic command merging. //! //! # About //! It uses the [Command Pattern] where the user implements the `UndoCmd` trait for each command. //! //! The `UndoStack` has two states, clean and dirty. The stack is clean when no more commands can //! be redone, otherwise it is dirty. The stack will notice when it's state changes to either dirty //! or clean, and call the user defined methods set in [`on_clean`] and [`on_dirty`]. This is useful if //! you want to trigger some event when the state changes, eg. enabling and disabling buttons in an ui. //! //! It also supports [automatic merging] of commands with the same id. //! //! # Redo vs Undo //! | | Redo | Undo | //! |-----------------|--------------|-----------------| //! | Dispatch | Static | Dynamic | //! | State Handling | Yes | Yes | //! | Command Merging | Yes (manual) | Yes (automatic) | //! //! `undo` uses [dynamic dispatch] instead of [static dispatch] to store the commands, which means //! it has some additional overhead compared to [`redo`]. However, this has the benefit that you //! can store multiple types of commands in a `UndoStack` at a time. Both supports state handling //! and command merging but `undo` will automatically merge commands with the same id, while //! in `redo` you need to implement the merge method yourself. //! //! # Examples //! //! ``` //! use undo::{self, UndoCmd, UndoStack}; //! //! #[derive(Clone, Copy, Debug)] //! struct PopCmd { //! vec: *mut Vec<i32>, //! e: Option<i32>, //! } //! //! impl UndoCmd for PopCmd { //! fn redo(&mut self) -> undo::Result { //! self.e = unsafe { //! let ref mut vec = *self.vec; //! vec.pop() //! }; //! Ok(()) //! } //! //! fn undo(&mut self) -> undo::Result { //! unsafe { //! let ref mut vec = *self.vec; //! let e = self.e.unwrap(); //! vec.push(e); //! } //! Ok(()) //! } //! } //! //! fn foo() -> undo::Result { //! let mut vec = vec![1, 2, 3]; //! let mut stack = UndoStack::new(); //! let cmd = PopCmd { vec: &mut vec, e: None }; //! //! stack.push(cmd)?; //! stack.push(cmd)?; //! stack.push(cmd)?; //! //! assert!(vec.is_empty()); //! //! stack.undo()?; //! stack.undo()?; //! stack.undo()?; //! //! assert_eq!(vec.len(), 3); //! Ok(()) //! } //! # foo().unwrap(); //! ``` //! //! [Command Pattern]: https://en.wikipedia.org/wiki/Command_pattern //! [`on_clean`]: struct.UndoStack.html#method.on_clean //! [`on_dirty`]: struct.UndoStack.html#method.on_dirty //! [automatic merging]: trait.UndoCmd.html#method.id //! [static dispatch]: https://doc.rust-lang.org/stable/book/trait-objects.html#static-dispatch //! [dynamic dispatch]: https://doc.rust-lang.org/stable/book/trait-objects.html#dynamic-dispatch //! [`redo`]: https://crates.io/crates/redo #![forbid(unstable_features)] #![deny(missing_docs, missing_debug_implementations, unused_import_braces, unused_qualifications)] extern crate fnv; mod group; mod stack; pub use group::UndoGroup; pub use stack::UndoStack; use std::fmt; use std::result; use std::error::Error; /// An unique id for an `UndoStack`. #[derive(Debug)] pub struct Id(u32); /// A specialized `Result` that does not carry any data on success. pub type Result = result::Result<(), Box<Error>>; /// Trait that defines the functionality of a command. /// /// Every command needs to implement this trait to be able to be used with the `UndoStack`. pub trait UndoCmd: fmt::Debug { /// Executes the desired command and returns `Ok` if everything went fine, and `Err` if /// something went wrong. fn redo(&mut self) -> Result; /// Restores the state as it was before [`redo`] was called and returns `Ok` if everything /// went fine, and `Err` if something went wrong. /// /// [`redo`]: trait.UndoCmd.html#tymethod.redo fn undo(&mut self) -> Result; /// Used for merging of `UndoCmd`s. /// /// Two commands are merged together when a command is pushed on the `UndoStack`, and it has /// the same id as the top command already on the stack. When commands are merged together, /// undoing and redoing them are done in one step. An example where this is useful is a text /// editor where you might want to undo a whole word instead of each character. /// /// Default implementation returns `None`, which means the command will never be merged. /// /// # Examples /// ``` /// use undo::{UndoCmd, UndoStack}; /// /// #[derive(Debug)] /// struct TxtCmd(char); /// /// impl UndoCmd for TxtCmd { /// fn redo(&mut self) -> undo::Result { /// Ok(()) /// } /// /// fn undo(&mut self) -> undo::Result { /// Ok(()) /// } /// /// fn id(&self) -> Option<u64> { /// // Merge cmd if not a space. /// if self.0 == ' ' { /// None /// } else { /// Some(1) /// } /// } /// } /// /// fn foo() -> undo::Result { /// let mut stack = UndoStack::new(); /// stack.push(TxtCmd('a'))?; /// stack.push(TxtCmd('b'))?; // 'a' and 'b' is merged. /// stack.push(TxtCmd(' '))?; /// stack.push(TxtCmd('c'))?; /// stack.push(TxtCmd('d')) // 'c' and 'd' is merged. /// } /// # foo().unwrap(); /// ``` #[inline] fn id(&self) -> Option<u64> { None } }