فهرست منبع

add redo test

appflowy 3 سال پیش
والد
کامیت
c667ee0f36

+ 15 - 10
rust-lib/flowy-ot/src/client/document.rs

@@ -72,10 +72,10 @@ impl Document {
         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;
+                let composed_delta = self.data.compose(&undo_delta)?;
+                let redo_delta = undo_delta.invert(&self.data);
+                let result = UndoResult::success(composed_delta.target_len as u64);
+                self.data = composed_delta;
                 self.history.add_redo(redo_delta);
 
                 Ok(result)
@@ -89,9 +89,9 @@ impl Document {
             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);
+                let undo_delta = redo_delta.invert(&self.data);
                 self.data = new_delta;
-                self.history.add_undo(redo_delta);
+                self.history.add_undo(undo_delta);
                 Ok(result)
             },
         }
@@ -104,6 +104,8 @@ impl Document {
 
     pub fn to_json(&self) -> String { self.data.to_json() }
 
+    pub fn to_string(&self) -> String { self.data.apply("").unwrap() }
+
     pub fn data(&self) -> &Delta { &self.data }
 
     pub fn set_data(&mut self, data: Delta) { self.data = data; }
@@ -135,12 +137,15 @@ impl Document {
             });
         }
 
-        let new_data = self.data.compose(&new_delta)?;
-        let undo_delta = new_delta.invert_delta(&self.data);
-        self.rev_id_counter += 1;
+        // c = a.compose(b)
+        // d = b.invert(a)
+        // a = c.compose(d)
+        let composed_delta = self.data.compose(&new_delta)?;
+        let undo_delta = new_delta.invert(&self.data);
 
+        self.rev_id_counter += 1;
         self.history.record(undo_delta);
-        self.data = new_data;
+        self.data = composed_delta;
         Ok(())
     }
 

+ 24 - 13
rust-lib/flowy-ot/src/core/delta.rs

@@ -3,7 +3,12 @@ use crate::{
     errors::{ErrorBuilder, OTError, OTErrorCode},
 };
 use bytecount::num_chars;
-use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr};
+use std::{
+    cmp::{max, min, Ordering},
+    fmt,
+    iter::FromIterator,
+    str::FromStr,
+};
 
 #[derive(Clone, Debug, PartialEq)]
 pub struct Delta {
@@ -444,7 +449,7 @@ impl Delta {
 
     /// Computes the inverse of an operation. The inverse of an operation is the
     /// operation that reverts the effects of the operation
-    pub fn invert(&self, s: &str) -> Self {
+    pub fn invert_str(&self, s: &str) -> Self {
         let mut inverted = Delta::default();
         let chars = &mut s.chars();
         for op in &self.ops {
@@ -472,7 +477,7 @@ impl Delta {
         inverted
     }
 
-    pub fn invert_delta(&self, other: &Delta) -> Delta {
+    pub fn invert(&self, other: &Delta) -> Delta {
         let mut inverted = Delta::default();
         if other.is_empty() {
             return inverted;
@@ -517,8 +522,8 @@ impl Delta {
                 },
                 Operation::Retain(_) => {
                     match op.has_attribute() {
-                        true => inverted.retain(len as u64, op.get_attributes()),
-                        false => inverted_from_other(&mut inverted, op, index, index + len),
+                        true => inverted_from_other(&mut inverted, op, index, index + len),
+                        false => inverted.retain(len as u64, op.get_attributes()),
                     }
                     index += len;
                 },
@@ -547,20 +552,26 @@ impl Delta {
         let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
         let mut offset: usize = 0;
         let mut ops_iter = self.ops.iter();
-        let mut op = ops_iter.next();
+        let mut next_op = ops_iter.next();
 
-        while offset < interval.end && op.is_some() {
-            if let Some(op) = op {
+        if offset < interval.end && next_op.is_some() {
+            let op = next_op.take().unwrap();
+            let len = op.length() as usize;
+            // log::info!("{:?}", op);
+            while offset < len {
                 if offset < interval.start {
-                    offset += op.length() as usize;
+                    offset += min(interval.start, op.length() as usize);
                 } else {
-                    ops.push(op.clone());
-                    offset += op.length() as usize;
+                    if interval.contains(offset) {
+                        ops.push(op.shrink_to_interval(interval));
+                        offset += max(op.length() as usize, interval.size());
+                    } else {
+                        break;
+                    }
                 }
             }
-            op = ops_iter.next();
+            next_op = ops_iter.next();
         }
-
         ops
     }
 

+ 29 - 4
rust-lib/flowy-ot/src/core/operation/operation.rs

@@ -1,6 +1,7 @@
-use crate::core::{Attributes, OpBuilder};
+use crate::core::{Attributes, Interval, OpBuilder};
 use bytecount::num_chars;
 use std::{
+    cmp::min,
     fmt,
     ops::{Deref, DerefMut},
     str::Chars,
@@ -52,9 +53,9 @@ impl Operation {
 
     pub fn has_attribute(&self) -> bool {
         match self.get_attributes() {
-            Attributes::Follow => true,
-            Attributes::Custom(_) => false,
-            Attributes::Empty => true,
+            Attributes::Follow => false,
+            Attributes::Custom(data) => data.is_empty(),
+            Attributes::Empty => false,
         }
     }
 
@@ -67,6 +68,30 @@ impl Operation {
     }
 
     pub fn is_empty(&self) -> bool { self.length() == 0 }
+
+    pub fn shrink_to_interval(&self, interval: Interval) -> Operation {
+        match self {
+            Operation::Delete(_) => Operation::Delete(interval.size() as u64),
+            Operation::Retain(retain) => {
+                //
+                OpBuilder::retain(interval.size() as u64)
+                    .attributes(retain.attributes.clone())
+                    .build()
+            },
+            Operation::Insert(insert) => {
+                // debug_assert!(insert.s.len() <= interval.size());
+                if interval.start > insert.s.len() {
+                    return OpBuilder::insert("").build();
+                }
+
+                let end = min(interval.end, insert.s.len());
+                let s = &insert.s[interval.start..end];
+                OpBuilder::insert(s)
+                    .attributes(insert.attributes.clone())
+                    .build()
+            },
+        }
+    }
 }
 
 impl fmt::Display for Operation {

+ 8 - 1
rust-lib/flowy-ot/tests/helper/mod.rs

@@ -35,6 +35,9 @@ pub enum TestOp {
     #[display(fmt = "Redo")]
     Redo(usize),
 
+    #[display(fmt = "AssertStr")]
+    AssertStr(usize, &'static str),
+
     #[display(fmt = "AssertOpsJson")]
     AssertOpsJson(usize, &'static str),
 }
@@ -107,7 +110,7 @@ impl OpTester {
                 log::debug!("b: {}", delta_b.to_json());
 
                 let (_, b_prime) = delta_a.transform(delta_b).unwrap();
-                let undo = b_prime.invert_delta(&delta_a);
+                let undo = b_prime.invert(&delta_a);
 
                 let new_delta = delta_a.compose(&b_prime).unwrap();
                 log::debug!("new delta: {}", new_delta.to_json());
@@ -127,6 +130,10 @@ impl OpTester {
             TestOp::Redo(delta_i) => {
                 self.documents[*delta_i].redo().unwrap();
             },
+            TestOp::AssertStr(delta_i, expected) => {
+                assert_eq!(&self.documents[*delta_i].to_string(), expected);
+            },
+
             TestOp::AssertOpsJson(delta_i, expected) => {
                 let delta_i_json = self.documents[*delta_i].to_json();
 

+ 1 - 1
rust-lib/flowy-ot/tests/invert_test.rs

@@ -10,7 +10,7 @@ fn delta_invert_no_attribute_delta() {
     let mut change = Delta::default();
     change.add(OpBuilder::retain(3).build());
     change.add(OpBuilder::insert("456").build());
-    let undo = change.invert_delta(&delta);
+    let undo = change.invert(&delta);
 
     let new_delta = delta.compose(&change).unwrap();
     let delta_after_undo = new_delta.compose(&undo).unwrap();

+ 1 - 1
rust-lib/flowy-ot/tests/op_test.rs

@@ -82,7 +82,7 @@ fn invert() {
         let mut rng = Rng::default();
         let s = rng.gen_string(50);
         let delta_a = rng.gen_delta(&s);
-        let delta_b = delta_a.invert(&s);
+        let delta_b = delta_a.invert_str(&s);
         assert_eq!(delta_a.base_len, delta_b.target_len);
         assert_eq!(delta_a.target_len, delta_b.base_len);
         assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);

+ 32 - 2
rust-lib/flowy-ot/tests/undo_redo_test.rs

@@ -3,13 +3,13 @@ pub mod helper;
 use crate::helper::{TestOp::*, *};
 
 #[test]
-fn delta_undo_insert_text() {
+fn delta_undo_insert() {
     let ops = vec![Insert(0, "123", 0), Undo(0), AssertOpsJson(0, r#"[]"#)];
     OpTester::new().run_script(ops);
 }
 
 #[test]
-fn delta_undo_insert_text2() {
+fn delta_undo_insert2() {
     let ops = vec![
         Insert(0, "123", 0),
         Insert(0, "456", 0),
@@ -20,3 +20,33 @@ fn delta_undo_insert_text2() {
     ];
     OpTester::new().run_script(ops);
 }
+
+#[test]
+fn delta_redo_insert() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        Undo(0),
+        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        Redo(0),
+        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_redo_insert2() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(0, "456", 3),
+        AssertStr(0, "123456\n"),
+        AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#),
+        Undo(0),
+        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        Redo(0),
+        AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#),
+        Undo(0),
+        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+    ];
+    OpTester::new().run_script(ops);
+}