Giter Club home page Giter Club logo

undo's Introduction

undo

An undo-redo library.

Rust Crates.io Docs

An implementation of the command pattern, where all edits are done by creating objects that applies the modifications. All objects knows how to undo the changes it applies, and by using the provided data structures it is easy to undo and redo edits made to a target.

See the documentation and examples for more information.

Examples

use undo::{Edit, Record};

struct Add(char);

impl Edit for Add {
    type Target = String;
    type Output = ();

    fn edit(&mut self, target: &mut String) {
        target.push(self.0);
    }

    fn undo(&mut self, target: &mut String) {
        self.0 = target.pop().unwrap();
    }
}

fn main() {
    let mut target = String::new();
    let mut record = Record::new();

    record.edit(&mut target, Add('a'));
    record.edit(&mut target, Add('b'));
    record.edit(&mut target, Add('c'));
    assert_eq!(target, "abc");

    record.undo(&mut target);
    record.undo(&mut target);
    record.undo(&mut target);
    assert_eq!(target, "");

    record.redo(&mut target);
    record.redo(&mut target);
    record.redo(&mut target);
    assert_eq!(target, "abc");
}

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

undo's People

Contributors

evenorog avatar mrnossiom avatar peterix avatar yuhr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

undo's Issues

chain/merged removed; non-transitive merging

Hi,

first of all thank you for developing this lib. It helps me a great deal with my current hobby project.

Now, this might be more of a question than an issue. I was planning to merge certain commands depending on the preceding command, which should be possible by implementing the merge method. However, if I understand correctly, merging is always transitive because all mergable Commands share the same id for merging. i.e. if A merges with B and B with C, A also merges with C, even if I do not want to merge A and C if there was no B in between. How can I prevent this?

I saw that there used to be the Merged/Chain struct that would allow me to manually create such a chain of commands, but chain.rs got removed. What is the reason for this or did I misunderstand this somehow and the desired behaviour can still be realized without a Chain.

Kind regards
Evylon

Question: How to store data for undo?

In the examples, undo always just removes the last symbol from the string.

When I also have a Remove action, I would need to store, which letter has been removed in order to undo that action.

I didn't find anything about that in the documentation.
It has to be possible already. Else this crate wouldn't be very useful.

feat(history): branches detail

I'm trying to use History to implement the Undo Tree feature in my editor, however, there's no direct method to query details of the branches except using the display method.

I propose the following methods to be added:

  1. fn branch_len() -> usize
  2. fn branch_actions(branch_index: usize) -> Range<At>

Unable to create a History without providing type information

let history = History::<_>::builder().limit(10).capacity(10).build();
gives the error:

error[E0282]: type annotations needed for `History<E>`      
   --> src\main.rs:157:17
    |
157 |             let history = History::<_>::builder().limit(10).capacity(10).build();
    |                 ^^^^^^^   ----------------------- type must be known at this point
    |
help: consider giving `history` an explicit type, where the type for type parameter `E` is specified
    |
157 |             let history: History<E> = History::<_>::builder().limit(10).capacity(10).build();

What am I supposed to put for the type information?

Any examples?

Having a hard time integrating this into my project. How would a struct own a Record?

Adding More Examples

Hello, I was wondering if we could possibly have an example of a more complex Undo, maybe one where we a managing the state of an app with the RefCell pattern? I tried to implement Undo Redo for this simple example but could not figure it out, do you have an approach?

use undo::{Action, History};
use std::cell::RefCell;
use std::rc::Rc;
use std::error::Error;

#[derive(Clone, Debug)]
struct AppState{
    names: Vec<String>
    //...
}

struct AppContainer(Rc<RefCell<AppState>>);
type BoxResult<T> = Result<T,Box<Error>>;

impl AppContainer{
    fn new (inner: AppState)->AppContainer{
        let cl = AppState{
            names: inner.names.clone(),
        };
        return AppContainer(Rc::new(RefCell::new(cl)));
    }
    fn add_name(&self, name: String){
        (self.0.borrow_mut()).names.push(name);
    }
    fn remove_name(&self, to_remove: String){
        (self.0.borrow_mut()).names.retain(|x| *x != to_remove)
    }
    fn names(&self)->Vec<String>{
        (self.0.borrow()).names
    }
}

struct AddName(String);
impl Action for AddName{
    type Target = (String, AppContainer);
    type Output = ();
    type Error = &'static str;

    fn apply(&mut self, s: (String, &mut AppContainer)) -> undo::Result<AddName> {
        s.1.add_name(s.0);
        Ok(())
    }

    fn undo(&mut self, s: (String, &mut AppContainer)) -> undo::Result<AddName> {
        s.1.remove_name(s.0);
        Ok(())
    }
    
}
//inverse of AddName
struct RemoveName(String);
impl Action for RemoveName{
    type Target = (String, AppContainer);
    type Output = ();
    type Error = &'static str;

    fn apply(&mut self, s: (String, &mut AppContainer)) -> undo::Result<RemoveName> {
        s.1.remove_name(s.0);
        Ok(())
    }

    fn undo(&mut self, s: (String, &mut AppContainer)) -> undo::Result<RemoveName> {
        s.1.add_name(s.0);
        Ok(())
    }
    
}

// fn main()->undo::Result<Box<dyn Action>>{
fn main(){
    type Target = (String, AppContainer);
    type Output = ();
    type Error = &'static str;

    let initial_state = AppState{
        names: vec!["name 1".to_string(), "name 2".to_string()],
    };
    let mut history = History::new();
    let container = AppContainer::new(initial_state.clone());
    history.apply(&mut container, AddName("My Name".to_string())).unwrap();
    dbg!(container.names());
    history.apply(&mut container, RemoveName("Name 1".to_string())).unwrap();
    dbg!(container.names());
    // Ok(())
}

Don't require Action to return a Result

Some actions can't error, so it's annoying to be forced to do so. I think you could just remove that and have the output associated type be a result if needed.

feat(history/display): better UX

Preface

Currently, I'm using History::display to display the navigation history for my editor, and I find it confusing during branch switching.

Steps to reproduce

For example, say we start with the following tree:

* 1-3 [HEAD] src/position.rs
* 1-2 src/soft_wrap.rs
| * 0-3 src/quickfix_list.rs
| * 0-2 src/context.rs
|/
* 1-1 src/components/editor.rs
* 1-0 [SAVED]

Expected outcome

And when I switched to [0-3], I expect it to display as follows (only [HEAD] is moved):

* 1-3 src/position.rs
* 1-2 src/soft_wrap.rs
| * 0-3 [HEAD] src/quickfix_list.rs
| * 0-2 src/context.rs
|/
* 1-1 src/components/editor.rs
* 1-0 [SAVED]

Actual outcome

However, it surprised me by totally modifying the tree, specifically:

  1. Making branch 0 the main trunk
  2. Renaming entries 1-1 and 1-0, to 0-1 and 0-0 respectively
* 0-3 [HEAD] src/quickfix_list.rs
* 0-2 src/context.rs
| * 1-3 src/position.rs
| * 1-2 src/soft_wrap.rs
|/
* 0-1 src/components/editor.rs
* 0-0 [SAVED]

Document difference to the redo crate

It would be good if there were a couple of sentences in the documentation at the top-level on the differences between this crate and the redo crate, linking between the two crates. It seems the main difference lies within Record, where in the redo crate a Record owns a Target but in this create you pass in a &mut Target each time you call the apply, undo, or redo functions.

Provide Type Arguments to History::new()

Hello,

Wondering if it would be possible to provide type arguments to History::new()?
Using lazy_static and other such libraries requires Send+Sync, but I dont' see a way to add these traits in History constructor.

use undo::{Action, History, Merged, Signal};
use enum_dispatch::enum_dispatch;

lazy_static::lazy_static!{
    static ref APP_STATE: AppState = AppState::new();
}

pub struct AppState{
    names: Vec<String>,
    history: History<Box<dyn AppAction>, Box<dyn FnMut(Signal) + Send + Sync>>
}

impl AppState{
    pub fn new()->Self{
        AppState{
            names: vec![],
            history: History::new(),
            // history: History::new<Box<dyn AppAction>, Box<dyn FnMut(Signal) + Send + Sync>>(),
        }
    }
    pub fn add_name(&mut self, name: String){
        self.names.push(name.clone())
    }
    pub fn remove_name(&mut self, name: String){
        let keep =  self.names.clone().into_iter().filter(|n|*n != name).collect();
        self.names = keep
    }
}

pub struct AddName(String);


#[enum_dispatch]
pub enum AppActionEnum {
    DoAddName(AddName),
}


#[enum_dispatch(AppActionEnum)]
pub trait AppAction: Send + Sync{
    fn apply(&mut self, target: &mut AppState) -> Result<(), String>;

    fn undo(&mut self, target: &mut AppState) -> Result<(), String>;

    fn redo(&mut self, target: &mut AppState) -> Result<(), String> {
        self.apply(target)
    }

    fn merge(&mut self, test: &mut AppActionEnum) -> Merged {
        Merged::No
    }
}

impl AppAction for AddName{
    fn apply(&mut self, target: &mut AppState) -> Result<(), String>{
        target.add_name("Test".to_string());
        Ok(())
    }

    fn undo(&mut self, target: &mut AppState) -> Result<(), String>{
        Ok(())
    }

    fn redo(&mut self, target: &mut AppState) -> Result<(), String> {
        self.apply(target)
    }

    fn merge(&mut self, test: &mut AppActionEnum) -> Merged {
        Merged::No
    }
}


impl Action for AppActionEnum {
    type Target = AppState;
    type Output = ();
    type Error = String;

    fn apply(&mut self, target: &mut AppState) -> Result<(), String> {
        AppAction::apply(self, target)
    }

    fn undo(&mut self, target: &mut AppState) -> Result<(), String> {
        AppAction::undo(self, target)
    }

    fn redo(&mut self, target: &mut AppState) -> Result<(), String> {
        AppAction::redo(self, target)
    }

    fn merge(&mut self, other: &mut AppActionEnum) -> Merged {
        AppAction::merge(self, other)
    }
} 

fn main(){
    dbg!("test");
}

gives error:

21 |             history: History::new(),
   |                      ^^^^^^^^^^^^^^ expected trait `FnMut(Signal) + Send + Sync`, found trait `FnMut(Signal)`

Question: Example of merge actions?

Doc says;
Actions can be merged into a single action by implementing the merge method on the action.

So I did like that...

struct Add(String);

impl Action for Add {
    type Target = String;
    type Output = ();
    type Error = &'static str;

    fn apply(&mut self, s: &mut String) -> undo::Result<Add> {
        s.push_str(&self.0.clone());
        Ok(())
    }

    fn undo(&mut self, s: &mut String) -> undo::Result<Add> {
        self.0 = s.pop().ok_or("s is empty")?.into();
        Ok(())
    }

    fn merge(&mut self, other : &mut Self) -> undo::Merged
        where
            Self: Sized, {
        self.0.push_str(&other.0);
        undo::Merged::Yes
    }
}

But I couldn't find a way to merge actions from History or Record.

    let mut target = String::new();
    let mut history = History::new();
    history.apply(&mut target, Add('a'.into())).unwrap();
    history.apply(&mut target, Add('b'.into())).unwrap();
    history.apply(&mut target, Add('c'.into())).unwrap();
    assert_eq!(target, "abc");
    //===Procedure that merge actions into an action...===
    history.undo(&mut target).unwrap().
    assert_eq!(target, "");

Suppose I want to make target empty by one "undo", then how "===Procedure that merge actions into an action...===" section should be?
Can someone give me more details on how to merge them?

Reference to the inner Action list

Can the crate provide a reference (at least immutable) to the Action list inside Record, Timeline and History?

Without it, there's no way to, for example, display the history in a Gtk TreeView, as each leaf needs detailed typed information.
As far as I know, there's no way to do it using the Display struct provided.

In my case, i need to inspect the history after adding things to it, and act accordingly.
But currently I can't.

trait cannot be made into an object because method `merge` references the `Self` type in this parameter

I'm trying to implement an undo/redo system for my app and desire a globally accessible HISTORYvariable to which I can push Box<dyn Action> trait objects.

use undo::{Action, History};

lazy_static::lazy_static! {
    static ref HISTORY: History<Box<dyn Action<Output = (), Error = String, Target = AppState>>> = History::new();
}

This gives error:

error[E0038]: the trait `Action` cannot be made into an object
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
   --> C:\Users\Primary User\.cargo\registry\src\github.com-1ecc6299db9ec823\undo-0.46.0\src\lib.rs:135:8
    |
135 |     fn merge(&mut self, _: &mut Self) -> Merged {
    |        ^^^^^ the trait cannot be made into an object because method `merge` references the `Self` type in this parameter

Is there any way to work around this?

wasm support

It seems the crate not support wasm. I compile it with my project and use trunk to package, cause

error from HTML pipeline

Caused by:
    0: failed to spawn assets finalization
    1: wasm-bindgen call returned a bad status

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.