From 760c1917587ff4763fe8fb69ad118a99f60637eb Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 5 Aug 2021 22:52:19 +0800 Subject: [PATCH] config undo redo for document --- rust-lib/flowy-ot/src/client/document.rs | 62 ++++++++++++++++++----- rust-lib/flowy-ot/src/client/history.rs | 47 +++++++++++++---- rust-lib/flowy-ot/src/core/delta.rs | 14 ++--- rust-lib/flowy-ot/src/errors.rs | 50 +++++++++++++++++- rust-lib/flowy-ot/tests/helper/mod.rs | 26 +++++++--- rust-lib/flowy-ot/tests/invert_test.rs | 12 ++--- rust-lib/flowy-ot/tests/undo_redo_test.rs | 0 7 files changed, 164 insertions(+), 47 deletions(-) create mode 100644 rust-lib/flowy-ot/tests/undo_redo_test.rs diff --git a/rust-lib/flowy-ot/src/client/document.rs b/rust-lib/flowy-ot/src/client/document.rs index fbb6655856..30b8847c7e 100644 --- a/rust-lib/flowy-ot/src/client/document.rs +++ b/rust-lib/flowy-ot/src/client/document.rs @@ -1,5 +1,5 @@ use crate::{ - client::{History, UndoResult}, + client::{History, RevId, Revision, UndoResult}, core::{ Attribute, Attributes, @@ -10,11 +10,13 @@ use crate::{ OpBuilder, Operation, }, + errors::{ErrorBuilder, OTError, OTErrorCode::*}, }; pub struct Document { data: Delta, history: History, + rev_id_counter: u64, } impl Document { @@ -22,6 +24,7 @@ impl Document { Document { data: Delta::new(), history: History::new(), + rev_id_counter: 1, } } @@ -53,9 +56,38 @@ impl Document { self.update_with_attribute(attributes, interval); } - pub fn undo(&mut self) -> UndoResult { unimplemented!() } + pub fn can_undo(&self) -> bool { self.history.can_undo() } - pub fn redo(&mut self) -> UndoResult { unimplemented!() } + pub fn can_redo(&self) -> bool { self.history.can_redo() } + + pub fn undo(&mut self) -> Result { + match self.history.undo() { + None => Err(ErrorBuilder::new(UndoFail).build()), + Some(undo_delta) => { + let new_delta = self.data.compose(&undo_delta)?; + let result = UndoResult::success(new_delta.target_len as u64); + let redo_delta = undo_delta.invert_delta(&self.data); + self.data = new_delta; + self.history.add_redo(redo_delta); + + Ok(result) + }, + } + } + + pub fn redo(&mut self) -> Result { + match self.history.redo() { + None => Err(ErrorBuilder::new(RedoFail).build()), + Some(redo_delta) => { + let new_delta = self.data.compose(&redo_delta)?; + let result = UndoResult::success(new_delta.target_len as u64); + let redo_delta = redo_delta.invert_delta(&self.data); + self.data = new_delta; + self.history.add_undo(redo_delta); + Ok(result) + }, + } + } pub fn delete(&mut self, interval: Interval) { let delete = OpBuilder::delete(interval.size() as u64).build(); @@ -96,6 +128,13 @@ impl Document { } let new_data = self.data.compose(&new_delta).unwrap(); + let undo_delta = new_data.invert_delta(&self.data); + self.rev_id_counter += 1; + + if !undo_delta.is_empty() { + self.history.add_undo(undo_delta); + } + self.data = new_data; } @@ -128,24 +167,19 @@ impl Document { self.update_with_op(retain, interval); } + + fn next_rev_id(&self) -> RevId { RevId(self.rev_id_counter) } } -pub fn transform(left: &Document, right: &Document) -> (Document, Document) { +pub fn transform(left: &mut Document, right: &mut Document) { let (a_prime, b_prime) = left.data.transform(&right.data).unwrap(); log::trace!("a:{:?},b:{:?}", a_prime, b_prime); let data_left = left.data.compose(&b_prime).unwrap(); let data_right = right.data.compose(&a_prime).unwrap(); - ( - Document { - data: data_left, - history: left.history.clone(), - }, - Document { - data: data_right, - history: right.history.clone(), - }, - ) + + left.set_data(data_left); + right.set_data(data_right); } fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) { diff --git a/rust-lib/flowy-ot/src/client/history.rs b/rust-lib/flowy-ot/src/client/history.rs index caaf9b1a6d..f83fa15023 100644 --- a/rust-lib/flowy-ot/src/client/history.rs +++ b/rust-lib/flowy-ot/src/client/history.rs @@ -2,10 +2,17 @@ use crate::core::{Delta, Interval, OpBuilder, Operation}; const MAX_UNDOS: usize = 20; +#[derive(Debug, Clone)] +pub struct RevId(pub u64); + #[derive(Debug, Clone)] pub struct Revision { - rev_id: u64, - delta: Delta, + rev_id: RevId, + pub delta: Delta, +} + +impl Revision { + pub fn new(rev_id: RevId, delta: Delta) -> Revision { Self { rev_id, delta } } } #[derive(Debug, Clone)] @@ -14,11 +21,22 @@ pub struct UndoResult { len: u64, } +impl UndoResult { + pub fn fail() -> Self { + UndoResult { + success: false, + len: 0, + } + } + + pub fn success(len: u64) -> Self { UndoResult { success: true, len } } +} + #[derive(Debug, Clone)] pub struct History { cur_undo: usize, - undos: Vec, - redos: Vec, + undos: Vec, + redos: Vec, } impl History { @@ -34,17 +52,24 @@ impl History { pub fn can_redo(&self) -> bool { !self.redos.is_empty() } - pub fn record(&mut self, _change: Delta) {} + pub fn add_undo(&mut self, delta: Delta) { self.undos.push(delta); } - pub fn undo(&mut self) -> Option { + pub fn add_redo(&mut self, delta: Delta) { self.redos.push(delta); } + + pub fn undo(&mut self) -> Option { if !self.can_undo() { return None; } - - let revision = self.undos.pop().unwrap(); - - Some(revision) + let delta = self.undos.pop().unwrap(); + Some(delta) } - pub fn redo(&mut self) -> Option { None } + pub fn redo(&mut self) -> Option { + if !self.can_redo() { + return None; + } + + let delta = self.redos.pop().unwrap(); + Some(delta) + } } diff --git a/rust-lib/flowy-ot/src/core/delta.rs b/rust-lib/flowy-ot/src/core/delta.rs index e751e120ac..ee3bcf7895 100644 --- a/rust-lib/flowy-ot/src/core/delta.rs +++ b/rust-lib/flowy-ot/src/core/delta.rs @@ -1,6 +1,6 @@ use crate::{ core::{attributes::*, operation::*, Interval}, - errors::OTError, + errors::{ErrorBuilder, OTError, OTErrorCode}, }; use bytecount::num_chars; use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr}; @@ -146,7 +146,7 @@ impl Delta { /// conflicts. pub fn compose(&self, other: &Self) -> Result { if self.target_len != other.base_len { - return Err(OTError); + return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); } let mut new_delta = Delta::default(); @@ -167,7 +167,7 @@ impl Delta { next_op2 = ops2.next(); }, (None, _) | (_, None) => { - return Err(OTError); + return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); }, (Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => { let composed_attrs = compose_operation(&next_op1, &next_op2); @@ -297,7 +297,7 @@ impl Delta { /// length conflicts. pub fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> { if self.base_len != other.base_len { - return Err(OTError); + return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); } let mut a_prime = Delta::default(); @@ -324,10 +324,10 @@ impl Delta { next_op2 = ops2.next(); }, (None, _) => { - return Err(OTError); + return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); }, (_, None) => { - return Err(OTError); + return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); }, (Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => { let composed_attrs = transform_operation(&next_op1, &next_op2); @@ -418,7 +418,7 @@ impl Delta { /// conflicts. pub fn apply(&self, s: &str) -> Result { if num_chars(s.as_bytes()) != self.base_len { - return Err(OTError); + return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); } let mut new_s = String::new(); let chars = &mut s.chars(); diff --git a/rust-lib/flowy-ot/src/errors.rs b/rust-lib/flowy-ot/src/errors.rs index 623cc96a79..7a74d44e46 100644 --- a/rust-lib/flowy-ot/src/errors.rs +++ b/rust-lib/flowy-ot/src/errors.rs @@ -1,7 +1,19 @@ use std::{error::Error, fmt}; #[derive(Clone, Debug)] -pub struct OTError; +pub struct OTError { + pub code: OTErrorCode, + pub msg: String, +} + +impl OTError { + pub fn new(code: OTErrorCode, msg: &str) -> OTError { + Self { + code, + msg: msg.to_owned(), + } + } +} impl fmt::Display for OTError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "incompatible lengths") } @@ -10,3 +22,39 @@ impl fmt::Display for OTError { impl Error for OTError { fn source(&self) -> Option<&(dyn Error + 'static)> { None } } + +#[derive(Debug, Clone)] +pub enum OTErrorCode { + IncompatibleLength, + UndoFail, + RedoFail, +} + +pub struct ErrorBuilder { + pub code: OTErrorCode, + pub msg: Option, +} + +impl ErrorBuilder { + pub fn new(code: OTErrorCode) -> Self { ErrorBuilder { code, msg: None } } + + pub fn msg(mut self, msg: T) -> Self + where + T: Into, + { + self.msg = Some(msg.into()); + self + } + + pub fn error(mut self, msg: T) -> Self + where + T: std::fmt::Debug, + { + self.msg = Some(format!("{:?}", msg)); + self + } + + pub fn build(mut self) -> OTError { + OTError::new(self.code, &self.msg.take().unwrap_or("".to_owned())) + } +} diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index 7608b07995..315bf8da1f 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -29,8 +29,14 @@ pub enum TestOp { Transform(usize, usize), // invert the delta_a base on the delta_b + #[display(fmt = "Invert")] + Invert(usize, usize), + #[display(fmt = "Undo")] - Undo(usize, usize), + Undo(usize), + + #[display(fmt = "Redo")] + Redo(usize), #[display(fmt = "AssertOpsJson")] AssertOpsJson(usize, &'static str), @@ -80,13 +86,12 @@ impl OpTester { document.format(*interval, Attribute::Italic, *enable); }, TestOp::Transform(delta_a_i, delta_b_i) => { - let (document_a, document_b) = - transform(&self.documents[*delta_a_i], &self.documents[*delta_b_i]); - - self.documents[*delta_a_i] = document_a; - self.documents[*delta_b_i] = document_b; + transform( + &mut self.documents[*delta_a_i], + &mut self.documents[*delta_b_i], + ); }, - TestOp::Undo(delta_a_i, delta_b_i) => { + TestOp::Invert(delta_a_i, delta_b_i) => { let delta_a = &self.documents[*delta_a_i].data(); let delta_b = &self.documents[*delta_b_i].data(); log::debug!("Invert: "); @@ -108,7 +113,12 @@ impl OpTester { self.documents[*delta_a_i].set_data(new_delta_after_undo); }, - + TestOp::Undo(delta_i) => { + self.documents[*delta_i].undo(); + }, + TestOp::Redo(delta_i) => { + self.documents[*delta_i].redo(); + }, TestOp::AssertOpsJson(delta_i, expected) => { let delta_i_json = self.documents[*delta_i].to_json(); diff --git a/rust-lib/flowy-ot/tests/invert_test.rs b/rust-lib/flowy-ot/tests/invert_test.rs index c92d79d5b1..ab4a9b1242 100644 --- a/rust-lib/flowy-ot/tests/invert_test.rs +++ b/rust-lib/flowy-ot/tests/invert_test.rs @@ -23,7 +23,7 @@ fn delta_invert_no_attribute_delta2() { let ops = vec![ Insert(0, "123", 0), Insert(1, "4567", 0), - Undo(0, 1), + Invert(0, 1), AssertOpsJson(0, r#"[{"insert":"123"}]"#), ]; OpTester::new().run_script(ops); @@ -36,7 +36,7 @@ fn delta_invert_attribute_delta_with_no_attribute_delta() { Bold(0, Interval::new(0, 3), true), AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#), Insert(1, "4567", 0), - Undo(0, 1), + Invert(0, 1), AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#), ]; OpTester::new().run_script(ops); @@ -55,7 +55,7 @@ fn delta_invert_attribute_delta_with_no_attribute_delta2() { r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#, ), Insert(1, "abc", 0), - Undo(0, 1), + Invert(0, 1), AssertOpsJson( 0, r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#, @@ -74,7 +74,7 @@ fn delta_invert_no_attribute_delta_with_attribute_delta() { 1, r#"[{"insert":"456","attributes":{"bold":"true"}},{"insert":"7"}]"#, ), - Undo(0, 1), + Invert(0, 1), AssertOpsJson(0, r#"[{"insert":"123"}]"#), ]; OpTester::new().run_script(ops); @@ -93,7 +93,7 @@ fn delta_invert_no_attribute_delta_with_attribute_delta2() { 1, r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"}}]"#, ), - Undo(0, 1), + Invert(0, 1), AssertOpsJson(0, r#"[{"insert":"123"}]"#), ]; OpTester::new().run_script(ops); @@ -119,7 +119,7 @@ fn delta_invert_attribute_delta_with_attribute_delta() { 1, r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"}}]"#, ), - Undo(0, 1), + Invert(0, 1), AssertOpsJson( 0, r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#, diff --git a/rust-lib/flowy-ot/tests/undo_redo_test.rs b/rust-lib/flowy-ot/tests/undo_redo_test.rs new file mode 100644 index 0000000000..e69de29bb2