瀏覽代碼

config undo redo for document

appflowy 3 年之前
父節點
當前提交
760c191758

+ 48 - 14
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<UndoResult, OTError> {
+        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<UndoResult, OTError> {
+        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) {

+ 35 - 10
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<Revision>,
-    redos: Vec<Revision>,
+    undos: Vec<Delta>,
+    redos: Vec<Delta>,
 }
 
 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<Revision> {
+    pub fn add_redo(&mut self, delta: Delta) { self.redos.push(delta); }
+
+    pub fn undo(&mut self) -> Option<Delta> {
         if !self.can_undo() {
             return None;
         }
+        let delta = self.undos.pop().unwrap();
+        Some(delta)
+    }
 
-        let revision = self.undos.pop().unwrap();
+    pub fn redo(&mut self) -> Option<Delta> {
+        if !self.can_redo() {
+            return None;
+        }
 
-        Some(revision)
+        let delta = self.redos.pop().unwrap();
+        Some(delta)
     }
-
-    pub fn redo(&mut self) -> Option<Revision> { None }
 }

+ 7 - 7
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<Self, OTError> {
         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<String, OTError> {
         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();

+ 49 - 1
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<String>,
+}
+
+impl ErrorBuilder {
+    pub fn new(code: OTErrorCode) -> Self { ErrorBuilder { code, msg: None } }
+
+    pub fn msg<T>(mut self, msg: T) -> Self
+    where
+        T: Into<String>,
+    {
+        self.msg = Some(msg.into());
+        self
+    }
+
+    pub fn error<T>(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()))
+    }
+}

+ 18 - 8
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();
 

+ 6 - 6
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"}}]"#,

+ 0 - 0
rust-lib/flowy-ot/tests/undo_redo_test.rs