浏览代码

config ot invert detal

appflowy 3 年之前
父节点
当前提交
80a94880fc

+ 41 - 27
rust-lib/flowy-ot/src/core/attributes.rs

@@ -27,6 +27,7 @@ impl Attributes {
         }
     }
     // remove attribute if the value is PLAIN
+    // { "k": PLAIN } -> {}
     pub fn remove_plain(&mut self) {
         match self {
             Attributes::Follow => {},
@@ -36,6 +37,14 @@ impl Attributes {
             Attributes::Empty => {},
         }
     }
+
+    pub fn get_attributes_data(&self) -> Option<AttributesData> {
+        match self {
+            Attributes::Follow => None,
+            Attributes::Custom(data) => Some(data.clone()),
+            Attributes::Empty => None,
+        }
+    }
 }
 
 impl std::default::Default for Attributes {
@@ -131,7 +140,7 @@ impl AttrsBuilder {
     pub fn build(self) -> Attributes { Attributes::Custom(self.inner) }
 }
 
-pub fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
+pub(crate) fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
     match operation {
         None => None,
         Some(operation) => Some(operation.get_attributes()),
@@ -196,6 +205,37 @@ pub fn transform_attributes(
     }
 }
 
+pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes {
+    let attr = attr.get_attributes_data();
+    let base = base.get_attributes_data();
+
+    if attr.is_none() && base.is_none() {
+        return Attributes::Empty;
+    }
+
+    let attr = attr.unwrap_or(AttributesData::new());
+    let base = base.unwrap_or(AttributesData::new());
+
+    let base_inverted = base
+        .iter()
+        .fold(AttributesData::new(), |mut attributes, (k, v)| {
+            if base.get(k) != attr.get(k) && attr.contains_key(k) {
+                attributes.insert(k.clone(), v.clone());
+            }
+            attributes
+        });
+
+    let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, _)| {
+        if base.get(k) != attr.get(k) && !base.contains_key(k) {
+            // attributes.insert(k.clone(), "".to_owned());
+            attributes.remove(k);
+        }
+        attributes
+    });
+
+    return Attributes::Custom(inverted);
+}
+
 fn transform_attribute_data(left: AttributesData, right: AttributesData) -> AttributesData {
     let result = right
         .iter()
@@ -207,29 +247,3 @@ fn transform_attribute_data(left: AttributesData, right: AttributesData) -> Attr
         });
     result
 }
-
-// pub fn invert_attributes(
-//     attr: Option<AttributesData>,
-//     base: Option<AttributesData>,
-// ) -> AttributesData {
-//     let attr = attr.unwrap_or(AttributesData::new());
-//     let base = base.unwrap_or(AttributesData::new());
-//
-//     let base_inverted = base
-//         .iter()
-//         .fold(AttributesData::new(), |mut attributes, (k, v)| {
-//             if base.get(k) != attr.get(k) && attr.contains_key(k) {
-//                 attributes.insert(k.clone(), v.clone());
-//             }
-//             attributes
-//         });
-//
-//     let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, _)| {
-//         if base.get(k) != attr.get(k) && !base.contains_key(k) {
-//             attributes.insert(k.clone(), "".to_owned());
-//         }
-//         attributes
-//     });
-//
-//     return inverted;
-// }

+ 142 - 94
rust-lib/flowy-ot/src/core/delta.rs

@@ -1,5 +1,5 @@
 use crate::{
-    core::{attributes::*, operation::*},
+    core::{attributes::*, operation::*, Interval},
     errors::OTError,
 };
 use bytecount::num_chars;
@@ -70,7 +70,7 @@ impl Delta {
         match op {
             Operation::Delete(i) => self.delete(i),
             Operation::Insert(i) => self.insert(&i.s, i.attributes),
-            Operation::Retain(r) => self.retain(r.num, r.attributes),
+            Operation::Retain(r) => self.retain(r.n, r.attributes),
         }
     }
 
@@ -95,11 +95,11 @@ impl Delta {
         let new_last = match self.ops.as_mut_slice() {
             [.., Operation::Insert(insert)] => {
                 //
-                merge_insert_or_new_op(insert, s, attrs)
+                insert.merge_or_new_op(s, attrs)
             },
             [.., Operation::Insert(pre_insert), Operation::Delete(_)] => {
                 //
-                merge_insert_or_new_op(pre_insert, s, attrs)
+                pre_insert.merge_or_new_op(s, attrs)
             },
             [.., op_last @ Operation::Delete(_)] => {
                 let new_last = op_last.clone();
@@ -123,9 +123,8 @@ impl Delta {
         self.target_len += n as usize;
 
         if let Some(Operation::Retain(retain)) = self.ops.last_mut() {
-            match merge_retain_or_new_op(retain, n, attrs) {
-                None => {},
-                Some(new_op) => self.ops.push(new_op),
+            if let Some(new_op) = retain.merge_or_new_op(n, attrs) {
+                self.ops.push(new_op);
             }
         } else {
             self.ops
@@ -175,24 +174,24 @@ impl Delta {
                     let composed_attrs = compose_attributes(&next_op1, &next_op2);
                     log::debug!(
                         "[retain:{} - retain:{}]: {:?}",
-                        retain.num,
-                        o_retain.num,
+                        retain.n,
+                        o_retain.n,
                         composed_attrs
                     );
                     match retain.cmp(&o_retain) {
                         Ordering::Less => {
-                            new_delta.retain(retain.num, composed_attrs);
-                            next_op2 = Some(OpBuilder::retain(o_retain.num - retain.num).build());
+                            new_delta.retain(retain.n, composed_attrs);
+                            next_op2 = Some(OpBuilder::retain(o_retain.n - retain.n).build());
                             next_op1 = ops1.next();
                         },
                         std::cmp::Ordering::Equal => {
-                            new_delta.retain(retain.num, composed_attrs);
+                            new_delta.retain(retain.n, composed_attrs);
                             next_op1 = ops1.next();
                             next_op2 = ops2.next();
                         },
                         std::cmp::Ordering::Greater => {
-                            new_delta.retain(o_retain.num, composed_attrs);
-                            next_op1 = Some(OpBuilder::retain(retain.num - o_retain.num).build());
+                            new_delta.retain(o_retain.n, composed_attrs);
+                            next_op1 = Some(OpBuilder::retain(retain.n - o_retain.n).build());
                             next_op2 = ops2.next();
                         },
                     }
@@ -227,14 +226,14 @@ impl Delta {
                     log::debug!(
                         "[insert:{} - retain:{}]: {:?}",
                         insert.s,
-                        o_retain.num,
+                        o_retain.n,
                         composed_attrs
                     );
                     match (insert.num_chars()).cmp(o_retain) {
                         Ordering::Less => {
                             new_delta.insert(&insert.s, composed_attrs.clone());
                             next_op2 = Some(
-                                OpBuilder::retain(o_retain.num - insert.num_chars())
+                                OpBuilder::retain(o_retain.n - insert.num_chars())
                                     .attributes(composed_attrs.clone())
                                     .build(),
                             );
@@ -248,7 +247,7 @@ impl Delta {
                         Ordering::Greater => {
                             let chars = &mut insert.chars();
                             new_delta.insert(
-                                &chars.take(o_retain.num as usize).collect::<String>(),
+                                &chars.take(o_retain.n as usize).collect::<String>(),
                                 composed_attrs,
                             );
                             next_op1 = Some(
@@ -263,8 +262,8 @@ impl Delta {
                 (Some(Operation::Retain(retain)), Some(Operation::Delete(o_num))) => {
                     match retain.cmp(&o_num) {
                         Ordering::Less => {
-                            new_delta.delete(retain.num);
-                            next_op2 = Some(OpBuilder::delete(*o_num - retain.num).build());
+                            new_delta.delete(retain.n);
+                            next_op2 = Some(OpBuilder::delete(*o_num - retain.n).build());
                             next_op1 = ops1.next();
                         },
                         Ordering::Equal => {
@@ -274,7 +273,7 @@ impl Delta {
                         },
                         Ordering::Greater => {
                             new_delta.delete(*o_num);
-                            next_op1 = Some(OpBuilder::retain(retain.num - *o_num).build());
+                            next_op1 = Some(OpBuilder::retain(retain.n - *o_num).build());
                             next_op2 = ops2.next();
                         },
                     }
@@ -331,21 +330,21 @@ impl Delta {
                     let composed_attrs = transform_attributes(&next_op1, &next_op2, true);
                     match retain.cmp(&o_retain) {
                         Ordering::Less => {
-                            a_prime.retain(retain.num, composed_attrs.clone());
-                            b_prime.retain(retain.num, composed_attrs.clone());
-                            next_op2 = Some(OpBuilder::retain(o_retain.num - retain.num).build());
+                            a_prime.retain(retain.n, composed_attrs.clone());
+                            b_prime.retain(retain.n, composed_attrs.clone());
+                            next_op2 = Some(OpBuilder::retain(o_retain.n - retain.n).build());
                             next_op1 = ops1.next();
                         },
                         Ordering::Equal => {
-                            a_prime.retain(retain.num, composed_attrs.clone());
-                            b_prime.retain(retain.num, composed_attrs.clone());
+                            a_prime.retain(retain.n, composed_attrs.clone());
+                            b_prime.retain(retain.n, composed_attrs.clone());
                             next_op1 = ops1.next();
                             next_op2 = ops2.next();
                         },
                         Ordering::Greater => {
-                            a_prime.retain(o_retain.num, composed_attrs.clone());
-                            b_prime.retain(o_retain.num, composed_attrs.clone());
-                            next_op1 = Some(OpBuilder::retain(retain.num - o_retain.num).build());
+                            a_prime.retain(o_retain.n, composed_attrs.clone());
+                            b_prime.retain(o_retain.n, composed_attrs.clone());
+                            next_op1 = Some(OpBuilder::retain(retain.n - o_retain.n).build());
                             next_op2 = ops2.next();
                         },
                     };
@@ -368,7 +367,7 @@ impl Delta {
                     match i.cmp(&o_retain) {
                         Ordering::Less => {
                             a_prime.delete(*i);
-                            next_op2 = Some(OpBuilder::retain(o_retain.num - *i).build());
+                            next_op2 = Some(OpBuilder::retain(o_retain.n - *i).build());
                             next_op1 = ops1.next();
                         },
                         Ordering::Equal => {
@@ -377,8 +376,8 @@ impl Delta {
                             next_op2 = ops2.next();
                         },
                         Ordering::Greater => {
-                            a_prime.delete(o_retain.num);
-                            next_op1 = Some(OpBuilder::delete(*i - o_retain.num).build());
+                            a_prime.delete(o_retain.n);
+                            next_op1 = Some(OpBuilder::delete(*i - o_retain.n).build());
                             next_op2 = ops2.next();
                         },
                     };
@@ -386,18 +385,18 @@ impl Delta {
                 (Some(Operation::Retain(retain)), Some(Operation::Delete(j))) => {
                     match retain.cmp(&j) {
                         Ordering::Less => {
-                            b_prime.delete(retain.num);
-                            next_op2 = Some(OpBuilder::delete(*j - retain.num).build());
+                            b_prime.delete(retain.n);
+                            next_op2 = Some(OpBuilder::delete(*j - retain.n).build());
                             next_op1 = ops1.next();
                         },
                         Ordering::Equal => {
-                            b_prime.delete(retain.num);
+                            b_prime.delete(retain.n);
                             next_op1 = ops1.next();
                             next_op2 = ops2.next();
                         },
                         Ordering::Greater => {
                             b_prime.delete(*j);
-                            next_op1 = Some(OpBuilder::retain(retain.num - *j).build());
+                            next_op1 = Some(OpBuilder::retain(retain.n - *j).build());
                             next_op2 = ops2.next();
                         },
                     };
@@ -423,7 +422,7 @@ impl Delta {
         for op in &self.ops {
             match &op {
                 Operation::Retain(retain) => {
-                    for c in chars.take(retain.num as usize) {
+                    for c in chars.take(retain.n as usize) {
                         new_s.push(c);
                     }
                 },
@@ -448,8 +447,11 @@ impl Delta {
         for op in &self.ops {
             match &op {
                 Operation::Retain(retain) => {
-                    inverted.retain(retain.num, Attributes::Follow);
-                    for _ in 0..retain.num {
+                    inverted.retain(retain.n, Attributes::Follow);
+
+                    // TODO: use advance_by instead, but it's unstable now
+                    // chars.advance_by(retain.num)
+                    for _ in 0..retain.n {
                         chars.next();
                     }
                 },
@@ -467,6 +469,54 @@ impl Delta {
         inverted
     }
 
+    pub fn invert_delta(&self, other: &Delta) -> Delta {
+        let mut inverted = Delta::default();
+        if other.is_empty() {
+            return inverted;
+        }
+
+        let a = |inverted: &mut Delta, op: &Operation, index: usize, op_len: usize| {
+            let ops = other.ops_in_interval(Interval::new(index, op_len));
+            ops.into_iter().for_each(|other_op| match op {
+                Operation::Delete(_) => {
+                    inverted.add(other_op);
+                },
+                Operation::Retain(_) => {},
+                Operation::Insert(_) => {
+                    if !op.is_plain() {
+                        let inverted_attrs =
+                            invert_attributes(op.get_attributes(), other_op.get_attributes());
+                        inverted.retain(other_op.length(), inverted_attrs);
+                    }
+                },
+            });
+        };
+
+        let mut index = 0;
+        for op in &self.ops {
+            let op_len: usize = op.length() as usize;
+            match op {
+                Operation::Delete(_) => {
+                    a(&mut inverted, op, index, op_len);
+                    index += op_len;
+                },
+                Operation::Retain(_) => {
+                    if op.is_plain() {
+                        inverted.retain(op_len as u64, Attributes::Empty);
+                    } else {
+                        a(&mut inverted, op, index, op_len as usize);
+                    }
+                    index += op_len;
+                },
+                Operation::Insert(insert) => {
+                    inverted.delete(op_len as u64);
+                },
+            }
+        }
+
+        inverted
+    }
+
     /// Checks if this operation has no effect.
     #[inline]
     pub fn is_noop(&self) -> bool {
@@ -477,66 +527,64 @@ impl Delta {
         }
     }
 
-    /// Returns the length of a string these operations can be applied to
-    #[inline]
-    pub fn base_len(&self) -> usize { self.base_len }
-
-    /// Returns the length of the resulting string after the operations have
-    /// been applied.
-    #[inline]
-    pub fn target_len(&self) -> usize { self.target_len }
-
-    /// Returns the wrapped sequence of operations.
-    #[inline]
-    pub fn ops(&self) -> &[Operation] { &self.ops }
-
     pub fn is_empty(&self) -> bool { self.ops.is_empty() }
-}
 
-fn merge_insert_or_new_op(
-    insert: &mut Insert,
-    s: &str,
-    attributes: Attributes,
-) -> Option<Operation> {
-    match &attributes {
-        Attributes::Follow => {
-            insert.s += s;
-            return None;
-        },
-        Attributes::Custom(_) | Attributes::Empty => {
-            if insert.attributes == attributes {
-                insert.s += s;
-                None
-            } else {
-                Some(OpBuilder::insert(s).attributes(attributes).build())
+    pub fn ops_in_interval(&self, interval: Interval) -> Vec<Operation> {
+        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();
+
+        while offset < interval.end && op.is_some() {
+            if let Some(op) = op {
+                if offset < interval.start {
+                    offset += op.length() as usize;
+                } else {
+                    ops.push(op.clone());
+                    offset += op.length() as usize;
+                }
             }
-        },
+            op = ops_iter.next();
+        }
+
+        ops
     }
-}
 
-fn merge_retain_or_new_op(
-    retain: &mut Retain,
-    n: u64,
-    attributes: Attributes,
-) -> Option<Operation> {
-    log::debug!(
-        "merge_retain_or_new_op: {:?}, {:?}",
-        retain.attributes,
-        attributes
-    );
-
-    match &attributes {
-        Attributes::Follow => {
-            retain.num += n;
-            None
-        },
-        Attributes::Custom(_) | Attributes::Empty => {
-            if retain.attributes == attributes {
-                retain.num += n;
-                None
-            } else {
-                Some(OpBuilder::retain(n).attributes(attributes).build())
-            }
-        },
+    pub fn attributes_in_interval(&self, interval: Interval) -> Attributes {
+        let mut attributes_data = AttributesData::new();
+        let mut offset: usize = 0;
+
+        self.ops.iter().for_each(|op| match op {
+            Operation::Delete(_n) => {},
+            Operation::Retain(_retain) => {
+                unimplemented!()
+                // if interval.contains(retain.n as usize) {
+                //     match &retain.attributes {
+                //         Attributes::Follow => {},
+                //         Attributes::Custom(data) => {
+                //             attributes_data.extend(data.clone());
+                //         },
+                //         Attributes::Empty => {},
+                //     }
+                // }
+            },
+            Operation::Insert(insert) => match &insert.attributes {
+                Attributes::Follow => {},
+                Attributes::Custom(data) => {
+                    let end = insert.num_chars() as usize;
+                    if interval.contains_range(offset, offset + end) {
+                        attributes_data.extend(data.clone());
+                    }
+                    offset += end;
+                },
+                Attributes::Empty => {},
+            },
+        });
+
+        if attributes_data.is_plain() {
+            Attributes::Empty
+        } else {
+            Attributes::Custom(attributes_data)
+        }
     }
 }

+ 4 - 0
rust-lib/flowy-ot/src/core/interval.rs

@@ -33,6 +33,10 @@ impl Interval {
 
     pub fn contains(&self, val: usize) -> bool { self.start <= val && val < self.end }
 
+    pub fn contains_range(&self, start: usize, end: usize) -> bool {
+        !self.intersect(Interval::new(start, end)).is_empty()
+    }
+
     pub fn is_after(&self, val: usize) -> bool { self.start > val }
 
     pub fn is_empty(&self) -> bool { self.end <= self.start }

+ 49 - 6
rust-lib/flowy-ot/src/core/operation.rs

@@ -73,10 +73,11 @@ impl Operation {
     pub fn length(&self) -> u64 {
         match self {
             Operation::Delete(n) => *n,
-            Operation::Retain(r) => r.num,
+            Operation::Retain(r) => r.n,
             Operation::Insert(i) => i.num_chars(),
         }
     }
+
     pub fn is_empty(&self) -> bool { self.length() == 0 }
 }
 
@@ -89,7 +90,7 @@ impl fmt::Display for Operation {
             Operation::Retain(r) => {
                 f.write_fmt(format_args!(
                     "retain: {}, attributes: {}",
-                    r.num, r.attributes
+                    r.n, r.attributes
                 ))?;
             },
             Operation::Insert(i) => {
@@ -141,15 +142,40 @@ impl OpBuilder {
 #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
 pub struct Retain {
     #[serde(rename(serialize = "retain", deserialize = "retain"))]
-    pub num: u64,
+    pub n: u64,
     #[serde(skip_serializing_if = "is_empty")]
     pub attributes: Attributes,
 }
 
+impl Retain {
+    pub fn merge_or_new_op(&mut self, n: u64, attributes: Attributes) -> Option<Operation> {
+        log::debug!(
+            "merge_retain_or_new_op: {:?}, {:?}",
+            self.attributes,
+            attributes
+        );
+
+        match &attributes {
+            Attributes::Follow => {
+                self.n += n;
+                None
+            },
+            Attributes::Custom(_) | Attributes::Empty => {
+                if self.attributes == attributes {
+                    self.n += n;
+                    None
+                } else {
+                    Some(OpBuilder::retain(n).attributes(attributes).build())
+                }
+            },
+        }
+    }
+}
+
 impl std::convert::From<u64> for Retain {
     fn from(n: u64) -> Self {
         Retain {
-            num: n,
+            n,
             attributes: Attributes::default(),
         }
     }
@@ -158,11 +184,11 @@ impl std::convert::From<u64> for Retain {
 impl Deref for Retain {
     type Target = u64;
 
-    fn deref(&self) -> &Self::Target { &self.num }
+    fn deref(&self) -> &Self::Target { &self.n }
 }
 
 impl DerefMut for Retain {
-    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.num }
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.n }
 }
 
 #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
@@ -180,6 +206,23 @@ impl Insert {
     pub fn chars(&self) -> Chars<'_> { self.s.chars() }
 
     pub fn num_chars(&self) -> u64 { num_chars(self.s.as_bytes()) as _ }
+
+    pub fn merge_or_new_op(&mut self, s: &str, attributes: Attributes) -> Option<Operation> {
+        match &attributes {
+            Attributes::Follow => {
+                self.s += s;
+                return None;
+            },
+            Attributes::Custom(_) | Attributes::Empty => {
+                if self.attributes == attributes {
+                    self.s += s;
+                    None
+                } else {
+                    Some(OpBuilder::insert(s).attributes(attributes).build())
+                }
+            },
+        }
+    }
 }
 
 impl std::convert::From<String> for Insert {

+ 23 - 23
rust-lib/flowy-ot/tests/attribute_test.rs

@@ -1,6 +1,6 @@
 pub mod helper;
 
-use crate::helper::{MergeTestOp::*, *};
+use crate::helper::{TestOp::*, *};
 use flowy_ot::core::Interval;
 
 #[test]
@@ -10,7 +10,7 @@ fn delta_insert_text() {
         Insert(0, "456", 3),
         AssertOpsJson(0, r#"[{"insert":"123456"}]"#),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -20,7 +20,7 @@ fn delta_insert_text_at_head() {
         Insert(0, "456", 0),
         AssertOpsJson(0, r#"[{"insert":"456123"}]"#),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -30,7 +30,7 @@ fn delta_insert_text_at_middle() {
         Insert(0, "456", 1),
         AssertOpsJson(0, r#"[{"insert":"145623"}]"#),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -49,7 +49,7 @@ fn delta_insert_text_with_attr() {
             r#"[{"insert":"1abc2","attributes":{"bold":"true"}},{"insert":"345"}]"#,
         ),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -61,7 +61,7 @@ fn delta_add_bold_and_invert_all() {
         Bold(0, Interval::new(0, 3), false),
         AssertOpsJson(0, r#"[{"insert":"123"}]"#),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -76,7 +76,7 @@ fn delta_add_bold_and_invert_partial_suffix() {
             r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#,
         ),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -93,7 +93,7 @@ fn delta_add_bold_and_invert_partial_suffix2() {
         Bold(0, Interval::new(2, 4), true),
         AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -108,7 +108,7 @@ fn delta_add_bold_and_invert_partial_prefix() {
             r#"[{"insert":"12"},{"insert":"34","attributes":{"bold":"true"}}]"#,
         ),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -126,14 +126,14 @@ fn delta_add_bold_consecutive() {
             r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#,
         ),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
 #[should_panic]
 fn delta_add_bold_empty_str() {
     let ops = vec![Bold(0, Interval::new(0, 4), true)];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -157,7 +157,7 @@ fn delta_add_bold_italic() {
             r#"[{"insert":"1234","attributes":{"italic":"true","bold":"true"}},{"insert":"56"},{"insert":"78","attributes":{"bold":"true","italic":"true"}}]"#,
         ),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -178,7 +178,7 @@ fn delta_add_bold_italic2() {
         ),
     ];
 
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -203,7 +203,7 @@ fn delta_add_bold_italic3() {
         ),
     ];
 
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -225,7 +225,7 @@ fn delta_add_bold_italic_delete() {
         ),
     ];
 
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -236,7 +236,7 @@ fn delta_merge_inserted_text_with_same_attribute() {
         InsertBold(0, "456", Interval::new(3, 6)),
         AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -251,7 +251,7 @@ fn delta_compose_attr_delta_with_attr_delta_test() {
         AssertOpsJson(1, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#),
     ];
 
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -278,7 +278,7 @@ fn delta_compose_attr_delta_with_attr_delta_test2() {
         ),
     ];
 
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -293,7 +293,7 @@ fn delta_compose_attr_delta_with_no_attr_delta_test() {
         AssertOpsJson(0, expected),
         AssertOpsJson(1, expected),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -305,7 +305,7 @@ fn delta_delete_heading() {
         AssertOpsJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#),
     ];
 
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -317,7 +317,7 @@ fn delta_delete_trailing() {
         AssertOpsJson(0, r#"[{"insert":"12345","attributes":{"bold":"true"}}]"#),
     ];
 
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -331,7 +331,7 @@ fn delta_delete_middle() {
         AssertOpsJson(0, r#"[{"insert":"34","attributes":{"bold":"true"}}]"#),
     ];
 
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]
@@ -343,5 +343,5 @@ fn delta_delete_all() {
         AssertOpsJson(0, r#"[]"#),
     ];
 
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }

+ 26 - 64
rust-lib/flowy-ot/tests/helper/mod.rs

@@ -3,7 +3,7 @@ use rand::{prelude::*, Rng as WrappedRng};
 use std::sync::Once;
 
 #[derive(Clone, Debug)]
-pub enum MergeTestOp {
+pub enum TestOp {
     Insert(usize, &'static str, usize),
     // delta_i, s, start, length,
     InsertBold(usize, &'static str, Interval),
@@ -16,11 +16,11 @@ pub enum MergeTestOp {
     AssertOpsJson(usize, &'static str),
 }
 
-pub struct MergeTest {
+pub struct OpTester {
     deltas: Vec<Delta>,
 }
 
-impl MergeTest {
+impl OpTester {
     pub fn new() -> Self {
         static INIT: Once = Once::new();
         INIT.call_once(|| {
@@ -36,29 +36,29 @@ impl MergeTest {
         Self { deltas }
     }
 
-    pub fn run_op(&mut self, op: &MergeTestOp) {
+    pub fn run_op(&mut self, op: &TestOp) {
         match op {
-            MergeTestOp::Insert(delta_i, s, index) => {
+            TestOp::Insert(delta_i, s, index) => {
                 self.update_delta_with_insert(*delta_i, s, *index);
             },
-            MergeTestOp::Delete(delta_i, interval) => {
+            TestOp::Delete(delta_i, interval) => {
                 //
                 self.update_delta_with_delete(*delta_i, interval);
             },
-            MergeTestOp::InsertBold(delta_i, s, _interval) => {
+            TestOp::InsertBold(delta_i, s, _interval) => {
                 let attrs = AttrsBuilder::new().bold(true).build();
                 let delta = &mut self.deltas[*delta_i];
                 delta.insert(s, attrs);
             },
-            MergeTestOp::Bold(delta_i, interval, enable) => {
+            TestOp::Bold(delta_i, interval, enable) => {
                 let attrs = AttrsBuilder::new().bold(*enable).build();
                 self.update_delta_with_attribute(*delta_i, attrs, interval);
             },
-            MergeTestOp::Italic(delta_i, interval, enable) => {
+            TestOp::Italic(delta_i, interval, enable) => {
                 let attrs = AttrsBuilder::new().italic(*enable).build();
                 self.update_delta_with_attribute(*delta_i, attrs, interval);
             },
-            MergeTestOp::Transform(delta_a_i, delta_b_i) => {
+            TestOp::Transform(delta_a_i, delta_b_i) => {
                 let delta_a = &self.deltas[*delta_a_i];
                 let delta_b = &self.deltas[*delta_b_i];
 
@@ -70,12 +70,12 @@ impl MergeTest {
                 self.deltas[*delta_a_i] = new_delta_a;
                 self.deltas[*delta_b_i] = new_delta_b;
             },
-            MergeTestOp::AssertStr(delta_i, expected) => {
+            TestOp::AssertStr(delta_i, expected) => {
                 let s = self.deltas[*delta_i].apply("").unwrap();
                 assert_eq!(&s, expected);
             },
 
-            MergeTestOp::AssertOpsJson(delta_i, expected) => {
+            TestOp::AssertOpsJson(delta_i, expected) => {
                 let delta_i_json = serde_json::to_string(&self.deltas[*delta_i]).unwrap();
 
                 let expected_delta: Delta = serde_json::from_str(expected).unwrap();
@@ -90,12 +90,14 @@ impl MergeTest {
         }
     }
 
-    pub fn run_script(&mut self, script: Vec<MergeTestOp>) {
+    pub fn run_script(&mut self, script: Vec<TestOp>) {
         for (_i, op) in script.iter().enumerate() {
             self.run_op(op);
         }
     }
 
+    pub fn get_delta(&mut self, index: usize) -> &mut Delta { &mut self.deltas[index] }
+
     pub fn update_delta_with_insert(&mut self, delta_index: usize, s: &str, index: usize) {
         let old_delta = &mut self.deltas[delta_index];
         let target_interval = Interval::new(0, old_delta.target_len);
@@ -103,7 +105,7 @@ impl MergeTest {
             log::error!("{} out of bounds {}", index, target_interval);
         }
 
-        let mut attributes = attributes_in_delta(old_delta, &Interval::new(index, index + 1));
+        let mut attributes = old_delta.attributes_in_interval(Interval::new(index, index + 1));
         if attributes == Attributes::Empty {
             attributes = Attributes::Follow;
         }
@@ -123,8 +125,8 @@ impl MergeTest {
             .attributes(attributes)
             .build();
 
-        let attrs = attributes_in_delta(old_delta, &interval);
-        retain.extend_attributes(attrs);
+        let attributes = old_delta.attributes_in_interval(*interval);
+        retain.extend_attributes(attributes);
 
         let new_delta = new_delta_with_op(old_delta, retain, *interval);
         self.deltas[delta_index] = new_delta;
@@ -133,8 +135,8 @@ impl MergeTest {
     pub fn update_delta_with_delete(&mut self, delta_index: usize, interval: &Interval) {
         let old_delta = &self.deltas[delta_index];
         let mut delete = OpBuilder::delete(interval.size() as u64).build();
-        let attrs = attributes_in_delta(old_delta, &interval);
-        delete.extend_attributes(attrs);
+        let attributes = old_delta.attributes_in_interval(*interval);
+        delete.extend_attributes(attributes);
 
         let new_delta = new_delta_with_op(old_delta, delete, *interval);
         self.deltas[delta_index] = new_delta;
@@ -149,8 +151,8 @@ fn new_delta_with_op(delta: &Delta, op: Operation, interval: Interval) -> Delta
     if prefix.is_empty() == false && prefix != interval {
         let intervals = split_interval_with_delta(delta, &prefix);
         intervals.into_iter().for_each(|interval| {
-            let attrs = attributes_in_delta(delta, &interval);
-            new_delta.retain(interval.size() as u64, attrs);
+            let attributes = delta.attributes_in_interval(interval);
+            new_delta.retain(interval.size() as u64, attributes);
         });
     }
 
@@ -160,8 +162,8 @@ fn new_delta_with_op(delta: &Delta, op: Operation, interval: Interval) -> Delta
     if suffix.is_empty() == false {
         let intervals = split_interval_with_delta(delta, &suffix);
         intervals.into_iter().for_each(|interval| {
-            let attrs = attributes_in_delta(delta, &interval);
-            new_delta.retain(interval.size() as u64, attrs);
+            let attributes = delta.attributes_in_interval(interval);
+            new_delta.retain(interval.size() as u64, attributes);
         });
     }
 
@@ -200,47 +202,7 @@ pub fn target_length_split_with_interval(
 }
 
 pub fn debug_print_delta(delta: &Delta) {
-    log::debug!("😁 {}", serde_json::to_string(delta).unwrap());
-}
-
-pub fn attributes_in_delta(delta: &Delta, interval: &Interval) -> Attributes {
-    let mut attributes_data = AttributesData::new();
-    let mut offset: usize = 0;
-
-    delta.ops.iter().for_each(|op| match op {
-        Operation::Delete(_n) => {},
-        Operation::Retain(retain) => {
-            if interval.contains(retain.num as usize) {
-                match &retain.attributes {
-                    Attributes::Follow => {},
-                    Attributes::Custom(data) => {
-                        attributes_data.extend(data.clone());
-                    },
-                    Attributes::Empty => {},
-                }
-            }
-        },
-        Operation::Insert(insert) => match &insert.attributes {
-            Attributes::Follow => {},
-            Attributes::Custom(data) => {
-                let end = insert.num_chars() as usize;
-                if !interval
-                    .intersect(Interval::new(offset, offset + end))
-                    .is_empty()
-                {
-                    attributes_data.extend(data.clone());
-                }
-                offset += end;
-            },
-            Attributes::Empty => {},
-        },
-    });
-
-    if attributes_data.is_plain() {
-        Attributes::Empty
-    } else {
-        Attributes::Custom(attributes_data)
-    }
+    eprintln!("😁 {}", serde_json::to_string(delta).unwrap());
 }
 
 pub struct Rng(StdRng);
@@ -259,7 +221,7 @@ impl Rng {
     pub fn gen_delta(&mut self, s: &str) -> Delta {
         let mut delta = Delta::default();
         loop {
-            let left = s.chars().count() - delta.base_len();
+            let left = s.chars().count() - delta.base_len;
             if left == 0 {
                 break;
             }

+ 68 - 0
rust-lib/flowy-ot/tests/invert_test.rs

@@ -0,0 +1,68 @@
+pub mod helper;
+use crate::helper::{TestOp::*, *};
+use flowy_ot::core::{Delta, Interval, OpBuilder};
+
+#[test]
+fn delta_invert_delta_test() {
+    let mut delta = Delta::default();
+    delta.add(OpBuilder::insert("123").build());
+
+    let mut change = Delta::default();
+    change.add(OpBuilder::retain(3).build());
+    change.add(OpBuilder::insert("456").build());
+    let undo = change.invert_delta(&delta);
+
+    let new_delta = delta.compose(&change).unwrap();
+    let delta_after_undo = new_delta.compose(&undo).unwrap();
+
+    assert_eq!(delta_after_undo, delta);
+}
+
+#[test]
+fn delta_get_ops_in_interval_1() {
+    let mut delta = Delta::default();
+    let insert_a = OpBuilder::insert("123").build();
+    let insert_b = OpBuilder::insert("4").build();
+
+    delta.add(insert_a.clone());
+    delta.add(insert_b.clone());
+
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(0, 3)),
+        vec![delta.ops.last().unwrap().clone()]
+    );
+}
+
+#[test]
+fn delta_get_ops_in_interval_2() {
+    let mut delta = Delta::default();
+    let insert_a = OpBuilder::insert("123").build();
+    let insert_b = OpBuilder::insert("4").build();
+    let insert_c = OpBuilder::insert("5").build();
+    let retain_a = OpBuilder::retain(3).build();
+
+    delta.add(insert_a.clone());
+    delta.add(retain_a.clone());
+    delta.add(insert_b.clone());
+    delta.add(insert_c.clone());
+
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(0, 3)),
+        vec![insert_a.clone()]
+    );
+
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(0, 4)),
+        vec![insert_a.clone(), retain_a.clone()]
+    );
+
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(0, 7)),
+        vec![
+            insert_a.clone(),
+            retain_a.clone(),
+            // insert_b and insert_c will be merged into one. insert: "45"
+            delta.ops.last().unwrap().clone()
+        ]
+    );
+}

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

@@ -1,6 +1,6 @@
 pub mod helper;
 
-use crate::helper::MergeTestOp::*;
+use crate::helper::TestOp::*;
 use bytecount::num_chars;
 use flowy_ot::core::*;
 use helper::*;
@@ -196,7 +196,7 @@ fn transform2() {
         AssertOpsJson(0, r#"[{"insert":"123456"}]"#),
         AssertOpsJson(1, r#"[{"insert":"123456"}]"#),
     ];
-    MergeTest::new().run_script(ops);
+    OpTester::new().run_script(ops);
 }
 
 #[test]