From c0c3da18a37d2bb022e798b80a4d5717f9e95f90 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sun, 10 Aug 2025 16:43:08 +0800 Subject: [PATCH 1/3] Add undo_2 --- crates/bevy_text/Cargo.toml | 2 + crates/bevy_text/src/undo_2/README.md | 261 ++ crates/bevy_text/src/undo_2/lib.rs | 2177 +++++++++++++++++ .../src/undo_2/tests/iter_realized.rs | 72 + crates/bevy_text/src/undo_2/tests/merge.rs | 96 + crates/bevy_text/src/undo_2/tests/redo.rs | 83 + .../src/undo_2/tests/remove_undone.rs | 188 ++ .../src/undo_2/tests/set_and_transition.rs | 101 + crates/bevy_text/src/undo_2/tests/splice.rs | 198 ++ crates/bevy_text/src/undo_2/tests/unbuild.rs | 37 + crates/bevy_text/src/undo_2/tests/undo.rs | 61 + .../bevy_text/src/undo_2/tests/undo_tests.rs | 205 ++ 12 files changed, 3481 insertions(+) create mode 100644 crates/bevy_text/src/undo_2/README.md create mode 100644 crates/bevy_text/src/undo_2/lib.rs create mode 100644 crates/bevy_text/src/undo_2/tests/iter_realized.rs create mode 100644 crates/bevy_text/src/undo_2/tests/merge.rs create mode 100644 crates/bevy_text/src/undo_2/tests/redo.rs create mode 100644 crates/bevy_text/src/undo_2/tests/remove_undone.rs create mode 100644 crates/bevy_text/src/undo_2/tests/set_and_transition.rs create mode 100644 crates/bevy_text/src/undo_2/tests/splice.rs create mode 100644 crates/bevy_text/src/undo_2/tests/unbuild.rs create mode 100644 crates/bevy_text/src/undo_2/tests/undo.rs create mode 100644 crates/bevy_text/src/undo_2/tests/undo_tests.rs diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index e8b237f5e691d..0c41accecf4e3 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -40,9 +40,11 @@ serde = { version = "1", features = ["derive"] } smallvec = { version = "1", default-features = false } sys-locale = "0.3.0" tracing = { version = "0.1", default-features = false, features = ["std"] } +document-features = "0.2" [dev-dependencies] approx = "0.5.1" +serde_json = "1" [lints] workspace = true diff --git a/crates/bevy_text/src/undo_2/README.md b/crates/bevy_text/src/undo_2/README.md new file mode 100644 index 0000000000000..4c68438b8f10d --- /dev/null +++ b/crates/bevy_text/src/undo_2/README.md @@ -0,0 +1,261 @@ +# Undo done the right way! + +Via https://gitlab.com/okannen/undo_2/-/tree/b32c34edb2c15c266b946f0d82188624f3aa3fdc + +## Introduction + +An undo crate that makes it so that, the instant you edit something you undid +to, instead of truncating the undo/redo history, it bakes the rewind onto the +end of the Undo history as a precursor to your new change. I found the idea in +[zaboople/klong][zaboople]. This crate is an implementation +of this idea with a minor variation explained below. + +As an example consider the following sequence of commands: + +| Command | State | +| ------- | ----- | +| Init | | +| Do A | A | +| Do B | A, B | +| Undo | A | +| Do C | A, C | + +With the **classical undo**, repeated undo would lead to the sequence: + +| Command | State | +|---------|-------| +| | A, C | +| Undo | A | +| Undo | | + + +Starting from 5, with **undo_2**, repeating undo would lead to the sequence: + +| Command | State | +|---------|-------| +| | A, C | +| Undo | A | +| Undo | A,B | +| Undo | A | +| Undo | | + +**undo_2**'s undo navigates back in history, while classical undo navigates back +through the sequence of command that builds the state. + +This is actualy the way undo is often implemented in mac's (cocoa library), emacs +and it is similar to vim :earlier. + +## Features + + 1. historical undo sequence, no commands are lost. + 2. user-friendly compared to complex undo trees. + 3. optimized implementation: no commands are ever copied. + 4. very lightweight, dumb and simple. + 5. possibility to merge and splice commands. + +## How to use it + +Add the dependency to the cargo file: + +```[toml] +[dependencies] +undo_2 = "0.1" +``` + +Then add this to your source file: + +```ignore +use undo_2::Commands; +``` + +The example below implements a dumb text editor. *undo_2* does not perform +itself "undos" and "redos", rather, it returns a sequence of commands that must +be interpreted by the application. This design pattern makes implementation +easier because it is not necessary to borrow data within the stored list of +commands. + +``` +use undo_2::{Commands,Action}; +use Action::{Do,Undo}; + +enum Command { + Add(char), + Delete(char), +} + +struct TextEditor { + text: String, + command: Commands, +} + +impl TextEditor { + pub fn new() -> Self { + Self { + text: String::new(), + command: Commands::new(), + } + } + pub fn add_char(&mut self, c: char) { + self.text.push(c); + self.command.push(Command::Add(c)); + } + pub fn delete_char(&mut self) { + if let Some(c) = self.text.pop() { + self.command.push(Command::Delete(c)); + } + } + pub fn undo(&mut self) { + for action in self.command.undo() { + interpret_action(&mut self.text, action) + } + } + pub fn redo(&mut self) { + for action in self.command.redo() { + interpret_action(&mut self.text, action) + } + } +} + +fn interpret_action(data: &mut String, action: Action<&Command>) { + use Command::*; + match action { + Do(Add(c)) | Undo(Delete(c)) => { + data.push(*c); + } + Undo(Add(_)) | Do(Delete(_)) => { + data.pop(); + } + } +} + +let mut editor = TextEditor::new(); +editor.add_char('a'); // :[1] +editor.add_char('b'); // :[2] +editor.add_char('d'); // :[3] +assert_eq!(editor.text, "abd"); + +editor.undo(); // first undo :[4] +assert_eq!(editor.text, "ab"); + +editor.add_char('c'); // :[5] +assert_eq!(editor.text, "abc"); + +editor.undo(); // Undo [5] :[6] +assert_eq!(editor.text, "ab"); +editor.undo(); // Undo the undo [4] :[7] +assert_eq!(editor.text, "abd"); +editor.undo(); // Undo [3] :[8] +assert_eq!(editor.text, "ab"); +editor.undo(); // Undo [2] :[9] +assert_eq!(editor.text, "a"); +``` + +## More information + +1. After a sequence of consecutive undo, if a new command is added, the undos + forming the sequence are merged. This makes the traversal of the undo + sequence more concise by avoiding state duplication. + +| Command | State | Comment | +|---------|------- |----------------------| +| Init | | | +| Do A | A | | +| Do B | A,B | | +| Do C | A, B, C | | +| Undo | A, B |Merged | +| Undo | A |Merged | +| Do D | A, D | | +| Undo | A |Redo the 2 Merged Undo| +| Undo | A, B, C | | +| Undo | A, B | | +| Undo | A | | +| Undo | | | + +2. Each execution of an undos or redo may lead to the execution of a sequence of + actions in the form `Undo(a)+Do(b)+Do(c)`. Basic arithmetic is implemented + assuming that `Do(a)+Undo(a)` is equivalent to not doing anything (here the + 2 `a`'s designate the same entity, not to equal objects). + +The piece of code below, which is the longer version of the code above, illustrates points 1 and 2. + +```ignore +let mut editor = TextEditor::new(); +editor.add_char('a'); // :[1] +editor.add_char('b'); // :[2] +editor.add_char('d'); // :[3] +assert_eq!(editor.text, "abd"); + +editor.undo(); // first undo :[4] +assert_eq!(editor.text, "ab"); + +editor.add_char('c'); // :[5] +assert_eq!(editor.text, "abc"); + +editor.undo(); // Undo [5] :[6] +assert_eq!(editor.text, "ab"); +editor.undo(); // Undo the undo [4] :[7] +assert_eq!(editor.text, "abd"); +editor.undo(); // Undo [3] :[8] +assert_eq!(editor.text, "ab"); +editor.undo(); // Undo [2] :[9] +assert_eq!(editor.text, "a"); + +editor.add_char('z'); // :[10] +assert_eq!(editor.text, "az"); +// when an action is performed after a sequence +// of undo, the undos are merged: undos [6] to [9] are merged now + +editor.undo(); // back to [10] +assert_eq!(editor.text, "a"); +editor.undo(); // back to [5]: reverses the consecutive sequence of undos in batch +assert_eq!(editor.text, "abc"); +editor.undo(); // back to [4] +assert_eq!(editor.text, "ab"); +editor.undo(); // back to [3] +assert_eq!(editor.text, "abd"); +editor.undo(); // back to [2] +assert_eq!(editor.text, "ab"); +editor.undo(); // back to [1] +assert_eq!(editor.text, "a"); +editor.undo(); // back to [0] +assert_eq!(editor.text, ""); + +editor.redo(); // back to [1] +assert_eq!(editor.text, "a"); +editor.redo(); // back to [2] +assert_eq!(editor.text, "ab"); +editor.redo(); // back to [3] +assert_eq!(editor.text, "abd"); +editor.redo(); // back to [4] +assert_eq!(editor.text, "ab"); +editor.redo(); // back to [5] +assert_eq!(editor.text, "abc"); +editor.redo(); // back to [9]: redo inner consecutive sequence of undos in batch + // (undo are merged only when they are not the last action) +assert_eq!(editor.text, "a"); +editor.redo(); // back to [10] +assert_eq!(editor.text, "az"); + +editor.add_char('1'); +editor.add_char('2'); +assert_eq!(editor.text, "az12"); +editor.undo(); +editor.undo(); +assert_eq!(editor.text, "az"); +editor.redo(); // undo is the last action, undo the undo only once +assert_eq!(editor.text, "az1"); +editor.redo(); +assert_eq!(editor.text, "az12"); +``` + +## Release note +### Version 0.3 + - [Action] is now an enum taking commands, the list of command to be + executed is of the form [Action]; + - added [Commands::can_undo] and [Commands::can_redo]; + - added [Commands::rebuild], which correspond to the classical redo; + - fixed a bug in [Commands::undo_or_redo_to_index] + - Added support for special commands that represent a state setting. See [SetOrTransition]. + +[zaboople]: https://github.com/zaboople/klonk/blob/master/TheGURQ.md + diff --git a/crates/bevy_text/src/undo_2/lib.rs b/crates/bevy_text/src/undo_2/lib.rs new file mode 100644 index 0000000000000..384e27c1ee923 --- /dev/null +++ b/crates/bevy_text/src/undo_2/lib.rs @@ -0,0 +1,2177 @@ +#![doc = include_str!("../../README-undo.md")] +#![doc = document_features::document_features!()] +use core::borrow::Borrow; +use core::cmp::min; +use core::iter::FusedIterator; +use core::ops::ControlFlow; +use core::ops::{Deref, Index}; +use core::option; +use std::fmt::Debug; +use std::hash::{DefaultHasher, Hash, Hasher as _}; +use std::slice::SliceIndex; +use std::{slice, vec}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Action that should be taken on the paired command +/// +/// [ActionIter] item type is a pair `(Action, C)` where +/// C is the client defined command type. `(Action::Do, t)` +/// signifies that the command `t` shall be executed. `(Action::Undo,t)` +/// means it shall be undone. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum Action { + /// The client shall do the paired command. + Do(T), + /// The client shall undo the paired command. + Undo(T), +} + +/// The commands in that are listed in [Commands] shall +/// represent state *transition* so that this transitions can +/// be done and undone. For exemple the state transition "add the letter 'a' +/// after the cursor" can be done by adding the letter 'a' and moving the cursor left, +/// or undone by executing a backspace. +/// +/// Representing commands as *transitions* is necessary as commands executions will +/// depend of the current state of the program that is a concequence of the sequence +/// of execution of indetermined commands. +/// +/// But sometimes, the transition affects a piece of state of the program which is +/// absolutely indepent of other state of the program and of which no other command depends. Lets +/// call such state *independent state*. This could be for exemple the position of the view port of the text editor. Moving the +/// position of the view port does not affect the edition. On the other hand, the current color +/// of the pencil can not be considered an *independent state* as edition commands depend on this +/// color. +/// +/// For such *independ state* the resulting global state of the program is independent of the order +/// in which the transition to this state are applied, and it is independent of the way this +/// transition are interlieved with other transitions. +/// +/// So the set of all commands can be partitionned in two: the set of transitions affecting +/// indepent state, and the other transitions. [SetOrTransition] provides such a partition. +/// It is supposed to be used as the element of the [Commands]: +/// `Commands`. +/// +/// [Commands] provides dedicateds methods (see [apply_actions][Commands::apply_actions]) to simplify the use of +/// commands partitionned in independent state and transitions. +/// +/// State commands are supposed to represent the setting of an indepent state such as "set the view +/// port to `x`, `y`, `lx`, `ly`". When a independent state command is undone within the `apply_actions`, the +/// algorithm will look for the previous application of a command the same key [IndepStateKey::key] and will +/// command the application of it. +/// +/// # Example +/// +/// In this example we use commands that set the state `color` and `length`. +/// +/// In the commands registered the previous states of `color` and `length` are not stored +/// but [Commands::apply_actions] will retrieve automatically the value to set for this states. +/// ``` +/// use std::mem::{discriminant, Discriminant}; +/// +/// use undo_2::{Commands, IndepStateKey, SetOrTransition, SetTransAction}; +/// +/// #[derive(Copy, Clone, Debug)] +/// struct State { +/// color: f64, +/// length: f64, +/// } +/// impl State { +/// fn new() -> Self { +/// INIT_STATE +/// } +/// fn apply_set(&mut self, c: &SetCommands) { +/// match c { +/// SetCommands::Color(v) => self.color = *v, +/// SetCommands::Length(v) => self.length = *v, +/// }; +/// } +/// fn execute_action(&mut self, c: SetTransAction) { +/// match c { +/// SetTransAction::Do(_) => {} +/// SetTransAction::Undo(_) => {} +/// SetTransAction::Set(c) => self.apply_set(c), +/// SetTransAction::SetToInitial(d) => self.apply_set(SetCommands::new_initial(d)), +/// } +/// } +/// } +/// static INIT_STATE: State = State { +/// color: 0., +/// length: 0., +/// }; +/// +/// #[derive(Debug, Copy, Clone, PartialEq)] +/// enum SetCommands { +/// Color(f64), +/// Length(f64), +/// } +/// static INIT_COLOR: SetCommands = SetCommands::Color(INIT_STATE.color); +/// static INIT_LENGTH: SetCommands = SetCommands::Length(INIT_STATE.length); +/// +/// impl SetCommands { +/// fn new_initial(d: Discriminant) -> &'static Self { +/// if d == discriminant(&INIT_COLOR) { +/// &INIT_COLOR +/// } else if d == discriminant(&INIT_LENGTH) { +/// &INIT_LENGTH +/// } else { +/// unreachable!("SetCommands::new_initial is not exhaustive: please, adds lacking initial value to this method") +/// } +/// } +/// } +/// +/// impl IndepStateKey for SetCommands { +/// type KeyType = Discriminant; +/// +/// fn key(&self) -> Self::KeyType { +/// discriminant(self) +/// } +/// } +/// +/// #[derive(Copy, Clone, Debug, PartialEq, Eq)] +/// enum TransitionCommand { +/// A, +/// B, +/// } +/// +/// use SetOrTransition::*; +/// let mut commands = Commands::new(); +/// let mut state = State::new(); +/// +/// let c = SetCommands::Color(1.); +/// state.apply_set(&c); +/// commands.push(Set(c)); +/// +/// commands.push(Transition(TransitionCommand::A)); +/// commands.push(Transition(TransitionCommand::B)); +/// +/// let c = SetCommands::Length(10.); +/// state.apply_set(&c); +/// commands.push(Set(c)); +/// +/// let c = SetCommands::Color(2.); +/// state.apply_set(&c); +/// commands.push(Set(c)); +/// +/// commands.apply_undo(|c| { +/// assert_eq!(c, SetTransAction::Set(&SetCommands::Color(1.))); +/// state.execute_action(c); +/// }); +/// assert_eq!(state.color, 1.); +/// assert_eq!(state.length, 10.); +/// commands.apply_redo(|c| { +/// assert_eq!(c, SetTransAction::Set(&SetCommands::Color(2.))); +/// state.execute_action(c); +/// }); +/// assert_eq!(state.color, 2.); +/// assert_eq!(state.length, 10.); +///``` +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum SetOrTransition { + /// A command that represent the setting of an independent state + Set(S), + /// Other commands + Transition(T), +} +/// A key discriminating the different type of +/// independent state commands. For most enum, +/// it should `KeyType=std::mem::Discriminant` +/// and the key is generated by `std::mem::discriminant(&self)` +/// +/// # Exemple +/// ```ignore +/// enum SetCommands{ +/// ViewPort{x:f64,y:f64,lx:f64,ly:f64}, +/// HideComment(bool), +/// } +/// +/// impl IndepStateKey for SetCommands { +/// type KeyType = std::mem::Discriminant +/// fn key(&self) -> Self::KeyType { +/// std::mem::discriminant(self) +/// } +/// } +/// ``` +pub trait IndepStateKey { + type KeyType: PartialEq + Hash; + fn key(&self) -> Self::KeyType; +} + +/// The actions asked to be performed by the user +/// when calling [Commands::apply_actions] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum SetTransAction<'a, S: IndepStateKey, T> { + /// Ask the execution of the transition .0 + Do(&'a T), + /// Ask the reverse of the execution of the transition .0 + Undo(&'a T), + /// Ask to set state to .0 + Set(&'a S), + /// Ask to set the state corresponding to the key .0 to its initial + /// value: the value that it has before any command where applied to + /// it + SetToInitial(S::KeyType), +} + +impl Action { + pub fn as_inner(&self) -> &T { + match self { + Action::Do(a) => a, + Action::Undo(a) => a, + } + } + pub fn as_inner_mut(&mut self) -> &mut T { + match self { + Action::Do(a) => a, + Action::Undo(a) => a, + } + } + pub fn into_inner(self) -> T { + match self { + Action::Do(a) => a, + Action::Undo(a) => a, + } + } +} + +/// The items stored in [Commands]. +/// +/// The list of `CommandItem` is accessible by dereferencing +/// the command list. +/// +/// *NB*: The value inside the Undo variant is the number +/// of time the undo command is repeated minus 1. +/// +/// # Example +/// +/// ``` +/// use undo_2::{Commands,CommandItem}; +/// +/// let mut commands = Commands::new(); +/// +/// #[derive(Debug,PartialEq)] +/// struct A; +/// +/// commands.push(A); +/// commands.undo(); +/// +/// assert_eq!(*commands, [CommandItem::Command(A),CommandItem::Undo(0)]); +/// +/// use CommandItem::Undo; +/// assert_eq!(*commands, [A.into(), Undo(0)]); +/// +/// commands.push(A); +/// commands.undo(); +/// commands.undo(); +/// assert_eq!(*commands, [A.into(), Undo(0),A.into(),Undo(1)]); +/// ``` +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum CommandItem { + // A command typically created by [Commands::push](Commands#method.push) + Command(T), + // Signify that `count` CommandItem previous to this item are undone. + // + // Where `count` refers to this variant field. + Undo(usize), +} + +/// Owns a slice of [commands](CommandItem) to undo and redo. +/// +/// Commands are added by invoking [`push`](#method.push) method. [`undo`](#method.undo) and +/// [`redo`](#method.redo) return a list of [`Action`] that the application must execute. +/// +/// To see a full functional example, read [How to use it](index.html#how-to-use-it). +/// +/// # Example +/// ``` +/// use undo_2::{Action, Commands}; +/// +/// #[derive(Debug, Eq, PartialEq)] +/// enum Command { +/// A, +/// B, +/// } +/// use Command::*; +/// use Action::*; +/// +/// let mut commands = Commands::new(); +/// +/// commands.push(A); +/// commands.push(B); +/// +/// let v: Vec<_> = commands.undo().collect(); +/// assert_eq!(v, [Undo(&B)]); +/// +/// let v: Vec<_> = commands.undo().collect(); +/// assert_eq!(v, [Undo(&A)]); +/// +/// commands.push(A); +/// +/// let v: Vec<_> = commands.undo().collect(); +/// assert_eq!(v, [Undo(&A)]); +/// +/// // undo the first 2 undos +/// let v: Vec<_> = commands.undo().collect(); +/// assert_eq!(v, [Do(&A), Do(&B)]); +/// ``` +/// +/// # Representation +/// +/// `Commands` owns a slice of [CommandItem] that is accesible +/// by dereferencing the command. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Eq)] +pub struct Commands { + commands: Vec>, + #[cfg_attr(feature = "serde", serde(skip))] + undo_cache: Vec, +} +impl Debug for Commands { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Commands") + .field("commands", &self.commands) + .finish() + } +} +impl PartialEq for Commands { + fn eq(&self, other: &Self) -> bool { + self.commands == other.commands + } +} +impl Hash for Commands { + fn hash(&self, state: &mut H) { + self.commands.hash(state); + } +} +impl Default for Commands { + fn default() -> Self { + Self { + commands: Default::default(), + undo_cache: Default::default(), + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum CachedAction { + Do, + Undo, + Skip, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +struct IndexedAction { + action: CachedAction, + index: usize, +} + +/// Specify a merge when calling [Commands::merge](Commands#method.merge) +/// +/// The [`end`, `start`) bounds the slice of command that will +/// be removed during the merge. `end` and `start` are in reverse order +/// because [IterRealized] goes backward. +/// +/// If `command` is `None` then the slice will be removed, otherwise if +/// the `command` is `Some(c)` the slice will be replace by `c`. +#[derive(Debug)] +pub struct Merge<'a, T> { + pub start: IterRealized<'a, T>, + pub end: IterRealized<'a, T>, + pub command: Option, +} + +/// Specify a splice when calling [Commands::splice](Commands#method.splice) +/// +/// The [`end`, `start`) bounds the slice of command that will +/// be removed during the merge. `end` and `start` are in reverse order +/// because [IterRealized] goes backward. +/// +/// The removed slice is then replaced by the sequence (not reversed) of +/// commands denoted by `commands`. +#[derive(Debug)] +pub struct Splice<'a, T, I: IntoIterator> { + pub start: IterRealized<'a, T>, + pub end: IterRealized<'a, T>, + pub commands: I, +} + +#[derive(Debug)] +/// Iterator of actions returned by [Commands::undo](Commands#method.undo) and +/// [Commands::redo](Commands#method.redo) +pub struct ActionIter<'a, T> { + commands: &'a [CommandItem], + to_do: std::slice::Iter<'a, IndexedAction>, +} +impl Clone for ActionIter<'_, T> { + fn clone(&self) -> Self { + Self { + commands: self.commands, + to_do: self.to_do.clone(), + } + } +} + +#[derive(Debug)] +/// The type of the iterator returned by [Commands::iter_realized](Commands#method.iter_realized). +pub struct IterRealized<'a, T> { + commands: &'a [CommandItem], + current: usize, +} +impl Clone for IterRealized<'_, T> { + fn clone(&self) -> Self { + Self { ..*self } + } +} + +impl Commands> { + /// Apply a series of actions represented by the type [SetTransAction] executed + /// by argument `f`. + /// + /// The list of action to be applied are provided by the call of `act` on + /// this [Commands]. This may be defined, for exemple as, `|c| c.redo()` + /// + /// This command only exist when the commands have the type [SetOrTransition]. + /// See the documentation of this type for an explanation. + /// + /// The function `f` will be called with [Do][SetTransAction::Do] and [Undo][SetTransAction::Undo] of commands that are + /// represented as [transitions][SetOrTransition::Transition], and [Set][SetTransAction::Set] or [SetToInitial][SetTransAction::SetToInitial] + /// (most probably only once) per [key][IndepStateKey::key] for commands that represent independent state setting. + pub fn apply_actions( + &mut self, + act: impl FnOnce(&mut Self) -> ActionIter<'_, SetOrTransition>, + mut f: impl FnMut(SetTransAction), + ) { + let mut state_commands = Vec::new(); + for command in act(self) { + Self::apply_action(command, &mut state_commands, &mut f) + } + self.restore_state(state_commands, f); + } + /// Equivalent to `apply_actions(|c| c.undo(),f)`. + pub fn apply_undo(&mut self, f: impl FnMut(SetTransAction)) { + self.apply_actions(|s| s.undo(), f) + } + /// Equivalent to `apply_actions(|c| c.redo(),f)`. + pub fn apply_redo(&mut self, f: impl FnMut(SetTransAction)) { + self.apply_actions(|s| s.redo(), f) + } + fn apply_action( + action: Action<&SetOrTransition>, + state_keys: &mut Vec>, + mut f: impl FnMut(SetTransAction), + ) { + match action { + Action::Do(SetOrTransition::Transition(tr)) => f(SetTransAction::Do(tr)), + Action::Undo(SetOrTransition::Transition(tr)) => f(SetTransAction::Undo(tr)), + Action::Do(SetOrTransition::Set(s)) | Action::Undo(SetOrTransition::Set(s)) => { + state_keys.push(Some(s.key())) + } + } + } + fn restore_state( + &self, + mut state_keys: Vec>, + mut f: impl FnMut(SetTransAction), + ) { + state_keys.sort_by_key(|v| { + let mut hasher = DefaultHasher::new(); + v.hash(&mut hasher); + hasher.finish() + }); + state_keys.dedup(); + let mut l = state_keys.len(); + if l == 0 { + return; + } + for command in self.iter_realized() { + if let SetOrTransition::Set(st) = command { + let st_key = st.key(); + if let Some(disc) = + state_keys.iter_mut().find( + |v| { + if let Some(d) = v { + *d == st_key + } else { + false + } + }, + ) + { + *disc = None; + f(SetTransAction::Set(st)); + l -= 1; + if l == 0 { + break; + } + } + } + } + if l > 0 { + for disc in state_keys.into_iter().flatten() { + f(SetTransAction::SetToInitial(disc)) + } + } + } +} + +impl Commands { + /// Create a new empty command sequence of type `T`. + pub fn new() -> Self { + Self { + commands: vec![], + undo_cache: vec![], + } + } + /// The capacity of the underlying storage + pub fn capacity(&self) -> usize { + self.commands.capacity() + } + /// Reserve space for new commands + pub fn reserve(&mut self, additional: usize) { + self.commands.reserve(additional) + } + + /// Return a reference to the last command + /// if it is not an Undo. + /// # Example + /// ``` + /// use undo_2::{Commands, CommandItem}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// + /// assert_eq!(commands.last_command().unwrap(), &A); + /// assert_eq!(commands.last().unwrap(), &CommandItem::Command(A)); + /// + /// commands.undo(); + /// assert!(commands.last_command().is_none()); + /// ``` + pub fn last_command(&self) -> Option<&T> { + self.commands.last().and_then(|v| match v { + CommandItem::Command(c) => Some(c), + CommandItem::Undo(_) => None, + }) + } + + /// Return a mutable reference to the last command + /// if it is not an Undo. + /// # Example + /// ``` + /// use undo_2::{Commands, CommandItem}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// + /// *commands.last_command_mut().unwrap() = Command::B; + /// + /// assert_eq!(commands.last_command().unwrap(), &B); + /// assert_eq!(commands.last().unwrap(), &CommandItem::Command(B)); + /// + /// commands.undo(); + /// assert!(commands.last_command_mut().is_none()); + /// ``` + pub fn last_command_mut(&mut self) -> Option<&mut T> { + self.commands.last_mut().and_then(|v| match v { + CommandItem::Command(c) => Some(c), + CommandItem::Undo(_) => None, + }) + } + /// Change in place the last command. + /// The update mail fail. + /// + /// It returns true if the update was possible and false otherwise. + /// + /// If the last command is accessible for modification, `updater` + /// will receive this last command to update this last command. If + /// updater shall return true if it modify the command and false otherwise + /// + /// # Example + /// ``` + /// use undo_2::{Commands, CommandItem}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A(usize), + /// B, + /// C, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A(0)); + /// + /// let updater = |a:&mut Command| if let A(i) = a { + /// *i+=1; + /// true + /// } else { + /// false + /// }; + /// + /// assert_eq!(commands.last_command().unwrap(), &A(0)); + /// assert!(commands.update_last(updater)); + /// assert_eq!(commands.last_command().unwrap(), &A(1)); + /// commands.undo(); + /// assert!(!commands.update_last(updater)); + /// commands.push(B); + /// assert!(!commands.update_last(updater)); + /// ``` + pub fn update_last(&mut self, updater: impl FnOnce(&mut T) -> bool) -> bool { + if let Some(c) = self.last_command_mut() { + updater(c) + } else { + false + } + } + /// Change in place the last command or push a new command + /// + /// If the last command is accessible for modification, `updater` + /// will receive this last command to update this last command. Otherwise + /// it will receive none. + /// + /// If updater return `Some`, the value returned is then pushed in the command list. + /// + /// + /// # Example + /// ``` + /// use undo_2::{Commands, CommandItem}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A(usize), + /// B, + /// C, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A(0)); + /// + /// let updater = |a:&mut Command| if let A(i) = a { + /// *i=10; + /// true + /// } else { + /// false + /// }; + /// let producer = || A(10); + /// + /// assert_eq!(commands.last_command().unwrap(), &A(0)); + /// commands.update_last_or_push(updater, producer); + /// assert_eq!(commands.last_command().unwrap(), &A(10)); + /// commands.undo(); + /// commands.update_last_or_push(updater, producer); + /// assert_eq!(commands.last_command().unwrap(), &A(10)); + /// commands.push(B); + /// commands.update_last_or_push(updater, producer); + /// assert_eq!(commands.last_command().unwrap(), &A(10)); + /// ``` + pub fn update_last_or_push( + &mut self, + updater: impl FnOnce(&mut T) -> bool, + producer: impl FnOnce() -> T, + ) { + if !self.update_last(updater) { + self.push(producer()) + } + } + + /// Add the command `T` + /// + /// # Example + /// ``` + /// use undo_2::Commands; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&A]); + /// ``` + pub fn push(&mut self, command: T) { + self.commands.push(CommandItem::Command(command)); + } + + /// Return an iterator over a sequence of actions to be performed by the client application to + /// undo. + /// + /// # Example + /// ``` + /// use undo_2::{Action,Commands}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C + /// } + /// use Command::*; + /// use Action::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// commands.push(B); + /// + /// let v: Vec<_> = commands.undo().collect(); + /// assert_eq!(v, [Undo(&B)]); + /// + /// commands.push(C); + /// + /// let v: Vec<_> = commands.undo().collect(); + /// assert_eq!(v, [Undo(&C)]); + /// + /// let v: Vec<_> = commands.undo().collect(); + /// assert_eq!(v, [Do(&B)]); + /// + /// let v: Vec<_> = commands.undo().collect(); + /// assert_eq!(v, [Undo(&B)]); + /// + /// let v: Vec<_> = commands.undo().collect(); + /// assert_eq!(v, [Undo(&A)]); + /// + /// let v: Vec<_> = commands.undo().collect(); + /// assert!(v.is_empty()) + /// ``` + #[must_use = "the returned actions should be realized"] + pub fn undo(&mut self) -> ActionIter<'_, T> { + self.undo_repeat(1) + } + /// Return an iterator over a sequence of actions to be performed by the client application to + /// undo `repeat` time. + /// + /// `undo_repeat(1)` is equivalent to `undo()` + /// + /// # Example + /// ``` + /// use undo_2::{Action,Commands}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C + /// } + /// use Command::*; + /// use Action::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// commands.push(B); + /// + /// let v: Vec<_> = commands.undo().collect(); + /// assert_eq!(v, [Undo(&B)]); + /// + /// commands.push(C); + /// + /// let v: Vec<_> = commands.undo_repeat(4).collect(); + /// assert_eq!(v, [Undo(&C), Undo(&A)]); + /// ``` + #[must_use = "the returned actions should be realized"] + pub fn undo_repeat(&mut self, repeat: usize) -> ActionIter<'_, T> { + let repeat = if let Some(v) = repeat.checked_sub(1) { + v + } else { + return ActionIter::new(); + }; + let l = self.len(); + match self.commands.last_mut() { + Some(CommandItem::Command(_)) => { + let count = min(repeat, l - 1); + self.commands.push(CommandItem::Undo(count)); + ActionIter::undo_at_count( + &self.commands, + &mut self.undo_cache, + l - 1 - count, + count, + ) + } + Some(CommandItem::Undo(i)) => { + if *i + 2 < l { + let count = min(l - *i - 3, repeat); + *i = *i + 1 + count; + let t = l - *i - 2; + ActionIter::undo_at_count(&self.commands, &mut self.undo_cache, t, count) + } else { + ActionIter::new() + } + } + None => ActionIter::new(), + } + } + /// An undo that skip undo branches. + /// + /// It returns the command that must be undone. + /// + /// It is equivalent to multiple successive call to `undo`. It behaves + /// as a classical undo. + /// + /// # Example + /// ``` + /// use undo_2::{Commands,Action}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// } + /// use Command::*; + /// use Action::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// commands.push(B); + /// commands.undo(); + /// commands.push(C); + /// + /// let c: Vec<_> = commands.unbuild().collect(); + /// assert_eq!(c, &[Undo(&C)]); + /// + /// let c: Vec<_> = commands.unbuild().collect(); + /// assert_eq!(c, &[Undo(&A)]); + /// + /// let c: Vec<_> = commands.unbuild().collect(); + /// assert!(c.is_empty()); + /// ``` + #[must_use = "the returned command should be undone"] + pub fn unbuild(&mut self) -> ActionIter<'_, T> { + let mut it = self.iter_realized(); + if it.next().is_none() { + return ActionIter::new(); + } + let start = it.index(); + let to_undo = if it.next().is_some() { + start - it.index() + } else { + start + 1 + }; + self.undo_repeat(to_undo) + } + #[must_use = "the returned command should be undone"] + pub fn rebuild(&mut self) -> ActionIter<'_, T> { + if !self.is_undoing() { + return ActionIter::new(); + } + let l = self.commands.len(); + let mut it = IterRealized { + commands: &self.commands[..l - 1], + current: l - 1, + }; + + let mut prev_i = l - 1; + match self.current_command_index() { + Some(cur_i) => { + while it.next().is_some() { + let n_i = it.index(); + if n_i <= cur_i { + break; + } + prev_i = n_i; + } + } + None => { + while it.next().is_some() { + prev_i = it.index(); + } + } + } + self.undo_or_redo_to_index(prev_i) + } + /// Return an iterator over a sequence of actions to be performed by the client application to + /// redo. + /// + /// # Example + /// ``` + /// use undo_2::{Action,Commands}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// use Action::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// commands.push(B); + /// commands.undo(); + /// let v: Vec<_> = commands.redo().collect(); + /// + /// assert_eq!(v, [Do(&B)]); + /// ``` + #[must_use = "the returned actions should be realized"] + pub fn redo(&mut self) -> ActionIter<'_, T> { + self.redo_repeat(1) + } + /// Return an iterator over a sequence of actions to be performed by the client application to + /// redo `repeat` time. + /// + /// # Example + /// ``` + /// use undo_2::{Action,Commands}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// use Action::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// commands.push(B); + /// commands.undo(); + /// commands.undo(); + /// let v: Vec<_> = commands.redo_repeat(2).collect(); + /// + /// assert_eq!(v, [Do(&A),Do(&B)]); + /// ``` + #[must_use = "the returned actions should be realized"] + pub fn redo_repeat(&mut self, repeat: usize) -> ActionIter<'_, T> { + let repeat = if let Some(v) = repeat.checked_sub(1) { + v + } else { + return ActionIter::new(); + }; + let l = self.len(); + match self.commands.last_mut() { + Some(CommandItem::Undo(i)) => { + if let Some(ni) = i.checked_sub(repeat.checked_add(1).unwrap()) { + let t = l - 2 - *i; + *i = ni; + ActionIter::do_at_count(&self.commands, &mut self.undo_cache, t, repeat) + } else { + let count = *i; + self.commands.pop(); + ActionIter::do_at_count(&self.commands, &mut self.undo_cache, l - 2, count) + } + } + _ => ActionIter::new(), + } + } + /// Return an iterator over a sequence of actions to be performed by the client application to + /// undo all commands (to return to the initial state). + /// + /// # Example + /// ``` + /// use undo_2::{Action,Commands}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// } + /// use Command::*; + /// use Action::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// commands.push(B); + /// + /// let v: Vec<_> = commands.undo_all().collect(); + /// assert_eq!(v, [Undo(&B), Undo(&A)]); + /// ``` + pub fn undo_all(&mut self) -> ActionIter<'_, T> { + use CommandItem::*; + let j = match self.last() { + None => return ActionIter::new(), + Some(Command(_)) => self.len(), + Some(Undo(i)) => self.len() - 2 - i, + }; + self.undo_repeat(j) + } + /// Return an iterator over a sequence of actions to be performed by the client application to + /// redo all undo. + /// + /// # Example + /// ``` + /// use undo_2::{Action,Commands}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// } + /// use Command::*; + /// use Action::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// commands.push(B); + /// commands.undo(); + /// commands.undo(); + /// + /// let v: Vec<_> = commands.redo_all().collect(); + /// assert_eq!(v, [Do(&A), Do(&B)]); + /// ``` + #[must_use = "the returned actions should be realized"] + pub fn redo_all(&mut self) -> ActionIter<'_, T> { + let l = self.len(); + match self.commands.last_mut() { + Some(CommandItem::Undo(i)) => { + let count = *i; + self.commands.pop(); + ActionIter::do_at_count(&self.commands, &mut self.undo_cache, l - 2, count) + } + _ => ActionIter::new(), + } + } + /// Return `true` if the last action is an undo. + /// + /// # Example + /// ``` + /// use undo_2::Commands; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// assert!(!commands.is_undoing()); + /// + /// commands.push(A); + /// assert!(!commands.is_undoing()); + /// + /// commands.undo(); + /// assert!(commands.is_undoing()); + /// + /// commands.push(A); + /// commands.push(A); + /// commands.undo(); + /// commands.undo(); + /// assert!(commands.is_undoing()); + /// commands.redo(); + /// assert!(commands.is_undoing()); + /// commands.redo(); + /// assert!(!commands.is_undoing()); + /// ``` + pub fn is_undoing(&self) -> bool { + matches!(self.commands.last(), Some(CommandItem::Undo(_))) + } + + /// Check weither there are still command that can be undone + /// + /// # Example + /// ``` + /// use undo_2::Commands; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// assert!(!commands.can_undo()); + /// + /// commands.push(A); + /// assert!(commands.can_undo()); + /// + /// commands.undo(); + /// assert!(!commands.can_undo()); + /// ``` + pub fn can_undo(&self) -> bool { + match self.commands.last() { + None => false, + Some(CommandItem::Command(_)) => true, + Some(CommandItem::Undo(i)) => i + 2 < self.commands.len(), + } + } + /// Check weither there are still command that can be redone + /// + /// # Example + /// ``` + /// use undo_2::Commands; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// assert!(!commands.can_redo()); + /// + /// commands.push(A); + /// assert!(!commands.can_redo()); + /// + /// commands.undo(); + /// assert!(commands.can_redo()); + /// ``` + pub fn can_redo(&self) -> bool { + self.is_undoing() + } + + /// Return the index of the first realized [command item](CommandItem). + /// + /// # Example + /// ``` + /// use undo_2::*; + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// + /// let mut c = Commands::new(); + /// + /// c.push(A); + /// c.push(B); + /// + /// let v: Vec<_> = c.iter().collect(); + /// assert_eq!(v, [&CommandItem::Command(A), &CommandItem::Command(B)]); + /// + /// assert_eq!(c.current_command_index(), Some(1)); + /// + /// c.undo(); + /// + /// let v: Vec<_> = c.iter().collect(); + /// assert_eq!(v, [&CommandItem::Command(A), &CommandItem::Command(B), &CommandItem::Undo(0)]); + /// + /// assert_eq!(c.current_command_index(), Some(0)); + /// ``` + pub fn current_command_index(&self) -> Option { + let mut it = self.iter_realized(); + it.next()?; + Some(it.current) + } + + /// Repeat undo or redo so that the last realiazed command correspond to + /// the [CommandItem] index passed `index`. + /// + /// ``` + /// use undo_2::{Action,Commands, CommandItem}; + /// use std::time::{Instant, Duration}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// D, + /// E, + /// } + /// use Command::*; + /// use Action::*; + /// + /// let mut commands = Commands::new(); + /// + /// let t0 = Instant::now(); + /// let t1 = t0 + Duration::from_secs(1); + /// let t2 = t0 + Duration::from_secs(2); + /// let t3 = t0 + Duration::from_secs(3); + /// let t4 = t0 + Duration::from_secs(4); + /// commands.push((t0,A)); + /// commands.push((t1,B)); + /// commands.undo(); + /// commands.push((t2,C)); + /// commands.push((t3,D)); + /// commands.undo(); + /// commands.undo(); + /// commands.push((t4,E)); + /// + /// + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&(t4,E),&(t0,A)]); + /// + /// let index = commands.iter().position(|item| match item { + /// CommandItem::Command(item) => item.0 == t2, + /// _ => false + /// }).unwrap(); + /// + /// let actions = commands.undo_or_redo_to_index(index); + /// let a: Vec<_> = actions.collect(); + /// assert_eq!(a, [Undo(&(t4,E)), Do(&(t2,C))]); + /// + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&(t2,C),&(t0,A)]); + /// ``` + pub fn undo_or_redo_to_index(&mut self, i: usize) -> ActionIter<'_, T> { + use CommandItem::*; + let j = match self.last() { + None => return ActionIter::new(), + Some(Command(_)) => self.len(), + Some(Undo(i)) => self.len() - 2 - i, + }; + if i >= j { + self.redo_repeat(i + 1 - j) + } else { + self.undo_repeat(j - i - 1) + } + } + /// Clear all the commands. + /// + /// # Example + /// + /// ``` + /// use undo_2::Commands; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// commands.push(B); + /// + /// commands.clear(); + /// + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v.len(), 0); + /// ``` + pub fn clear(&mut self) { + self.commands.clear(); + } + /// Remove all removable commands that have been added before the + /// first item fulfilling the predicate. + /// + /// A command is removable if it was added before the predicate fulfilling item + /// and is not covered by any undo. + /// + /// Complexity: O(n) + /// + /// ``` + /// use undo_2::Commands; + /// use std::time::{Instant, Duration}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// D, + /// E, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// let t0 = Instant::now(); + /// let t1 = t0 + Duration::from_secs(1); + /// let t2 = t0 + Duration::from_secs(2); + /// let t3 = t0 + Duration::from_secs(3); + /// let t4 = t0 + Duration::from_secs(4); + /// commands.push((t0,A)); + /// commands.push((t1,B)); + /// commands.push((t2,C)); + /// commands.push((t3,D)); + /// commands.push((t4,E)); + /// + /// commands.remove_until(|(t, _)| *t > t1); + /// + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&(t4,E),&(t3,D), &(t2,C)]); + /// + /// + /// let mut commands = Commands::new(); + /// + /// commands.push((t0,A)); + /// commands.push((t1,B)); //B + /// commands.push((t2,C)); + /// commands.undo(); + /// commands.undo();// undo covering B + /// commands.push((t3,D)); + /// commands.push((t4,E)); + /// + /// commands.remove_until(|(t, _)| *t > t1); + /// + /// commands.undo();//remove E + /// commands.undo();//remove D + /// commands.undo();//undo the 2 undos + /// + /// // B not removed because it is covered by an undo + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&(t2,C),&(t1,B)]); + /// + /// ``` + pub fn remove_until(&mut self, mut stop_pred: impl FnMut(&T) -> bool) { + if let Some(i) = self.commands.iter().position(move |c| match c { + CommandItem::Undo(_) => false, + CommandItem::Command(c) => stop_pred(c), + }) { + self.remove_first(i) + } + } + /// Try to keep `count` most recent commands by dropping removable commands. + /// + /// A command is removable if it was added before the 'count' last commands + /// and is not covered by any undo. + /// + /// Complexity: O(n) + /// + /// ``` + /// use undo_2::Commands; + /// use std::time::{Instant, Duration}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// D, + /// E, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// let t0 = Instant::now(); + /// let t1 = t0 + Duration::from_secs(1); + /// let t2 = t0 + Duration::from_secs(2); + /// let t3 = t0 + Duration::from_secs(3); + /// let t4 = t0 + Duration::from_secs(4); + /// commands.push((t0,A)); + /// commands.push((t1,B)); + /// commands.push((t2,C)); + /// commands.push((t3,D)); + /// commands.push((t4,E)); + /// + /// commands.keep_last(2); + /// + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&(t4,E),&(t3,D)]); + /// + /// + /// let mut commands = Commands::new(); + /// + /// commands.push((t0,A)); + /// commands.push((t1,B)); //B + /// commands.push((t2,C)); + /// commands.undo(); + /// commands.undo();// undo covering B + /// commands.push((t3,D)); + /// commands.push((t4,E)); + /// + /// // sequence of undo count for 1 + /// // so try to remove A and B + /// commands.keep_last(4); + /// + /// commands.undo();//remove E + /// commands.undo();//remove D + /// commands.undo();//undo the 2 undos + /// + /// // B not removed because it is covered by an undo + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&(t2,C),&(t1,B)]); + /// ``` + pub fn keep_last(&mut self, count: usize) { + let i = self.len().saturating_sub(count); + self.remove_first(i) + } + /// Remove `count` or less of the oldest command. + /// + /// Less commands may be dropped to ensure that none of the dropped + /// command is covered by an undo within the recent non dropped commands. + /// + /// Complexity: O(n) + /// + /// ``` + /// use undo_2::Commands; + /// use std::time::{Instant, Duration}; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// D, + /// E, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// let t0 = Instant::now(); + /// let t1 = t0 + Duration::from_secs(1); + /// let t2 = t0 + Duration::from_secs(2); + /// let t3 = t0 + Duration::from_secs(3); + /// let t4 = t0 + Duration::from_secs(4); + /// commands.push((t0,A)); + /// commands.push((t1,B)); + /// commands.push((t2,C)); + /// commands.push((t3,D)); + /// commands.push((t4,E)); + /// + /// commands.remove_first(3); + /// + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&(t4,E),&(t3,D)]); + /// + /// + /// let mut commands = Commands::new(); + /// + /// commands.push((t0,A)); + /// commands.push((t1,B)); //B + /// commands.push((t2,C)); + /// commands.undo(); + /// commands.undo();// undo covering B + /// commands.push((t3,D)); + /// commands.push((t4,E)); + /// + /// // sequence of undo count for 1 + /// // so try to remove A and B + /// commands.remove_first(2); + /// + /// commands.undo();//remove E + /// commands.undo();//remove D + /// commands.undo();//undo the 2 undos + /// + /// // B not removed because it is covered by an undo + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&(t2,C),&(t1,B)]); + /// ``` + /// + /// # Panic + /// + /// Panics if `i` is greater than `self.len()` + pub fn remove_first(&mut self, i: usize) { + let j = self.remove_i(i, self.commands.len()); + self.commands.drain(..j); + } + fn remove_i(&self, mut i: usize, end: usize) -> usize { + let i0 = i; + for j in i0..end { + if let CommandItem::Undo(count) = self.commands[j] { + if j - count - 1 < i { + i = self.remove_i(j - count - 1, i) + } + } + } + i + } + /// Iterate the sequence of [*realized commands*](Commands#method.iter_realized) from the + /// newest to the oldest. + /// + /// *Realized commands* are commands that are not undone. For example assuming + /// the folowing sequence of commands: + /// + /// | Command | State | + /// |---------|-------| + /// | Init | | + /// | Do A | A | + /// | Do B | A, B | + /// | Undo | A | + /// | Do C | A, C | + /// + /// The iterator would iterator over the sequence [C, A]. + /// + /// ``` + /// use undo_2::Commands; + /// + /// #[derive(Debug, Eq, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// D, + /// E, + /// } + /// use Command::*; + /// + /// let mut commands = Commands::new(); + /// + /// commands.push(A); + /// commands.push(B); + /// commands.push(C); + /// commands.undo(); + /// commands.undo(); + /// commands.push(D); + /// commands.push(E); + /// + /// let v: Vec<_> = commands.iter_realized().collect(); + /// assert_eq!(v, [&E,&D, &A]); + /// ``` + pub fn iter_realized(&self) -> IterRealized<'_, T> { + IterRealized { + commands: &self.commands, + current: self.commands.len(), + } + } + /// Merge a sequence of [*realized commands*](Commands#method.iter_realized) into a single new + /// command or remove the sequence. + /// + /// The parameter `f` takes as an input a [IterRealized], and returns a + /// [`std::ops::ControlFlow, Option>`](std::ops::ControlFlow). If the + /// returned value contain a `Some(merge)`[Merge], the action specified by `merge` is then + /// inserted in place. + /// + /// The function is excuted recursively while it returns a `ControlFlow::Continue(_)` with a + /// [realized iterator](Commands#method.iter_realized) that is advanced by 1 if no merge + /// command is returned, or set to the element previous to the last merge command. + /// + /// The execution stops when the functions either returned `ControlFlow::Break` or after the + /// last element in the iteration. + /// + /// *Remember*: the element are iterated from the newest to the oldest (in reverse order). + /// + /// # Example + /// + /// ``` + /// use undo_2::{Commands, CommandItem, Merge, IterRealized}; + /// use std::ops::ControlFlow; + /// + /// #[derive(Eq, PartialEq, Debug)] + /// enum Command { + /// A, + /// B, + /// AB, + /// } + /// + /// use Command::*; + /// + /// fn is_ab<'a>(mut it: IterRealized<'a, Command>) -> (bool, IterRealized<'a, Command>) { + /// let cond = it.next() == Some(&B) && it.next() == Some(&A); + /// (cond, it) + /// } + /// + /// + /// let mut commands = Commands::new(); + /// commands.push(A); + /// commands.push(B); + /// + /// commands.merge(|start| { + /// if let (true, end) = is_ab(start.clone()) { + /// ControlFlow::Continue(Some(Merge { + /// start, + /// end, + /// command: Some(AB), + /// })) + /// } else { + /// ControlFlow::Continue(None) + /// } + /// }); + /// + /// assert_eq!(&*commands, &[AB.into()]); + /// ``` + pub fn merge(&mut self, mut f: F) + where + for<'a> F: + FnMut(IterRealized<'a, T>) -> ControlFlow>, Option>>, + { + use ControlFlow::*; + self.splice(|it| match f(it) { + Continue(c) => Continue(c.map(|m| m.into())), + Break(c) => Break(c.map(|m| m.into())), + }) + } + + /// Replace a sequence of command by an other. This is a generalization of + /// [Commands::merge](Commands#method.merge) + /// + /// The parameter `f` takes as an input a [IterRealized], and returns a + /// [`std::ops::ControlFlow, Option>`](std::ops::ControlFlow). If the returned value + /// contain a `Some(splice)`[Splice], the actions specified by `splice` are then inserted in + /// place. + /// + /// The function is excuted recursively while it returns a `ControlFlow::Continue(_)` with a + /// [realized iterator](Commands#method.iter_realized) that is advanced by 1 if no merge + /// command is returned, or set to the element previous to the last merge command. + /// + /// The execution stops when the functions either returned `ControlFlow::Break` or after the + /// last element in the iteration. + /// + /// *Remember*: the element are iterated from the newest to the oldest (in reverse order). + /// + /// # Example + /// + /// ``` + /// use undo_2::{Commands, CommandItem, Splice, IterRealized}; + /// use std::ops::ControlFlow; + /// + /// // we suppose that A, B, C is equivalent to D,E + /// #[derive(Eq, PartialEq, Debug)] + /// enum Command { + /// A, + /// B, + /// C, + /// D, + /// E, + /// } + /// + /// use Command::*; + /// + /// fn is_abc<'a>(mut it: IterRealized<'a, Command>) -> (bool, IterRealized<'a, Command>) { + /// let cond = it.next() == Some(&C) && it.next() == Some(&B) && it.next() == Some(&A); + /// (cond, it) + /// } + /// + /// + /// let mut commands = Commands::new(); + /// commands.push(A); + /// commands.push(B); + /// commands.push(C); + /// + /// commands.splice(|start| { + /// if let (true, end) = is_abc(start.clone()) { + /// ControlFlow::Continue(Some(Splice { + /// start, + /// end, + /// commands: [D,E], + /// })) + /// } else { + /// ControlFlow::Continue(None) + /// } + /// }); + /// + /// assert_eq!(&*commands, &[D.into(), E.into()]); + /// ``` + pub fn splice(&mut self, mut f: F) + where + F: for<'a> FnMut( + IterRealized<'a, T>, + ) + -> ControlFlow>, Option>>, + I: IntoIterator, + { + use ControlFlow::*; + let mut start = self.commands.len(); + while start != 0 { + let it = IterRealized { + commands: &self.commands, + current: start, + }; + match f(it) { + Continue(Some(m)) => { + let rev_start = m.start.current; + let rev_end = m.end.current; + let commands = m.commands; + self.do_splice(rev_start, rev_end, commands); + start = rev_end; + } + Break(Some(m)) => { + let rev_start = m.start.current; + let rev_end = m.end.current; + let commands = m.commands; + self.do_splice(rev_start, rev_end, commands); + break; + } + Break(None) => break, + Continue(None) => start -= 1, + } + } + } + fn do_splice(&mut self, rev_start: usize, rev_end: usize, commands: I) + where + I: IntoIterator, + { + let end_i = rev_start; + let start_i = rev_end; + self.commands + .splice(start_i..end_i, commands.into_iter().map(|c| c.into())); + } + + /// Clean up the history of all the undone commands. + /// + /// After this call the sequence of command will not contain + /// any `CommandItem::Undo` + /// + /// # Example + /// ``` + /// use undo_2::{CommandItem, Commands}; + /// + /// #[derive(Debug, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// } + /// use Command::*; + /// let mut c = Commands::default(); + /// + /// c.push(A); + /// c.push(B); + /// c.undo(); + /// c.push(C); + /// assert_eq!(*c, [A.into(), B.into(), CommandItem::Undo(0), C.into()]); + /// + /// c.remove_all_undone(); + /// assert_eq!(*c, [A.into(), C.into()]); + /// ``` + pub fn remove_all_undone(&mut self) { + self.remove_undone(|i| i) + } + /// Clean up the history of all undone commands before a given + /// [realized iterator](Commands#method.iter_realized). + /// + /// # Example + /// ``` + /// use undo_2::{Commands,CommandItem}; + /// + /// #[derive(Debug, PartialEq)] + /// enum Command { + /// A, + /// B, + /// C, + /// D, + /// } + /// use Command::*; + /// let mut c = Commands::default(); + /// + /// c.push(A); + /// c.push(B); + /// c.undo(); + /// c.push(C); + /// c.push(C); + /// c.undo(); + /// c.push(D); + /// assert_eq!( + /// *c, + /// [ + /// A.into(), + /// B.into(), + /// CommandItem::Undo(0), + /// C.into(), + /// C.into(), + /// CommandItem::Undo(0), + /// D.into() + /// ] + /// ); + /// + /// let v: Vec<_> = c.iter_realized().collect(); + /// assert_eq!(*v, [&D, &C, &A]); + /// + /// c.remove_undone(|mut it| { + /// it.nth(1); + /// it + /// }); + /// assert_eq!( + /// *c, + /// [A.into(), C.into(), C.into(), CommandItem::Undo(0), D.into()] + /// ); + /// + /// // This operation does not change the sequence of realized commands: + /// let v: Vec<_> = c.iter_realized().collect(); + /// assert_eq!(*v, [&D, &C, &A]); + /// ``` + pub fn remove_undone(&mut self, from: F) + where + F: for<'a> FnOnce(IterRealized<'a, T>) -> IterRealized<'a, T>, + { + use CachedAction::*; + let from = from(self.iter_realized()); + + let mut it = IterRealized { + commands: &self.commands, + ..from + }; + + self.undo_cache.clear(); + + let start = it.current; + while let Some(_) = it.next() { + self.undo_cache.push(IndexedAction { + action: Do, + index: it.current, + }); + } + + let mut i = 0; + let mut shift = 0; + for u in self.undo_cache.iter().rev() { + let j = u.index; + self.commands.drain(i - shift..j - shift); + shift += j - i; + i = j + 1; + } + self.commands.drain(i - shift..start - shift); + } +} + +impl Clone for Commands { + fn clone(&self) -> Self { + Self { + commands: self.commands.clone(), + undo_cache: vec![], + } + } + fn clone_from(&mut self, source: &Self) { + self.commands.clone_from(&source.commands) + } +} + +impl Deref for Commands { + type Target = [CommandItem]; + fn deref(&self) -> &Self::Target { + &self.commands + } +} +impl AsRef<[CommandItem]> for Commands { + fn as_ref(&self) -> &[CommandItem] { + self + } +} +impl Borrow<[CommandItem]> for Commands { + fn borrow(&self) -> &[CommandItem] { + self + } +} +impl Extend for Commands { + fn extend>(&mut self, iter: I) { + self.commands.extend(iter.into_iter().map(|c| c.into())) + } +} +impl FromIterator for Commands { + fn from_iter>(iter: I) -> Self { + Self { + commands: iter.into_iter().map(|c| c.into()).collect(), + undo_cache: vec![], + } + } +} +impl<'a, T> IntoIterator for &'a Commands { + type Item = &'a CommandItem; + type IntoIter = slice::Iter<'a, CommandItem>; + fn into_iter(self) -> Self::IntoIter { + self.commands.iter() + } +} +impl IntoIterator for Commands { + type Item = CommandItem; + type IntoIter = std::vec::IntoIter>; + fn into_iter(self) -> Self::IntoIter { + self.commands.into_iter() + } +} +impl Index for Commands +where + I: SliceIndex<[CommandItem]>, +{ + type Output = ]>>::Output; + fn index(&self, index: I) -> &Self::Output { + self.commands.index(index) + } +} + +impl From for CommandItem { + fn from(value: T) -> Self { + CommandItem::Command(value) + } +} + +impl<'a, T> From> for Splice<'a, T, option::IntoIter> { + fn from(m: Merge<'a, T>) -> Self { + Splice { + start: m.start, + end: m.end, + commands: m.command.into_iter(), + } + } +} + +impl IterRealized<'_, T> { + // Returned the index of the command refered by the previous non `None` result of call to + // `next`. + // + // This same command is accessible by indexing [Commands] at this returned index. + // + // This index can be used to set the first realized command with + // [Commands::undo_or_redo_to_index](Commands#method.undo_or_redo_to_index). + pub fn index(&self) -> usize { + self.current + } +} + +impl<'a, T> Iterator for IterRealized<'a, T> { + type Item = &'a T; + fn next(&mut self) -> Option { + use CommandItem::*; + loop { + self.current = self.current.checked_sub(1)?; + match self.commands[self.current] { + Command(ref c) => break Some(c), + Undo(i) => self.current -= i + 1, + } + } + } + fn size_hint(&self) -> (usize, Option) { + (0, Some(self.current)) + } +} +impl FusedIterator for IterRealized<'_, T> {} + +impl<'a, T> Iterator for ActionIter<'a, T> { + type Item = Action<&'a T>; + fn next(&mut self) -> Option { + use CachedAction::*; + loop { + let a = self.to_do.next()?; + match a { + IndexedAction { action: Do, index } => { + break if let CommandItem::Command(v) = &self.commands[*index] { + Some(Action::Do(v)) + } else { + unreachable!() + } + } + IndexedAction { + action: Undo, + index, + } => { + break if let CommandItem::Command(v) = &self.commands[*index] { + Some(Action::Undo(v)) + } else { + unreachable!() + } + } + IndexedAction { action: Skip, .. } => (), + } + } + } + fn size_hint(&self) -> (usize, Option) { + self.to_do.size_hint() + } +} +impl FusedIterator for ActionIter<'_, T> {} +impl ExactSizeIterator for ActionIter<'_, T> {} + +impl IndexedAction { + fn is_reverse_of(&self, other: &Self) -> bool { + use CachedAction::*; + self.index == other.index + && (self.action == Do && other.action == Undo + || self.action == Undo && other.action == Do) + } +} + +impl<'a, T> ActionIter<'a, T> { + fn new() -> Self { + Self { + commands: &[], + to_do: [].iter(), + } + } + fn undo_at_count( + commands: &'a [CommandItem], + cache: &'a mut Vec, + i: usize, + count: usize, + ) -> Self { + cache.clear(); + cache_undo_indexes(commands, i + 1 + count, i, cache); + do_simplify(cache); + Self { + commands, + to_do: cache.iter(), + } + } + fn do_at_count( + commands: &'a [CommandItem], + cache: &'a mut Vec, + i: usize, + count: usize, + ) -> Self { + cache.clear(); + cache_do_indexes(commands, i - count, i + 1, cache); + do_simplify(cache); + Self { + commands, + to_do: cache.iter(), + } + } +} +fn cache_undo_indexes( + commands: &[CommandItem], + undo_from: usize, + undo_to: usize, + to_do: &mut Vec, +) { + use CachedAction::*; + for i in (undo_to..undo_from).rev() { + match commands[i] { + CommandItem::Command(_) => to_do.push(IndexedAction { + action: Undo, + index: i, + }), + CommandItem::Undo(count) => cache_do_indexes(commands, i - (count + 1), i, to_do), + } + } +} +fn cache_do_indexes( + commands: &[CommandItem], + do_from: usize, + do_to: usize, + to_do: &mut Vec, +) { + use CachedAction::*; + for i in do_from..do_to { + match commands[i] { + CommandItem::Command(_) => to_do.push(IndexedAction { + action: Do, + index: i, + }), + CommandItem::Undo(count) => cache_undo_indexes(commands, i, i - (count + 1), to_do), + } + } +} +fn do_simplify(to_do: &mut [IndexedAction]) { + use CachedAction::*; + if to_do.len() < 2 { + return; + } + let mut analyzed = to_do.len() - 1; + let mut cursor = to_do.len() - 1; + while analyzed > 0 { + analyzed -= 1; + let action = &to_do[analyzed]; + let l = to_do.len(); + if cursor < to_do.len() { + if to_do[cursor].is_reverse_of(action) { + cursor += 1; + while cursor < l && to_do[cursor].action == Skip { + cursor += 1 + } + } else { + to_do[analyzed + 1..cursor] + .iter_mut() + .for_each(|a| a.action = Skip); + if cursor == analyzed + 1 { + cursor = analyzed + } else { + cursor = analyzed + 1; + analyzed += 1; + } + } + } else { + to_do[analyzed + 1..] + .iter_mut() + .for_each(|a| a.action = Skip); + cursor = analyzed; + } + } + to_do[..cursor].iter_mut().for_each(|a| a.action = Skip); +} + +#[cfg(test)] +mod test { + use super::CachedAction::*; + use super::IndexedAction; + #[test] + fn simplify() { + use super::do_simplify; + fn simplify(mut to_do: Vec) -> Vec { + do_simplify(&mut to_do); + to_do.iter().filter(|c| c.action != Skip).copied().collect() + } + fn _do(i: usize) -> IndexedAction { + IndexedAction { + action: Do, + index: i, + } + } + fn undo(i: usize) -> IndexedAction { + IndexedAction { + action: Undo, + index: i, + } + } + { + let v = vec![]; + assert_eq!(simplify(v), vec![]); + } + { + let v = vec![_do(1)]; + assert_eq!(simplify(v), vec![_do(1)]); + } + { + let v = vec![undo(1)]; + assert_eq!(simplify(v), vec![undo(1)]); + } + { + let v = vec![_do(1), undo(1)]; + assert_eq!(simplify(v), vec![]); + } + { + let v = vec![_do(0), _do(1), undo(1)]; + assert_eq!(simplify(v), vec![_do(0)]); + } + { + let v = vec![_do(1), undo(1), _do(2)]; + assert_eq!(simplify(v), vec![_do(2)]); + } + { + let v = vec![_do(0), _do(1), undo(1), _do(2)]; + assert_eq!(simplify(v), vec![_do(0), _do(2)]); + } + { + let v = vec![_do(1), _do(2), undo(2), undo(1)]; + assert_eq!(simplify(v), vec![]); + } + { + let v = vec![_do(0), _do(1), _do(2), undo(2), undo(1)]; + assert_eq!(simplify(v), vec![_do(0)]); + } + { + let v = vec![_do(1), _do(2), undo(2), undo(1), _do(3)]; + assert_eq!(simplify(v), vec![_do(3)]); + } + { + let v = vec![_do(0), _do(1), _do(2), undo(2), undo(1), _do(3)]; + assert_eq!(simplify(v), vec![_do(0), _do(3)]); + } + { + let v = vec![_do(0), _do(1), _do(2), undo(2), undo(1), undo(0)]; + assert_eq!(simplify(v), vec![]); + } + { + let v = vec![ + _do(0), + _do(1), + _do(2), + undo(2), + undo(1), + _do(10), + undo(10), + undo(0), + ]; + assert_eq!(simplify(v), vec![]); + } + } +} diff --git a/crates/bevy_text/src/undo_2/tests/iter_realized.rs b/crates/bevy_text/src/undo_2/tests/iter_realized.rs new file mode 100644 index 0000000000000..3afdd2179e1b6 --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/iter_realized.rs @@ -0,0 +1,72 @@ +#![allow(unused)] + +use undo_2::*; + +#[derive(PartialEq, Debug)] +enum Command { + A, + B, + C, + D, + E, +} +use Command::*; + +#[test] +fn iter_realized() { + let mut c = Commands::default(); + c.push(A); + c.push(B); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&A]); + + c.push(C); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&C, &A]); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&A]); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&B, &A]); + + c.push(D); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&D, &B, &A]); + + c.push(E); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&E, &D, &B, &A]); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&D, &B, &A]); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&B, &A]); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&C, &A]); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&A]); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(v, [&B, &A]); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(v, [&A]); + + c.undo(); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(v, Vec::<&Command>::new()); +} diff --git a/crates/bevy_text/src/undo_2/tests/merge.rs b/crates/bevy_text/src/undo_2/tests/merge.rs new file mode 100644 index 0000000000000..79589fcb7bb89 --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/merge.rs @@ -0,0 +1,96 @@ +use undo_2::*; + +#[test] +fn merge() { + use std::ops::ControlFlow; + use undo_2::CommandItem; + use undo_2::IterRealized; + use undo_2::Merge; + #[derive(Eq, PartialEq, Debug)] + enum Command { + A, + B, + C, + AB, + } + use Command::*; + fn is_ab(mut it: IterRealized<'_, Command>) -> (bool, IterRealized<'_, Command>) { + let cond = it.next() == Some(&Command::B) && it.next() == Some(&Command::A); + (cond, it) + } + fn parse( + start: IterRealized<'_, Command>, + ) -> ControlFlow>, Option>> { + if let (true, end) = is_ab(start.clone()) { + ControlFlow::Continue(Some(Merge { + start, + end, + command: Some(Command::AB), + })) + } else { + ControlFlow::Continue(None) + } + } + { + let mut commands = Commands::new(); + commands.push(Command::A); + commands.push(Command::B); + + commands.merge(parse); + assert_eq!(*commands, [CommandItem::Command(Command::AB)]); + } + { + let mut commands = Commands::new(); + commands.push(Command::A); + commands.push(Command::C); + commands.push(Command::B); + commands.push(Command::A); + commands.push(Command::B); + + commands.merge(parse); + assert_eq!( + *commands, + [ + CommandItem::Command(A), + CommandItem::Command(C), + CommandItem::Command(B), + CommandItem::Command(AB) + ] + ); + } + { + let mut commands = Commands::new(); + commands.push(Command::A); + commands.push(Command::C); + commands.push(Command::A); + commands.push(Command::B); + commands.push(Command::B); + commands.push(Command::A); + commands.push(Command::B); + + commands.merge(parse); + assert_eq!( + *commands, + [ + CommandItem::Command(A), + CommandItem::Command(C), + CommandItem::Command(AB), + CommandItem::Command(B), + CommandItem::Command(AB) + ] + ); + } + { + let mut commands = Commands::new(); + commands.push(Command::A); + commands.push(Command::B); + commands.push(Command::A); + commands.push(Command::B); + + commands.merge(parse); + assert_eq!( + &*commands, + &[CommandItem::Command(AB), CommandItem::Command(AB)] + ); + } +} diff --git a/crates/bevy_text/src/undo_2/tests/redo.rs b/crates/bevy_text/src/undo_2/tests/redo.rs new file mode 100644 index 0000000000000..bfbb7eb163ae5 --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/redo.rs @@ -0,0 +1,83 @@ +#![allow(unused)] +use undo_2::{Action, Commands}; + +#[derive(Debug, Eq, PartialEq)] +enum Command { + A, + B, + C, + D, +} +use Action::*; +use Command::*; + +#[test] +fn redo() { + { + let mut commands = Commands::new(); + + commands.push(A); // A + commands.push(B); // A B + commands.undo(); // A + commands.undo(); // + commands.push(C); // C + commands.undo(); // + commands.undo(); // A B + + let v: Vec<_> = commands.redo().collect(); + assert_eq!(v, [Undo(&B), Undo(&A)]); + + let v: Vec<_> = commands.redo().collect(); + assert_eq!(v, [Do(&C)]); + } + { + let mut commands = Commands::new(); + + commands.push(A); // A + commands.push(B); // A B + commands.undo(); // A + commands.undo(); // + // + commands.push(C); // C + // + commands.undo(); // + commands.undo(); // A B + commands.undo(); // A + commands.undo(); // + + commands.push(D); // D + + commands.undo(); // + commands.undo(); // C + commands.undo(); // + commands.undo(); // A B + + let v: Vec<_> = commands.redo().collect(); + assert_eq!(v, [Undo(&B), Undo(&A)]); + + let v: Vec<_> = commands.redo().collect(); + assert_eq!(v, [Do(&C)]); + + let v: Vec<_> = commands.redo().collect(); + assert_eq!(v, [Undo(&C)]); + + let v: Vec<_> = commands.redo().collect(); + assert_eq!(v, [Do(&D)]); + + let v: Vec<_> = commands.redo().collect(); + assert_eq!(v, []); + } +} + +#[test] +fn redo_all() { + let mut commands = Commands::new(); + + commands.push(A); + commands.push(B); + commands.undo(); + commands.undo(); + + let v: Vec<_> = commands.redo_all().collect(); + assert_eq!(v, [Do(&A), Do(&Command::B)]); +} diff --git a/crates/bevy_text/src/undo_2/tests/remove_undone.rs b/crates/bevy_text/src/undo_2/tests/remove_undone.rs new file mode 100644 index 0000000000000..7ebcc33128d5b --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/remove_undone.rs @@ -0,0 +1,188 @@ +#![allow(unused)] +use undo_2::*; + +#[test] +fn remove_undone() { + #[derive(Debug, PartialEq)] + enum Command { + A, + B, + C, + } + use Command::*; + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.undo(); + c.push(C); + assert_eq!(*c, [A.into(), B.into(), CommandItem::Undo(0), C.into()]); + + c.remove_all_undone(); + assert_eq!(*c, [A.into(), C.into()]); + } + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.push(C); + c.undo(); + + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&B, &A]); + + c.remove_all_undone(); + assert_eq!(*c, [A.into(), B.into()]); + } + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.undo(); + c.push(C); + c.push(C); + c.undo(); + + c.remove_all_undone(); + assert_eq!(*c, [A.into(), C.into()]); + } + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.undo(); + c.push(C); + c.push(C); + c.undo(); + c.undo(); + + c.remove_all_undone(); + assert_eq!(*c, [A.into()]); + } + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.undo(); + c.push(C); + c.push(C); + c.undo(); + c.undo(); + c.undo(); + + c.remove_all_undone(); + assert_eq!(*c, [A.into(), B.into()]); + } +} +fn remove_undo_from() { + #[derive(Debug, PartialEq)] + enum Command { + A, + B, + C, + D, + } + use Command::*; + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.undo(); + c.push(C); + c.push(C); + c.undo(); + c.push(D); + assert_eq!( + *c, + [ + A.into(), + B.into(), + CommandItem::Undo(0), + C.into(), + C.into(), + CommandItem::Undo(0), + D.into() + ] + ); + + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&D, &C, &A]); + + c.remove_undone(|mut it| { + it.nth(1); + it + }); + assert_eq!( + *c, + [A.into(), C.into(), C.into(), CommandItem::Undo(0), D.into()] + ); + + // This operation does not change the sequence of realized commands: + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&D, &C, &A]); + } + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.undo(); + c.push(C); + c.push(C); + c.undo(); + c.push(D); + assert_eq!( + *c, + [ + A.into(), + B.into(), + CommandItem::Undo(0), + C.into(), + C.into(), + CommandItem::Undo(0), + D.into() + ] + ); + + c.remove_undone(|mut it| { + it.next(); + it + }); + assert_eq!(*c, [A.into(), C.into(), D.into()]); + } + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.undo(); + c.push(C); + c.push(C); + c.undo(); + c.push(D); + assert_eq!( + *c, + [ + A.into(), + B.into(), + CommandItem::Undo(0), + C.into(), + C.into(), + CommandItem::Undo(0), + D.into() + ] + ); + + c.remove_undone(|mut it| { + it.nth(2); + it + }); + assert_eq!(*c, []); + } +} diff --git a/crates/bevy_text/src/undo_2/tests/set_and_transition.rs b/crates/bevy_text/src/undo_2/tests/set_and_transition.rs new file mode 100644 index 0000000000000..b1909ca1893c3 --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/set_and_transition.rs @@ -0,0 +1,101 @@ +use std::mem::{discriminant, Discriminant}; + +use undo_2::{Commands, IndepStateKey, SetOrTransition, SetTransAction}; + +#[derive(Copy, Clone, Debug)] +struct State { + color: f64, + length: f64, +} +impl State { + fn new() -> Self { + INIT_STATE + } + fn apply_set(&mut self, c: &SetCommands) { + match c { + SetCommands::Color(v) => self.color = *v, + SetCommands::Length(v) => self.length = *v, + }; + } + fn execute_action(&mut self, c: SetTransAction) { + match c { + SetTransAction::Do(_) => {} + SetTransAction::Undo(_) => {} + SetTransAction::Set(c) => self.apply_set(c), + SetTransAction::SetToInitial(d) => self.apply_set(SetCommands::new_initial(d)), + } + } +} +static INIT_STATE: State = State { + color: 0., + length: 0., +}; + +#[derive(Debug, Copy, Clone, PartialEq)] +enum SetCommands { + Color(f64), + Length(f64), +} +static INIT_COLOR: SetCommands = SetCommands::Color(INIT_STATE.color); +static INIT_LENGTH: SetCommands = SetCommands::Length(INIT_STATE.length); + +impl SetCommands { + fn new_initial(d: Discriminant) -> &'static Self { + if d == discriminant(&INIT_COLOR) { + &INIT_COLOR + } else if d == discriminant(&INIT_LENGTH) { + &INIT_LENGTH + } else { + unreachable!("SetCommands::new_initial is not exhaustive: please, adds lacking initial value to this method") + } + } +} + +impl IndepStateKey for SetCommands { + type KeyType = Discriminant; + + fn key(&self) -> Self::KeyType { + discriminant(self) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum TransitionCommand { + A, + B, +} + +#[test] +fn set_and_transition() { + use SetOrTransition::*; + let mut commands = Commands::new(); + let mut state = State::new(); + + let c = SetCommands::Color(1.); + state.apply_set(&c); + commands.push(Set(c)); + + commands.push(Transition(TransitionCommand::A)); + commands.push(Transition(TransitionCommand::B)); + + let c = SetCommands::Length(10.); + state.apply_set(&c); + commands.push(Set(c)); + + let c = SetCommands::Color(2.); + state.apply_set(&c); + commands.push(Set(c)); + + commands.apply_undo(|c| { + assert_eq!(c, SetTransAction::Set(&SetCommands::Color(1.))); + state.execute_action(c); + }); + assert_eq!(state.color, 1.); + assert_eq!(state.length, 10.); + commands.apply_redo(|c| { + assert_eq!(c, SetTransAction::Set(&SetCommands::Color(2.))); + state.execute_action(c); + }); + assert_eq!(state.color, 2.); + assert_eq!(state.length, 10.); +} diff --git a/crates/bevy_text/src/undo_2/tests/splice.rs b/crates/bevy_text/src/undo_2/tests/splice.rs new file mode 100644 index 0000000000000..9a6bb6f2049e3 --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/splice.rs @@ -0,0 +1,198 @@ +#![allow(unused)] + +use std::ops::ControlFlow; +use undo_2::*; + +#[derive(PartialEq, Debug)] +enum Command { + A, + B, + C, + D, + E, +} +use Command::*; + +fn is_abc(mut it: IterRealized<'_, Command>) -> (bool, IterRealized<'_, Command>) { + let cond = it.next() == Some(&C) && it.next() == Some(&B) && it.next() == Some(&A); + (cond, it) +} + +fn do_splice(c: &mut Commands) { + c.splice(|start| { + if let (true, end) = is_abc(start.clone()) { + ControlFlow::Continue(Some(Splice { + start, + end, + commands: [D, E], + })) + } else { + ControlFlow::Continue(None) + } + }) +} + +#[test] +fn splice_without_undos() { + { + let mut c = Commands::default(); + + do_splice(&mut c); + assert_eq!(*c, []); + + c.push(A); + + do_splice(&mut c); + assert_eq!(*c, [A.into()]); + + c.push(B); + + do_splice(&mut c); + assert_eq!(*c, [A.into(), B.into()]); + + c.push(C); + + do_splice(&mut c); + assert_eq!(*c, [D.into(), E.into()]); + + c.push(A); + + do_splice(&mut c); + assert_eq!(*c, [D.into(), E.into(), A.into()]); + + c.push(B); + + do_splice(&mut c); + assert_eq!(*c, [D.into(), E.into(), A.into(), B.into()]); + + c.push(C); + } + { + let mut c = Commands::default(); + + do_splice(&mut c); + assert_eq!(*c, []); + + c.push(A); + + do_splice(&mut c); + assert_eq!(*c, [A.into()]); + + c.push(B); + + do_splice(&mut c); + assert_eq!(*c, [A.into(), B.into()]); + + c.push(C); + + do_splice(&mut c); + assert_eq!(*c, [D.into(), E.into()]); + + c.push(D); + + do_splice(&mut c); + assert_eq!(*c, [D.into(), E.into(), D.into()]); + + c.push(A); + + do_splice(&mut c); + assert_eq!(*c, [D.into(), E.into(), D.into(), A.into()]); + + c.push(B); + + do_splice(&mut c); + assert_eq!(*c, [D.into(), E.into(), D.into(), A.into(), B.into()]); + + c.push(C); + do_splice(&mut c); + assert_eq!(*c, [D.into(), E.into(), D.into(), D.into(), E.into()]); + } + { + let mut c = Commands::default(); + + do_splice(&mut c); + assert_eq!(*c, []); + + c.push(A); + c.push(B); + c.push(C); + c.push(D); + c.push(A); + c.push(B); + c.push(C); + + do_splice(&mut c); + assert_eq!(*c, [D.into(), E.into(), D.into(), D.into(), E.into()]); + } +} + +#[test] +fn splice_with_undos() { + { + let mut c = Commands::default(); + + do_splice(&mut c); + let v: Vec<_> = c.iter_realized().collect(); + assert!(v.is_empty()); + + c.push(A); + do_splice(&mut c); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&A]); + + c.push(D); + do_splice(&mut c); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&D, &A]); + + c.undo(); + do_splice(&mut c); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&A]); + + c.push(B); + do_splice(&mut c); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&B, &A]); + + c.push(C); + do_splice(&mut c); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&E, &D]); + assert_eq!(*c, [D.into(), E.into()]) + } + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.push(C); + c.undo(); + c.undo(); + c.push(D); + c.push(E); + c.undo(); + c.undo(); + c.undo(); + c.push(E); + + do_splice(&mut c); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&E, &E, &D]); + assert_eq!(*c, [D.into(), E.into(), E.into()]) + } + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.push(C); + c.push(C); + c.undo(); + + do_splice(&mut c); + let v: Vec<_> = c.iter_realized().collect(); + assert_eq!(*v, [&E, &D]); + assert_eq!(*c, [D.into(), E.into()]) + } +} diff --git a/crates/bevy_text/src/undo_2/tests/unbuild.rs b/crates/bevy_text/src/undo_2/tests/unbuild.rs new file mode 100644 index 0000000000000..af443f66b8415 --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/unbuild.rs @@ -0,0 +1,37 @@ +#![allow(unused)] +use undo_2::{Action, Commands}; + +#[derive(Debug, Eq, PartialEq)] +enum Command { + A, + B, + C, +} +use Command::*; + +#[test] +fn unbuild() { + use Action::*; + let mut commands = Commands::new(); + + commands.push(A); + commands.push(B); + commands.undo(); + commands.push(C); + + let c: Vec<_> = commands.unbuild().collect(); + assert_eq!(c, [Undo(&C)]); + + let c: Vec<_> = commands.unbuild().collect(); + assert_eq!(c, &[Undo(&A)]); + + let c: Vec<_> = commands.unbuild().collect(); + assert!(c.is_empty()); + + dbg!(&commands); + let v: Vec<_> = commands.rebuild().collect(); + assert_eq!(v, &[Do(&A)]); + + let v: Vec<_> = commands.rebuild().collect(); + assert_eq!(v, &[Do(&C)]); +} diff --git a/crates/bevy_text/src/undo_2/tests/undo.rs b/crates/bevy_text/src/undo_2/tests/undo.rs new file mode 100644 index 0000000000000..2caa42b27a21d --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/undo.rs @@ -0,0 +1,61 @@ +#![allow(unused)] +use undo_2::{Action, Commands}; + +#[derive(Debug, Eq, PartialEq)] +enum Command { + A, + B, + C, + D, +} +use Command::*; + +#[test] +fn undo() { + use Action::*; + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + + let v: Vec<_> = c.undo().collect(); + assert_eq!(v, [Undo(&B)]); + + let v: Vec<_> = c.undo().collect(); + assert_eq!(v, [Undo(&A)]); + } + { + let mut c = Commands::default(); + + c.push(A); + c.push(B); + c.undo(); + c.undo(); + c.push(C); + + let v: Vec<_> = c.undo().collect(); + assert_eq!(v, [Undo(&C)]); + + let v: Vec<_> = c.undo().collect(); + assert_eq!(v, [Do(&A), Do(&B)]); + } + { + let mut c = Commands::default(); + + c.push(A); // A + c.push(B); // A B + c.undo(); // A + c.undo(); // + c.push(C); // C + c.undo(); // + c.undo(); // A B + c.push(D); // A B D + + let v: Vec<_> = c.undo().collect(); + assert_eq!(v, [Undo(&D)]); + + let v: Vec<_> = c.undo().collect(); + assert_eq!(v, [Undo(&B), Undo(&A), Do(&C)]); + } +} diff --git a/crates/bevy_text/src/undo_2/tests/undo_tests.rs b/crates/bevy_text/src/undo_2/tests/undo_tests.rs new file mode 100644 index 0000000000000..9711dc601795e --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/undo_tests.rs @@ -0,0 +1,205 @@ +use undo_2::Action; +use undo_2::Action::*; +use undo_2::Commands; +#[test] +#[allow(unused)] +fn application() { + enum Command { + Add(char), + Delete(char), + } + struct TextEditor { + text: String, + command: Commands, + } + impl TextEditor { + pub fn new() -> Self { + Self { + text: String::new(), + command: Commands::new(), + } + } + pub fn add_char(&mut self, c: char) { + self.text.push(c); + self.command.push(Command::Add(c)); + } + pub fn delete_char(&mut self) { + if let Some(c) = self.text.pop() { + self.command.push(Command::Delete(c)); + } + } + pub fn undo(&mut self) { + for action in self.command.undo() { + interpret_action(&mut self.text, action) + } + } + pub fn redo(&mut self) { + for action in self.command.redo() { + interpret_action(&mut self.text, action) + } + } + } + fn interpret_action(data: &mut String, action: Action<&Command>) { + use Action::*; + use Command::*; + match action { + Do(Add(c)) | Undo(Delete(c)) => { + data.push(*c); + } + Undo(Add(_)) | Do(Delete(_)) => { + data.pop(); + } + } + } + let mut editor = TextEditor::new(); + editor.add_char('a'); // :[1] + editor.add_char('b'); // :[2] + editor.add_char('d'); // :[3] + assert_eq!(editor.text, "abd"); + + editor.undo(); // first undo :[4] + assert_eq!(editor.text, "ab"); + + editor.add_char('c'); // :[5] + assert_eq!(editor.text, "abc"); + + editor.undo(); // Undo [5] :[6] + assert_eq!(editor.text, "ab"); + editor.undo(); // Undo the undo [4] :[7] + assert_eq!(editor.text, "abd"); + editor.undo(); // Undo [3] :[8] + assert_eq!(editor.text, "ab"); + editor.undo(); // Undo [2] :[9] + assert_eq!(editor.text, "a"); + + editor.add_char('z'); // :[10] + assert_eq!(editor.text, "az"); + // when an action is performed after a sequence + // of undo, the undos are merged: undos [6] to [9] are merged now + + editor.undo(); // back to [10] + assert_eq!(editor.text, "a"); + editor.undo(); // back to [5]: reverses the consecutive sequence of undos in batch + assert_eq!(editor.text, "abc"); + editor.undo(); // back to [4] + assert_eq!(editor.text, "ab"); + editor.undo(); // back to [3] + assert_eq!(editor.text, "abd"); + editor.undo(); // back to [2] + assert_eq!(editor.text, "ab"); + editor.undo(); // back to [1] + assert_eq!(editor.text, "a"); + editor.undo(); // back to [0] + assert_eq!(editor.text, ""); + + editor.redo(); // back to [1] + assert_eq!(editor.text, "a"); + editor.redo(); // back to [2] + assert_eq!(editor.text, "ab"); + editor.redo(); // back to [3] + assert_eq!(editor.text, "abd"); + editor.redo(); // back to [4] + assert_eq!(editor.text, "ab"); + editor.redo(); // back to [5] + assert_eq!(editor.text, "abc"); + editor.redo(); // back to [9]: redo inner consecutive sequence of undos in batch + // (undo are merged only when they are not the last action) + assert_eq!(editor.text, "a"); + editor.redo(); // back to [10] + assert_eq!(editor.text, "az"); + + editor.add_char('1'); + editor.add_char('2'); + assert_eq!(editor.text, "az12"); + editor.undo(); + editor.undo(); + assert_eq!(editor.text, "az"); + editor.redo(); // undo is the last action, undo the undo only once + assert_eq!(editor.text, "az1"); + editor.redo(); + assert_eq!(editor.text, "az12"); +} +struct CommandString(Vec<(usize, char)>, Commands<(usize, char)>); +impl CommandString { + fn new() -> Self { + CommandString(vec![], Commands::new()) + } + fn push(&mut self, c: char) { + let l = self.0.len(); + self.0.push((l, c)); + self.1.push((l, c)); + } + #[track_caller] + fn undo(&mut self) { + Self::apply(&mut self.0, self.1.undo()); + } + #[track_caller] + fn redo(&mut self) { + Self::apply(&mut self.0, self.1.redo()); + } + #[track_caller] + fn apply<'a>(s: &mut Vec<(usize, char)>, it: impl Iterator>) { + for c in it { + match c { + Do(i) => { + assert_eq!(s.len(), i.0, "inconsitent push"); + s.push(*i); + } + Undo(i) => { + assert_eq!(s.pop(), Some(*i), "inconsistent pop"); + } + } + } + } +} +#[test] +fn command_sequence() { + let mut c = CommandString::new(); + c.push('a'); + assert!(c.0 == [(0, 'a')]); + assert!(c.1.len() == 1); + c.undo(); + assert!(c.0.is_empty()); + c.redo(); + assert!(c.0 == [(0, 'a')]); + c.push('b'); + c.push('c'); + assert!(c.0 == [(0, 'a'), (1, 'b'), (2, 'c')]); + c.redo(); + assert!(c.0 == [(0, 'a'), (1, 'b'), (2, 'c')]); + c.undo(); + assert!(c.0 == [(0, 'a'), (1, 'b')]); + c.push('d'); + assert!(c.0 == [(0, 'a'), (1, 'b'), (2, 'd')]); + c.push('e'); + assert!(c.0 == [(0, 'a'), (1, 'b'), (2, 'd'), (3, 'e')]); + c.undo(); + assert!(c.0 == [(0, 'a'), (1, 'b'), (2, 'd')]); + c.undo(); + assert!(c.0 == [(0, 'a'), (1, 'b')]); + c.undo(); + assert!(c.0 == [(0, 'a'), (1, 'b'), (2, 'c')]); + c.undo(); + assert!(c.0 == [(0, 'a'), (1, 'b')]); + c.undo(); + assert!(c.0 == [(0, 'a')]); + c.push('f'); + assert!(c.0 == [(0, 'a'), (1, 'f')]); + c.undo(); + assert!(c.0 == [(0, 'a')]); + c.undo(); + assert!(c.0 == [(0, 'a'), (1, 'b'), (2, 'd'), (3, 'e')]); + c.redo(); + assert!(c.0 == [(0, 'a')]); + c.undo(); + assert!(c.0 == [(0, 'a'), (1, 'b'), (2, 'd'), (3, 'e')]); +} +#[cfg(feature = "serde")] +#[test] +fn serde() { + let mut commands = Commands::new(); + commands.push("a"); + let str = serde_json::to_string(&commands).unwrap(); + let commands: Commands = serde_json::from_str(&str).unwrap(); + assert_eq!(*commands, ["a".to_owned().into()]); +} From 78ee54697454532d05bf3887728ed1f06bf50c43 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sun, 10 Aug 2025 17:02:09 +0800 Subject: [PATCH 2/3] Re-link Lints More lints Clippy More More clippy Fmt More doc work More clippy More --- crates/bevy_ecs/src/system/builder.rs | 2 +- crates/bevy_text/Cargo.toml | 1 + crates/bevy_text/src/lib.rs | 1 + crates/bevy_text/src/undo_2/README.md | 66 ++--- crates/bevy_text/src/undo_2/lib.rs | 229 +++++++++--------- crates/bevy_text/src/undo_2/mod.rs | 6 + .../src/undo_2/tests/iter_realized.rs | 4 +- crates/bevy_text/src/undo_2/tests/merge.rs | 53 ++-- crates/bevy_text/src/undo_2/tests/mod.rs | 9 + crates/bevy_text/src/undo_2/tests/redo.rs | 6 +- .../src/undo_2/tests/remove_undone.rs | 4 +- .../src/undo_2/tests/set_and_transition.rs | 9 +- crates/bevy_text/src/undo_2/tests/splice.rs | 14 +- crates/bevy_text/src/undo_2/tests/unbuild.rs | 4 +- crates/bevy_text/src/undo_2/tests/undo.rs | 4 +- .../bevy_text/src/undo_2/tests/undo_tests.rs | 12 +- typos.toml | 9 +- 17 files changed, 228 insertions(+), 205 deletions(-) create mode 100644 crates/bevy_text/src/undo_2/mod.rs create mode 100644 crates/bevy_text/src/undo_2/tests/mod.rs diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 523c904c1dc7b..937911ca834c1 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -106,7 +106,7 @@ use super::{Res, ResMut, SystemState}; /// /// The implementor must ensure that the state returned /// from [`SystemParamBuilder::build`] is valid for `P`. -/// Note that the exact safety requiremensts depend on the implementation of [`SystemParam`], +/// Note that the exact safety requirements depend on the implementation of [`SystemParam`], /// so if `Self` is not a local type then you must call [`SystemParam::init_state`] /// or another [`SystemParamBuilder::build`]. pub unsafe trait SystemParamBuilder: Sized { diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index 0c41accecf4e3..97bb05b00545e 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["bevy"] [features] default_font = [] +serde = [] [dependencies] # bevy diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 56ff703ccaceb..2cb2e4c26cd44 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -42,6 +42,7 @@ mod pipeline; mod text; mod text2d; mod text_access; +pub mod undo_2; use bevy_camera::{visibility::VisibilitySystems, CameraUpdateSystems}; pub use bounds::*; diff --git a/crates/bevy_text/src/undo_2/README.md b/crates/bevy_text/src/undo_2/README.md index 4c68438b8f10d..f30f8d4db7089 100644 --- a/crates/bevy_text/src/undo_2/README.md +++ b/crates/bevy_text/src/undo_2/README.md @@ -1,6 +1,6 @@ -# Undo done the right way! +# Undo done the right way -Via https://gitlab.com/okannen/undo_2/-/tree/b32c34edb2c15c266b946f0d82188624f3aa3fdc +Via [original](https://gitlab.com/okannen/undo_2/-/tree/b32c34edb2c15c266b946f0d82188624f3aa3fdc) ## Introduction @@ -10,34 +10,33 @@ end of the Undo history as a precursor to your new change. I found the idea in [zaboople/klong][zaboople]. This crate is an implementation of this idea with a minor variation explained below. -As an example consider the following sequence of commands: +As an example consider the following sequence of commands: | Command | State | | ------- | ----- | -| Init | | +| Init | | | Do A | A | | Do B | A, B | -| Undo | A | -| Do C | A, C | +| Undo | A | +| Do C | A, C | -With the **classical undo**, repeated undo would lead to the sequence: +With the **classical undo**, repeated undo would lead to the sequence: | Command | State | |---------|-------| | | A, C | | Undo | A | -| Undo | | - +| Undo | | -Starting from 5, with **undo_2**, repeating undo would lead to the sequence: +Starting from 5, with **undo_2**, repeating undo would lead to the sequence: | Command | State | |---------|-------| | | A, C | | Undo | A | -| Undo | A,B | -| Undo | A | -| Undo | | +| Undo | A,B | +| Undo | A | +| Undo | | **undo_2**'s undo navigates back in history, while classical undo navigates back through the sequence of command that builds the state. @@ -53,11 +52,11 @@ and it is similar to vim :earlier. 4. very lightweight, dumb and simple. 5. possibility to merge and splice commands. -## How to use it +## How to use it Add the dependency to the cargo file: -```[toml] +```toml [dependencies] undo_2 = "0.1" ``` @@ -74,7 +73,7 @@ be interpreted by the application. This design pattern makes implementation easier because it is not necessary to borrow data within the stored list of commands. -``` +```rs use undo_2::{Commands,Action}; use Action::{Do,Undo}; @@ -156,22 +155,22 @@ assert_eq!(editor.text, "a"); forming the sequence are merged. This makes the traversal of the undo sequence more concise by avoiding state duplication. -| Command | State | Comment | +| Command | State | Comment | |---------|------- |----------------------| -| Init | | | +| Init | | | | Do A | A | | | Do B | A,B | | | Do C | A, B, C | | -| Undo | A, B |Merged | -| Undo | A |Merged | -| Do D | A, D | | +| Undo | A, B |Merged | +| Undo | A |Merged | +| Do D | A, D | | | Undo | A |Redo the 2 Merged Undo| -| Undo | A, B, C | | -| Undo | A, B | | -| Undo | A | | -| Undo | | | +| Undo | A, B, C | | +| Undo | A, B | | +| Undo | A | | +| Undo | | | -2. Each execution of an undos or redo may lead to the execution of a sequence of +1. Each execution of an undos or redo may lead to the execution of a sequence of actions in the form `Undo(a)+Do(b)+Do(c)`. Basic arithmetic is implemented assuming that `Do(a)+Undo(a)` is equivalent to not doing anything (here the 2 `a`'s designate the same entity, not to equal objects). @@ -249,13 +248,14 @@ assert_eq!(editor.text, "az12"); ``` ## Release note + ### Version 0.3 - - [Action] is now an enum taking commands, the list of command to be - executed is of the form [Action]; - - added [Commands::can_undo] and [Commands::can_redo]; - - added [Commands::rebuild], which correspond to the classical redo; - - fixed a bug in [Commands::undo_or_redo_to_index] - - Added support for special commands that represent a state setting. See [SetOrTransition]. -[zaboople]: https://github.com/zaboople/klonk/blob/master/TheGURQ.md +- [`Action`] is now an enum taking commands, the list of command to be +executed is of the form [`Action`]; +- added [`Commands::can_undo`] and [`Commands::can_redo`]; +- added [`Commands::rebuild`], which correspond to the classical redo; +- fixed a bug in [`Commands::undo_or_redo_to_index`] +- Added support for special commands that represent a state setting. See [`SetOrTransition`]. +[zaboople]: https://github.com/zaboople/klonk/blob/master/TheGURQ.md diff --git a/crates/bevy_text/src/undo_2/lib.rs b/crates/bevy_text/src/undo_2/lib.rs index 384e27c1ee923..ee712bdeca2cb 100644 --- a/crates/bevy_text/src/undo_2/lib.rs +++ b/crates/bevy_text/src/undo_2/lib.rs @@ -1,22 +1,25 @@ -#![doc = include_str!("../../README-undo.md")] -#![doc = document_features::document_features!()] +//! AA +#![doc = include_str!("./README.md")] +//! BB +// #![doc = document_features::document_features!()] +//! CC +use alloc::{slice, vec}; use core::borrow::Borrow; use core::cmp::min; +use core::fmt::Debug; use core::iter::FusedIterator; use core::ops::ControlFlow; use core::ops::{Deref, Index}; use core::option; -use std::fmt::Debug; +use core::slice::SliceIndex; use std::hash::{DefaultHasher, Hash, Hasher as _}; -use std::slice::SliceIndex; -use std::{slice, vec}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Action that should be taken on the paired command /// -/// [ActionIter] item type is a pair `(Action, C)` where +/// [`ActionIter`] item type is a pair `(Action, C)` where /// C is the client defined command type. `(Action::Do, t)` /// signifies that the command `t` shall be executed. `(Action::Undo,t)` /// means it shall be undone. @@ -50,16 +53,16 @@ pub enum Action { /// transition are interlieved with other transitions. /// /// So the set of all commands can be partitionned in two: the set of transitions affecting -/// indepent state, and the other transitions. [SetOrTransition] provides such a partition. +/// indepent state, and the other transitions. [`SetOrTransition`] provides such a partition. /// It is supposed to be used as the element of the [Commands]: /// `Commands`. /// -/// [Commands] provides dedicateds methods (see [apply_actions][Commands::apply_actions]) to simplify the use of +/// [Commands] provides dedicateds methods (see [`apply_actions`][Commands::apply_actions]) to simplify the use of /// commands partitionned in independent state and transitions. /// /// State commands are supposed to represent the setting of an indepent state such as "set the view /// port to `x`, `y`, `lx`, `ly`". When a independent state command is undone within the `apply_actions`, the -/// algorithm will look for the previous application of a command the same key [IndepStateKey::key] and will +/// algorithm will look for the previous application of a command the same key [`IndepStateKey::key`] and will /// command the application of it. /// /// # Example @@ -67,11 +70,11 @@ pub enum Action { /// In this example we use commands that set the state `color` and `length`. /// /// In the commands registered the previous states of `color` and `length` are not stored -/// but [Commands::apply_actions] will retrieve automatically the value to set for this states. +/// but [`Commands::apply_actions`] will retrieve automatically the value to set for this states. /// ``` /// use std::mem::{discriminant, Discriminant}; /// -/// use undo_2::{Commands, IndepStateKey, SetOrTransition, SetTransAction}; +/// use bevy_text::undo_2::{Commands, IndepStateKey, SetOrTransition, SetTransAction}; /// /// #[derive(Copy, Clone, Debug)] /// struct State { @@ -136,24 +139,23 @@ pub enum Action { /// B, /// } /// -/// use SetOrTransition::*; /// let mut commands = Commands::new(); /// let mut state = State::new(); /// /// let c = SetCommands::Color(1.); /// state.apply_set(&c); -/// commands.push(Set(c)); +/// commands.push(SetOrTransition::Set(c)); /// -/// commands.push(Transition(TransitionCommand::A)); -/// commands.push(Transition(TransitionCommand::B)); +/// commands.push(SetOrTransition::Transition(TransitionCommand::A)); +/// commands.push(SetOrTransition::Transition(TransitionCommand::B)); /// /// let c = SetCommands::Length(10.); /// state.apply_set(&c); -/// commands.push(Set(c)); +/// commands.push(SetOrTransition::Set(c)); /// /// let c = SetCommands::Color(2.); /// state.apply_set(&c); -/// commands.push(Set(c)); +/// commands.push(SetOrTransition::Set(c)); /// /// commands.apply_undo(|c| { /// assert_eq!(c, SetTransAction::Set(&SetCommands::Color(1.))); @@ -195,12 +197,14 @@ pub enum SetOrTransition { /// } /// ``` pub trait IndepStateKey { + /// stub `KeyType` type KeyType: PartialEq + Hash; + /// stub key fn key(&self) -> Self::KeyType; } /// The actions asked to be performed by the user -/// when calling [Commands::apply_actions] +/// when calling [`Commands::apply_actions`] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum SetTransAction<'a, S: IndepStateKey, T> { /// Ask the execution of the transition .0 @@ -216,22 +220,22 @@ pub enum SetTransAction<'a, S: IndepStateKey, T> { } impl Action { + /// stub `as_inner` pub fn as_inner(&self) -> &T { match self { - Action::Do(a) => a, - Action::Undo(a) => a, + Action::Do(a) | Action::Undo(a) => a, } } + /// stub `as_inner_mut` pub fn as_inner_mut(&mut self) -> &mut T { match self { - Action::Do(a) => a, - Action::Undo(a) => a, + Action::Do(a) | Action::Undo(a) => a, } } + /// stub `into_inner` pub fn into_inner(self) -> T { match self { - Action::Do(a) => a, - Action::Undo(a) => a, + Action::Do(a) | Action::Undo(a) => a, } } } @@ -247,7 +251,7 @@ impl Action { /// # Example /// /// ``` -/// use undo_2::{Commands,CommandItem}; +/// use bevy_text::undo_2::{Commands,CommandItem}; /// /// let mut commands = Commands::new(); /// @@ -270,11 +274,11 @@ impl Action { #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum CommandItem { - // A command typically created by [Commands::push](Commands#method.push) + /// A command typically created by [`Commands::push`](Commands#method.push) Command(T), - // Signify that `count` CommandItem previous to this item are undone. - // - // Where `count` refers to this variant field. + /// Signify that `count` `CommandItem` previous to this item are undone. + /// + /// Where `count` refers to this variant field. Undo(usize), } @@ -287,7 +291,7 @@ pub enum CommandItem { /// /// # Example /// ``` -/// use undo_2::{Action, Commands}; +/// use bevy_text::undo_2::{Action, Commands}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -320,7 +324,7 @@ pub enum CommandItem { /// /// # Representation /// -/// `Commands` owns a slice of [CommandItem] that is accesible +/// `Commands` owns a slice of [`CommandItem`] that is accesible /// by dereferencing the command. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Eq)] @@ -330,7 +334,7 @@ pub struct Commands { undo_cache: Vec, } impl Debug for Commands { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Commands") .field("commands", &self.commands) .finish() @@ -342,7 +346,7 @@ impl PartialEq for Commands { } } impl Hash for Commands { - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { self.commands.hash(state); } } @@ -368,42 +372,48 @@ struct IndexedAction { index: usize, } -/// Specify a merge when calling [Commands::merge](Commands#method.merge) +/// Specify a merge when calling [`Commands::merge`](Commands#method.merge) /// /// The [`end`, `start`) bounds the slice of command that will /// be removed during the merge. `end` and `start` are in reverse order -/// because [IterRealized] goes backward. +/// because [`IterRealized`] goes backward. /// /// If `command` is `None` then the slice will be removed, otherwise if /// the `command` is `Some(c)` the slice will be replace by `c`. #[derive(Debug)] pub struct Merge<'a, T> { + /// start pub start: IterRealized<'a, T>, + /// end pub end: IterRealized<'a, T>, + /// command pub command: Option, } -/// Specify a splice when calling [Commands::splice](Commands#method.splice) +/// Specify a splice when calling [`Commands::splice`](Commands#method.splice) /// /// The [`end`, `start`) bounds the slice of command that will /// be removed during the merge. `end` and `start` are in reverse order -/// because [IterRealized] goes backward. +/// because [`IterRealized`] goes backward. /// /// The removed slice is then replaced by the sequence (not reversed) of /// commands denoted by `commands`. #[derive(Debug)] pub struct Splice<'a, T, I: IntoIterator> { + /// start pub start: IterRealized<'a, T>, + /// end pub end: IterRealized<'a, T>, + /// commands pub commands: I, } #[derive(Debug)] -/// Iterator of actions returned by [Commands::undo](Commands#method.undo) and -/// [Commands::redo](Commands#method.redo) +/// Iterator of actions returned by [`Commands::undo`](Commands#method.undo) and +/// [`Commands::redo`](Commands#method.redo) pub struct ActionIter<'a, T> { commands: &'a [CommandItem], - to_do: std::slice::Iter<'a, IndexedAction>, + to_do: slice::Iter<'a, IndexedAction>, } impl Clone for ActionIter<'_, T> { fn clone(&self) -> Self { @@ -415,7 +425,7 @@ impl Clone for ActionIter<'_, T> { } #[derive(Debug)] -/// The type of the iterator returned by [Commands::iter_realized](Commands#method.iter_realized). +/// The type of the iterator returned by [`Commands::iter_realized`](Commands#method.iter_realized). pub struct IterRealized<'a, T> { commands: &'a [CommandItem], current: usize, @@ -427,17 +437,17 @@ impl Clone for IterRealized<'_, T> { } impl Commands> { - /// Apply a series of actions represented by the type [SetTransAction] executed + /// Apply a series of actions represented by the type [`SetTransAction`] executed /// by argument `f`. /// /// The list of action to be applied are provided by the call of `act` on /// this [Commands]. This may be defined, for exemple as, `|c| c.redo()` /// - /// This command only exist when the commands have the type [SetOrTransition]. + /// This command only exist when the commands have the type [`SetOrTransition`]. /// See the documentation of this type for an explanation. /// /// The function `f` will be called with [Do][SetTransAction::Do] and [Undo][SetTransAction::Undo] of commands that are - /// represented as [transitions][SetOrTransition::Transition], and [Set][SetTransAction::Set] or [SetToInitial][SetTransAction::SetToInitial] + /// represented as [transitions][SetOrTransition::Transition], and [Set][SetTransAction::Set] or [`SetToInitial`][SetTransAction::SetToInitial] /// (most probably only once) per [key][IndepStateKey::key] for commands that represent independent state setting. pub fn apply_actions( &mut self, @@ -446,17 +456,17 @@ impl Commands> { ) { let mut state_commands = Vec::new(); for command in act(self) { - Self::apply_action(command, &mut state_commands, &mut f) + Self::apply_action(command, &mut state_commands, &mut f); } self.restore_state(state_commands, f); } /// Equivalent to `apply_actions(|c| c.undo(),f)`. pub fn apply_undo(&mut self, f: impl FnMut(SetTransAction)) { - self.apply_actions(|s| s.undo(), f) + self.apply_actions(Commands::undo, f); } /// Equivalent to `apply_actions(|c| c.redo(),f)`. pub fn apply_redo(&mut self, f: impl FnMut(SetTransAction)) { - self.apply_actions(|s| s.redo(), f) + self.apply_actions(Commands::redo, f); } fn apply_action( action: Action<&SetOrTransition>, @@ -467,7 +477,7 @@ impl Commands> { Action::Do(SetOrTransition::Transition(tr)) => f(SetTransAction::Do(tr)), Action::Undo(SetOrTransition::Transition(tr)) => f(SetTransAction::Undo(tr)), Action::Do(SetOrTransition::Set(s)) | Action::Undo(SetOrTransition::Set(s)) => { - state_keys.push(Some(s.key())) + state_keys.push(Some(s.key())); } } } @@ -511,7 +521,7 @@ impl Commands> { } if l > 0 { for disc in state_keys.into_iter().flatten() { - f(SetTransAction::SetToInitial(disc)) + f(SetTransAction::SetToInitial(disc)); } } } @@ -531,14 +541,14 @@ impl Commands { } /// Reserve space for new commands pub fn reserve(&mut self, additional: usize) { - self.commands.reserve(additional) + self.commands.reserve(additional); } /// Return a reference to the last command /// if it is not an Undo. /// # Example /// ``` - /// use undo_2::{Commands, CommandItem}; + /// use bevy_text::undo_2::{Commands, CommandItem}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -568,7 +578,7 @@ impl Commands { /// if it is not an Undo. /// # Example /// ``` - /// use undo_2::{Commands, CommandItem}; + /// use bevy_text::undo_2::{Commands, CommandItem}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -606,7 +616,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{Commands, CommandItem}; + /// use bevy_text::undo_2::{Commands, CommandItem}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -653,7 +663,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{Commands, CommandItem}; + /// use bevy_text::undo_2::{Commands, CommandItem}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -691,7 +701,7 @@ impl Commands { producer: impl FnOnce() -> T, ) { if !self.update_last(updater) { - self.push(producer()) + self.push(producer()); } } @@ -699,7 +709,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::Commands; + /// use bevy_text::undo_2::Commands; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -724,7 +734,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{Action,Commands}; + /// use bevy_text::undo_2::{Action,Commands}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -771,7 +781,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{Action,Commands}; + /// use bevy_text::undo_2::{Action,Commands}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -797,9 +807,7 @@ impl Commands { /// ``` #[must_use = "the returned actions should be realized"] pub fn undo_repeat(&mut self, repeat: usize) -> ActionIter<'_, T> { - let repeat = if let Some(v) = repeat.checked_sub(1) { - v - } else { + let Some(repeat) = repeat.checked_sub(1) else { return ActionIter::new(); }; let l = self.len(); @@ -836,7 +844,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{Commands,Action}; + /// use bevy_text::undo_2::{Commands,Action}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -877,6 +885,7 @@ impl Commands { }; self.undo_repeat(to_undo) } + /// rebuild #[must_use = "the returned command should be undone"] pub fn rebuild(&mut self) -> ActionIter<'_, T> { if !self.is_undoing() { @@ -912,7 +921,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{Action,Commands}; + /// use bevy_text::undo_2::{Action,Commands}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -940,7 +949,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{Action,Commands}; + /// use bevy_text::undo_2::{Action,Commands}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -962,9 +971,7 @@ impl Commands { /// ``` #[must_use = "the returned actions should be realized"] pub fn redo_repeat(&mut self, repeat: usize) -> ActionIter<'_, T> { - let repeat = if let Some(v) = repeat.checked_sub(1) { - v - } else { + let Some(repeat) = repeat.checked_sub(1) else { return ActionIter::new(); }; let l = self.len(); @@ -988,7 +995,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{Action,Commands}; + /// use bevy_text::undo_2::{Action,Commands}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -1021,7 +1028,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{Action,Commands}; + /// use bevy_text::undo_2::{Action,Commands}; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -1058,7 +1065,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::Commands; + /// use bevy_text::undo_2::Commands; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -1094,7 +1101,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::Commands; + /// use bevy_text::undo_2::Commands; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -1123,7 +1130,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::Commands; + /// use bevy_text::undo_2::Commands; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -1149,7 +1156,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::*; + /// use bevy_text::undo_2::*; /// #[derive(Debug, Eq, PartialEq)] /// enum Command { /// A, @@ -1181,10 +1188,10 @@ impl Commands { } /// Repeat undo or redo so that the last realiazed command correspond to - /// the [CommandItem] index passed `index`. + /// the [`CommandItem`] index passed `index`. /// /// ``` - /// use undo_2::{Action,Commands, CommandItem}; + /// use bevy_text::undo_2::{Action,Commands, CommandItem}; /// use std::time::{Instant, Duration}; /// /// #[derive(Debug, Eq, PartialEq)] @@ -1248,7 +1255,7 @@ impl Commands { /// # Example /// /// ``` - /// use undo_2::Commands; + /// use bevy_text::undo_2::Commands; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -1279,7 +1286,7 @@ impl Commands { /// Complexity: O(n) /// /// ``` - /// use undo_2::Commands; + /// use bevy_text::undo_2::Commands; /// use std::time::{Instant, Duration}; /// /// #[derive(Debug, Eq, PartialEq)] @@ -1337,7 +1344,7 @@ impl Commands { CommandItem::Undo(_) => false, CommandItem::Command(c) => stop_pred(c), }) { - self.remove_first(i) + self.remove_first(i); } } /// Try to keep `count` most recent commands by dropping removable commands. @@ -1348,7 +1355,7 @@ impl Commands { /// Complexity: O(n) /// /// ``` - /// use undo_2::Commands; + /// use bevy_text::undo_2::Commands; /// use std::time::{Instant, Duration}; /// /// #[derive(Debug, Eq, PartialEq)] @@ -1404,7 +1411,7 @@ impl Commands { /// ``` pub fn keep_last(&mut self, count: usize) { let i = self.len().saturating_sub(count); - self.remove_first(i) + self.remove_first(i); } /// Remove `count` or less of the oldest command. /// @@ -1414,7 +1421,7 @@ impl Commands { /// Complexity: O(n) /// /// ``` - /// use undo_2::Commands; + /// use bevy_text::undo_2::Commands; /// use std::time::{Instant, Duration}; /// /// #[derive(Debug, Eq, PartialEq)] @@ -1479,10 +1486,10 @@ impl Commands { fn remove_i(&self, mut i: usize, end: usize) -> usize { let i0 = i; for j in i0..end { - if let CommandItem::Undo(count) = self.commands[j] { - if j - count - 1 < i { - i = self.remove_i(j - count - 1, i) - } + if let CommandItem::Undo(count) = self.commands[j] + && j - count - 1 < i + { + i = self.remove_i(j - count - 1, i); } } i @@ -1504,7 +1511,7 @@ impl Commands { /// The iterator would iterator over the sequence [C, A]. /// /// ``` - /// use undo_2::Commands; + /// use bevy_text::undo_2::Commands; /// /// #[derive(Debug, Eq, PartialEq)] /// enum Command { @@ -1538,7 +1545,7 @@ impl Commands { /// Merge a sequence of [*realized commands*](Commands#method.iter_realized) into a single new /// command or remove the sequence. /// - /// The parameter `f` takes as an input a [IterRealized], and returns a + /// The parameter `f` takes as an input a [`IterRealized`], and returns a /// [`std::ops::ControlFlow, Option>`](std::ops::ControlFlow). If the /// returned value contain a `Some(merge)`[Merge], the action specified by `merge` is then /// inserted in place. @@ -1555,7 +1562,7 @@ impl Commands { /// # Example /// /// ``` - /// use undo_2::{Commands, CommandItem, Merge, IterRealized}; + /// use bevy_text::undo_2::{Commands, CommandItem, Merge, IterRealized}; /// use std::ops::ControlFlow; /// /// #[derive(Eq, PartialEq, Debug)] @@ -1598,15 +1605,15 @@ impl Commands { { use ControlFlow::*; self.splice(|it| match f(it) { - Continue(c) => Continue(c.map(|m| m.into())), - Break(c) => Break(c.map(|m| m.into())), - }) + Continue(c) => Continue(c.map(Into::into)), + Break(c) => Break(c.map(Into::into)), + }); } /// Replace a sequence of command by an other. This is a generalization of - /// [Commands::merge](Commands#method.merge) + /// [`Commands::merge`](Commands#method.merge) /// - /// The parameter `f` takes as an input a [IterRealized], and returns a + /// The parameter `f` takes as an input a [`IterRealized`], and returns a /// [`std::ops::ControlFlow, Option>`](std::ops::ControlFlow). If the returned value /// contain a `Some(splice)`[Splice], the actions specified by `splice` are then inserted in /// place. @@ -1623,7 +1630,7 @@ impl Commands { /// # Example /// /// ``` - /// use undo_2::{Commands, CommandItem, Splice, IterRealized}; + /// use bevy_text::undo_2::{Commands, CommandItem, Splice, IterRealized}; /// use std::ops::ControlFlow; /// /// // we suppose that A, B, C is equivalent to D,E @@ -1705,7 +1712,7 @@ impl Commands { let end_i = rev_start; let start_i = rev_end; self.commands - .splice(start_i..end_i, commands.into_iter().map(|c| c.into())); + .splice(start_i..end_i, commands.into_iter().map(Into::into)); } /// Clean up the history of all the undone commands. @@ -1715,7 +1722,7 @@ impl Commands { /// /// # Example /// ``` - /// use undo_2::{CommandItem, Commands}; + /// use bevy_text::undo_2::{CommandItem, Commands}; /// /// #[derive(Debug, PartialEq)] /// enum Command { @@ -1736,14 +1743,14 @@ impl Commands { /// assert_eq!(*c, [A.into(), C.into()]); /// ``` pub fn remove_all_undone(&mut self) { - self.remove_undone(|i| i) + self.remove_undone(|i| i); } /// Clean up the history of all undone commands before a given /// [realized iterator](Commands#method.iter_realized). /// /// # Example /// ``` - /// use undo_2::{Commands,CommandItem}; + /// use bevy_text::undo_2::{Commands,CommandItem}; /// /// #[derive(Debug, PartialEq)] /// enum Command { @@ -1833,7 +1840,7 @@ impl Clone for Commands { } } fn clone_from(&mut self, source: &Self) { - self.commands.clone_from(&source.commands) + self.commands.clone_from(&source.commands); } } @@ -1855,13 +1862,13 @@ impl Borrow<[CommandItem]> for Commands { } impl Extend for Commands { fn extend>(&mut self, iter: I) { - self.commands.extend(iter.into_iter().map(|c| c.into())) + self.commands.extend(iter.into_iter().map(Into::into)); } } impl FromIterator for Commands { fn from_iter>(iter: I) -> Self { Self { - commands: iter.into_iter().map(|c| c.into()).collect(), + commands: iter.into_iter().map(Into::into).collect(), undo_cache: vec![], } } @@ -1875,7 +1882,7 @@ impl<'a, T> IntoIterator for &'a Commands { } impl IntoIterator for Commands { type Item = CommandItem; - type IntoIter = std::vec::IntoIter>; + type IntoIter = vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.commands.into_iter() } @@ -1907,13 +1914,13 @@ impl<'a, T> From> for Splice<'a, T, option::IntoIter> { } impl IterRealized<'_, T> { - // Returned the index of the command refered by the previous non `None` result of call to - // `next`. - // - // This same command is accessible by indexing [Commands] at this returned index. - // - // This index can be used to set the first realized command with - // [Commands::undo_or_redo_to_index](Commands#method.undo_or_redo_to_index). + /// Returned the index of the command refered by the previous non `None` result of call to + /// `next`. + /// + /// This same command is accessible by indexing [Commands] at this returned index. + /// + /// This index can be used to set the first realized command with + /// [`Commands::undo_or_redo_to_index`](Commands#method.undo_or_redo_to_index). pub fn index(&self) -> usize { self.current } @@ -2066,14 +2073,14 @@ fn do_simplify(to_do: &mut [IndexedAction]) { if to_do[cursor].is_reverse_of(action) { cursor += 1; while cursor < l && to_do[cursor].action == Skip { - cursor += 1 + cursor += 1; } } else { to_do[analyzed + 1..cursor] .iter_mut() .for_each(|a| a.action = Skip); if cursor == analyzed + 1 { - cursor = analyzed + cursor = analyzed; } else { cursor = analyzed + 1; analyzed += 1; diff --git a/crates/bevy_text/src/undo_2/mod.rs b/crates/bevy_text/src/undo_2/mod.rs new file mode 100644 index 0000000000000..3db4fc569c309 --- /dev/null +++ b/crates/bevy_text/src/undo_2/mod.rs @@ -0,0 +1,6 @@ +//! Support for undo + +mod lib; +mod tests; + +pub use lib::*; diff --git a/crates/bevy_text/src/undo_2/tests/iter_realized.rs b/crates/bevy_text/src/undo_2/tests/iter_realized.rs index 3afdd2179e1b6..f2c20a771f488 100644 --- a/crates/bevy_text/src/undo_2/tests/iter_realized.rs +++ b/crates/bevy_text/src/undo_2/tests/iter_realized.rs @@ -1,6 +1,6 @@ -#![allow(unused)] +#![allow(unused, reason = "tests")] -use undo_2::*; +use crate::undo_2::*; #[derive(PartialEq, Debug)] enum Command { diff --git a/crates/bevy_text/src/undo_2/tests/merge.rs b/crates/bevy_text/src/undo_2/tests/merge.rs index 79589fcb7bb89..d2348c0e94dfa 100644 --- a/crates/bevy_text/src/undo_2/tests/merge.rs +++ b/crates/bevy_text/src/undo_2/tests/merge.rs @@ -1,11 +1,10 @@ -use undo_2::*; - #[test] fn merge() { - use std::ops::ControlFlow; - use undo_2::CommandItem; - use undo_2::IterRealized; - use undo_2::Merge; + use crate::undo_2::CommandItem; + use crate::undo_2::Commands; + use crate::undo_2::IterRealized; + use crate::undo_2::Merge; + use core::ops::ControlFlow; #[derive(Eq, PartialEq, Debug)] enum Command { A, @@ -15,7 +14,7 @@ fn merge() { } use Command::*; fn is_ab(mut it: IterRealized<'_, Command>) -> (bool, IterRealized<'_, Command>) { - let cond = it.next() == Some(&Command::B) && it.next() == Some(&Command::A); + let cond = it.next() == Some(&B) && it.next() == Some(&A); (cond, it) } fn parse( @@ -25,7 +24,7 @@ fn merge() { ControlFlow::Continue(Some(Merge { start, end, - command: Some(Command::AB), + command: Some(AB), })) } else { ControlFlow::Continue(None) @@ -33,19 +32,19 @@ fn merge() { } { let mut commands = Commands::new(); - commands.push(Command::A); - commands.push(Command::B); + commands.push(A); + commands.push(B); commands.merge(parse); - assert_eq!(*commands, [CommandItem::Command(Command::AB)]); + assert_eq!(*commands, [CommandItem::Command(AB)]); } { let mut commands = Commands::new(); - commands.push(Command::A); - commands.push(Command::C); - commands.push(Command::B); - commands.push(Command::A); - commands.push(Command::B); + commands.push(A); + commands.push(C); + commands.push(B); + commands.push(A); + commands.push(B); commands.merge(parse); assert_eq!( @@ -60,13 +59,13 @@ fn merge() { } { let mut commands = Commands::new(); - commands.push(Command::A); - commands.push(Command::C); - commands.push(Command::A); - commands.push(Command::B); - commands.push(Command::B); - commands.push(Command::A); - commands.push(Command::B); + commands.push(A); + commands.push(C); + commands.push(A); + commands.push(B); + commands.push(B); + commands.push(A); + commands.push(B); commands.merge(parse); assert_eq!( @@ -82,10 +81,10 @@ fn merge() { } { let mut commands = Commands::new(); - commands.push(Command::A); - commands.push(Command::B); - commands.push(Command::A); - commands.push(Command::B); + commands.push(A); + commands.push(B); + commands.push(A); + commands.push(B); commands.merge(parse); assert_eq!( diff --git a/crates/bevy_text/src/undo_2/tests/mod.rs b/crates/bevy_text/src/undo_2/tests/mod.rs new file mode 100644 index 0000000000000..5b18d9370d989 --- /dev/null +++ b/crates/bevy_text/src/undo_2/tests/mod.rs @@ -0,0 +1,9 @@ +mod iter_realized; +mod merge; +mod redo; +mod remove_undone; +mod set_and_transition; +mod splice; +mod unbuild; +mod undo; +mod undo_tests; diff --git a/crates/bevy_text/src/undo_2/tests/redo.rs b/crates/bevy_text/src/undo_2/tests/redo.rs index bfbb7eb163ae5..1bc520691da33 100644 --- a/crates/bevy_text/src/undo_2/tests/redo.rs +++ b/crates/bevy_text/src/undo_2/tests/redo.rs @@ -1,5 +1,5 @@ -#![allow(unused)] -use undo_2::{Action, Commands}; +#![allow(unused, reason = "tests")] +use crate::undo_2::{Action, Commands}; #[derive(Debug, Eq, PartialEq)] enum Command { @@ -79,5 +79,5 @@ fn redo_all() { commands.undo(); let v: Vec<_> = commands.redo_all().collect(); - assert_eq!(v, [Do(&A), Do(&Command::B)]); + assert_eq!(v, [Do(&A), Do(&B)]); } diff --git a/crates/bevy_text/src/undo_2/tests/remove_undone.rs b/crates/bevy_text/src/undo_2/tests/remove_undone.rs index 7ebcc33128d5b..37de3f0b7b676 100644 --- a/crates/bevy_text/src/undo_2/tests/remove_undone.rs +++ b/crates/bevy_text/src/undo_2/tests/remove_undone.rs @@ -1,5 +1,5 @@ -#![allow(unused)] -use undo_2::*; +#![allow(unused, reason = "tests")] +use crate::undo_2::*; #[test] fn remove_undone() { diff --git a/crates/bevy_text/src/undo_2/tests/set_and_transition.rs b/crates/bevy_text/src/undo_2/tests/set_and_transition.rs index b1909ca1893c3..c93b1702096f5 100644 --- a/crates/bevy_text/src/undo_2/tests/set_and_transition.rs +++ b/crates/bevy_text/src/undo_2/tests/set_and_transition.rs @@ -1,6 +1,6 @@ -use std::mem::{discriminant, Discriminant}; +use core::mem::{discriminant, Discriminant}; -use undo_2::{Commands, IndepStateKey, SetOrTransition, SetTransAction}; +use crate::undo_2::{IndepStateKey, SetTransAction}; #[derive(Copy, Clone, Debug)] struct State { @@ -19,8 +19,7 @@ impl State { } fn execute_action(&mut self, c: SetTransAction) { match c { - SetTransAction::Do(_) => {} - SetTransAction::Undo(_) => {} + SetTransAction::Do(_) | SetTransAction::Undo(_) => {} SetTransAction::Set(c) => self.apply_set(c), SetTransAction::SetToInitial(d) => self.apply_set(SetCommands::new_initial(d)), } @@ -67,7 +66,7 @@ enum TransitionCommand { #[test] fn set_and_transition() { - use SetOrTransition::*; + use crate::undo_2::{Commands, SetOrTransition::*}; let mut commands = Commands::new(); let mut state = State::new(); diff --git a/crates/bevy_text/src/undo_2/tests/splice.rs b/crates/bevy_text/src/undo_2/tests/splice.rs index 9a6bb6f2049e3..c906b9a3bd9bc 100644 --- a/crates/bevy_text/src/undo_2/tests/splice.rs +++ b/crates/bevy_text/src/undo_2/tests/splice.rs @@ -1,7 +1,7 @@ -#![allow(unused)] +#![allow(unused, reason = "tests")] -use std::ops::ControlFlow; -use undo_2::*; +use crate::undo_2::*; +use core::ops::ControlFlow; #[derive(PartialEq, Debug)] enum Command { @@ -29,7 +29,7 @@ fn do_splice(c: &mut Commands) { } else { ControlFlow::Continue(None) } - }) + }); } #[test] @@ -159,7 +159,7 @@ fn splice_with_undos() { do_splice(&mut c); let v: Vec<_> = c.iter_realized().collect(); assert_eq!(*v, [&E, &D]); - assert_eq!(*c, [D.into(), E.into()]) + assert_eq!(*c, [D.into(), E.into()]); } { let mut c = Commands::default(); @@ -179,7 +179,7 @@ fn splice_with_undos() { do_splice(&mut c); let v: Vec<_> = c.iter_realized().collect(); assert_eq!(*v, [&E, &E, &D]); - assert_eq!(*c, [D.into(), E.into(), E.into()]) + assert_eq!(*c, [D.into(), E.into(), E.into()]); } { let mut c = Commands::default(); @@ -193,6 +193,6 @@ fn splice_with_undos() { do_splice(&mut c); let v: Vec<_> = c.iter_realized().collect(); assert_eq!(*v, [&E, &D]); - assert_eq!(*c, [D.into(), E.into()]) + assert_eq!(*c, [D.into(), E.into()]); } } diff --git a/crates/bevy_text/src/undo_2/tests/unbuild.rs b/crates/bevy_text/src/undo_2/tests/unbuild.rs index af443f66b8415..dd80720cd93cb 100644 --- a/crates/bevy_text/src/undo_2/tests/unbuild.rs +++ b/crates/bevy_text/src/undo_2/tests/unbuild.rs @@ -1,5 +1,5 @@ -#![allow(unused)] -use undo_2::{Action, Commands}; +#![allow(unused, reason = "tests")] +use crate::undo_2::{Action, Commands}; #[derive(Debug, Eq, PartialEq)] enum Command { diff --git a/crates/bevy_text/src/undo_2/tests/undo.rs b/crates/bevy_text/src/undo_2/tests/undo.rs index 2caa42b27a21d..6a1728671d7eb 100644 --- a/crates/bevy_text/src/undo_2/tests/undo.rs +++ b/crates/bevy_text/src/undo_2/tests/undo.rs @@ -1,5 +1,5 @@ -#![allow(unused)] -use undo_2::{Action, Commands}; +#![allow(unused, reason = "tests")] +use crate::undo_2::{Action, Commands}; #[derive(Debug, Eq, PartialEq)] enum Command { diff --git a/crates/bevy_text/src/undo_2/tests/undo_tests.rs b/crates/bevy_text/src/undo_2/tests/undo_tests.rs index 9711dc601795e..a700d3b06ffe6 100644 --- a/crates/bevy_text/src/undo_2/tests/undo_tests.rs +++ b/crates/bevy_text/src/undo_2/tests/undo_tests.rs @@ -1,8 +1,8 @@ -use undo_2::Action; -use undo_2::Action::*; -use undo_2::Commands; +#![allow(unused, reason = "tests")] +use crate::undo_2::Action; +use crate::undo_2::Action::*; +use crate::undo_2::Commands; #[test] -#[allow(unused)] fn application() { enum Command { Add(char), @@ -30,12 +30,12 @@ fn application() { } pub fn undo(&mut self) { for action in self.command.undo() { - interpret_action(&mut self.text, action) + interpret_action(&mut self.text, action); } } pub fn redo(&mut self) { for action in self.command.redo() { - interpret_action(&mut self.text, action) + interpret_action(&mut self.text, action); } } } diff --git a/typos.toml b/typos.toml index 16aab11772821..83014e7e1dc0c 100644 --- a/typos.toml +++ b/typos.toml @@ -1,9 +1,10 @@ [files] extend-exclude = [ - "*.pbxproj", # metadata file - "*.patch", # Automatically generated files that should not be manually modified. - "*.bin", # Binary files - ".git/", # Version control files + "*.pbxproj", # metadata file + "*.patch", # Automatically generated files that should not be manually modified. + "*.bin", # Binary files + ".git/", # Version control files + "crates/bevy_text/src/undo_2/", # undo_2 ] ignore-hidden = false From eae50b02be5d71cd1d59ecf1ba8b42c8feb4d3db Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Mon, 11 Aug 2025 07:49:18 +0800 Subject: [PATCH 3/3] Add temporary notice --- crates/bevy_text/src/undo_2/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bevy_text/src/undo_2/README.md b/crates/bevy_text/src/undo_2/README.md index f30f8d4db7089..e367e8f6a8c43 100644 --- a/crates/bevy_text/src/undo_2/README.md +++ b/crates/bevy_text/src/undo_2/README.md @@ -1,6 +1,10 @@ # Undo done the right way -Via [original](https://gitlab.com/okannen/undo_2/-/tree/b32c34edb2c15c266b946f0d82188624f3aa3fdc) +This code is from the library `undo_2` , which has a version `0.3.0` that is not released to `crates.io`. + +[Original source](https://gitlab.com/okannen/undo_2/-/tree/b32c34edb2c15c266b946f0d82188624f3aa3fdc) + +It is temporarily upstreamed to this repo, with the intention to use the original source in the future. ## Introduction