فهرست منبع

config flowy ot attributes and add attribute test

appflowy 4 سال پیش
والد
کامیت
0b82336b6c

+ 4 - 4
app_flowy/packages/flowy_editor/example/pubspec.lock

@@ -7,7 +7,7 @@ packages:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.6.1"
+    version: "2.7.0"
   boolean_selector:
     dependency: transitive
     description:
@@ -28,7 +28,7 @@ packages:
       name: charcode
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.1"
   clock:
     dependency: transitive
     description:
@@ -141,7 +141,7 @@ packages:
       name: meta
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.3.0"
+    version: "1.7.0"
   nested:
     dependency: transitive
     description:
@@ -302,7 +302,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.0"
+    version: "0.4.1"
   tuple:
     dependency: transitive
     description:

+ 4 - 4
app_flowy/packages/flowy_editor/pubspec.lock

@@ -7,7 +7,7 @@ packages:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.6.1"
+    version: "2.7.0"
   boolean_selector:
     dependency: transitive
     description:
@@ -28,7 +28,7 @@ packages:
       name: charcode
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.1"
   clock:
     dependency: transitive
     description:
@@ -113,7 +113,7 @@ packages:
       name: meta
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.3.0"
+    version: "1.7.0"
   path:
     dependency: transitive
     description:
@@ -211,7 +211,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.0"
+    version: "0.4.1"
   tuple:
     dependency: "direct main"
     description:

+ 3 - 0
rust-lib/flowy-ot/Cargo.toml

@@ -9,7 +9,10 @@ edition = "2018"
 bytecount = "0.6.0"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = {version = "1.0"}
+log = "0.4"
 
 [dev-dependencies]
 criterion = "0.3"
 rand = "0.7.3"
+env_logger = "0.8.2"
+

+ 18 - 14
rust-lib/flowy-ot/src/attributes.rs

@@ -15,9 +15,11 @@ impl Attributes {
         }
     }
 
-    pub fn remove_empty_value(&mut self) { self.inner.retain(|_, v| v.is_empty()); }
+    pub fn remove_empty_value(&mut self) { self.inner.retain(|_, v| v.is_empty() == false); }
 
     pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); }
+
+    pub fn is_empty(&self) -> bool { self.inner.is_empty() }
 }
 
 impl std::convert::From<HashMap<String, String>> for Attributes {
@@ -36,11 +38,11 @@ impl std::ops::DerefMut for Attributes {
     fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
 }
 
-pub struct AttributesBuilder {
+pub struct AttrsBuilder {
     inner: Attributes,
 }
 
-impl AttributesBuilder {
+impl AttrsBuilder {
     pub fn new() -> Self {
         Self {
             inner: Attributes::default(),
@@ -52,6 +54,11 @@ impl AttributesBuilder {
         self
     }
 
+    pub fn un_bold(mut self) -> Self {
+        self.inner.insert("bold".to_owned(), "".to_owned());
+        self
+    }
+
     pub fn italic(mut self) -> Self {
         self.inner.insert("italic".to_owned(), "true".to_owned());
         self
@@ -68,7 +75,7 @@ impl AttributesBuilder {
 pub fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
     match operation {
         None => None,
-        Some(operation) => operation.attributes(),
+        Some(operation) => operation.get_attributes(),
     }
 }
 
@@ -79,22 +86,19 @@ pub fn compose_attributes(
 ) -> Option<Attributes> {
     let a = attributes_from(op1);
     let b = attributes_from(op2);
-
-    if a.is_none() {
-        return b;
-    }
-
-    if b.is_none() {
-        return None;
-    }
-
     let mut attrs_a = a.unwrap_or(Attributes::default());
     let attrs_b = b.unwrap_or(Attributes::default());
-    attrs_a.extend(attrs_b);
 
+    // log::debug!(
+    //     "before compose_attributes: a: {:?}, b: {:?}",
+    //     attrs_a,
+    //     attrs_b
+    // );
+    attrs_a.extend(attrs_b);
     if !keep_empty {
         attrs_a.remove_empty_value()
     }
+    // log::debug!("after compose_attributes: a: {:?}", attrs_a);
 
     return if attrs_a.is_empty() {
         None

+ 90 - 81
rust-lib/flowy-ot/src/delta.rs

@@ -57,7 +57,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.n, r.attributes),
+            Operation::Retain(r) => self.retain(r.num, r.attributes),
         }
     }
 
@@ -80,13 +80,13 @@ impl Delta {
 
         self.target_len += num_chars(s.as_bytes());
         let new_last = match self.ops.as_mut_slice() {
-            [.., Operation::Insert(s_last)] => {
+            [.., Operation::Insert(insert)] => {
                 //
-                merge_insert_or_new_op(s_last, s, attrs)
+                merge_insert_or_new_op(insert, s, attrs)
             },
-            [.., Operation::Insert(s_pre_last), Operation::Delete(_)] => {
+            [.., Operation::Insert(pre_insert), Operation::Delete(_)] => {
                 //
-                merge_insert_or_new_op(s_pre_last, s, attrs)
+                merge_insert_or_new_op(pre_insert, s, attrs)
             },
             [.., op_last @ Operation::Delete(_)] => {
                 let new_last = op_last.clone();
@@ -109,8 +109,8 @@ impl Delta {
         self.base_len += n as usize;
         self.target_len += n as usize;
 
-        if let Some(Operation::Retain(i_last)) = self.ops.last_mut() {
-            match merge_retain_or_new_op(i_last, n, attrs) {
+        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),
             }
@@ -148,38 +148,39 @@ impl Delta {
                     new_delta.delete(*i);
                     next_op1 = ops1.next();
                 },
-                (_, Some(Operation::Insert(insert))) => {
-                    new_delta.insert(&insert.s, attributes_from(&next_op2));
+                (_, Some(Operation::Insert(o_insert))) => {
+                    new_delta.insert(&o_insert.s, attributes_from(&next_op2));
                     next_op2 = ops2.next();
                 },
                 (None, _) | (_, None) => {
                     return Err(OTError);
                 },
-                (Some(Operation::Retain(i)), Some(Operation::Retain(j))) => {
-                    let new_attrs = compose_attributes(&next_op1, &next_op2, true);
-                    match i.cmp(&j) {
+                (Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => {
+                    let composed_attrs = compose_attributes(&next_op1, &next_op2, true);
+                    match retain.cmp(&o_retain) {
                         Ordering::Less => {
-                            new_delta.retain(i.n, new_attrs);
-                            next_op2 = Some(OpBuilder::retain(j.n - i.n).build());
+                            new_delta.retain(retain.num, composed_attrs);
+                            next_op2 = Some(OpBuilder::retain(o_retain.num - retain.num).build());
                             next_op1 = ops1.next();
                         },
                         std::cmp::Ordering::Equal => {
-                            new_delta.retain(i.n, new_attrs);
+                            new_delta.retain(retain.num, composed_attrs);
                             next_op1 = ops1.next();
                             next_op2 = ops2.next();
                         },
                         std::cmp::Ordering::Greater => {
-                            new_delta.retain(j.n, new_attrs);
-                            next_op1 = Some(OpBuilder::retain(i.n - j.n).build());
+                            new_delta.retain(o_retain.num, composed_attrs);
+                            next_op1 = Some(OpBuilder::retain(retain.num - o_retain.num).build());
                             next_op2 = ops2.next();
                         },
                     }
                 },
-                (Some(Operation::Insert(insert)), Some(Operation::Delete(j))) => {
-                    match (num_chars(insert.as_bytes()) as u64).cmp(j) {
+                (Some(Operation::Insert(insert)), Some(Operation::Delete(o_num))) => {
+                    match (num_chars(insert.as_bytes()) as u64).cmp(o_num) {
                         Ordering::Less => {
                             next_op2 = Some(
-                                OpBuilder::delete(*j - num_chars(insert.as_bytes()) as u64).build(),
+                                OpBuilder::delete(*o_num - num_chars(insert.as_bytes()) as u64)
+                                    .build(),
                             );
                             next_op1 = ops1.next();
                         },
@@ -190,7 +191,7 @@ impl Delta {
                         Ordering::Greater => {
                             next_op1 = Some(
                                 OpBuilder::insert(
-                                    &insert.chars().skip(*j as usize).collect::<String>(),
+                                    &insert.chars().skip(*o_num as usize).collect::<String>(),
                                 )
                                 .build(),
                             );
@@ -198,44 +199,52 @@ impl Delta {
                         },
                     }
                 },
-                (Some(Operation::Insert(insert)), Some(Operation::Retain(j))) => {
-                    let new_attrs = compose_attributes(&next_op1, &next_op2, false);
-                    match (insert.num_chars()).cmp(j) {
+                (Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => {
+                    let composed_attrs = compose_attributes(&next_op1, &next_op2, false);
+                    match (insert.num_chars()).cmp(o_retain) {
                         Ordering::Less => {
-                            new_delta.insert(&insert.s, new_attrs);
-                            next_op2 = Some(OpBuilder::retain(j.n - insert.num_chars()).build());
+                            new_delta.insert(&insert.s, composed_attrs.clone());
+                            next_op2 = Some(
+                                OpBuilder::retain(o_retain.num - insert.num_chars())
+                                    .attributes(composed_attrs.clone())
+                                    .build(),
+                            );
                             next_op1 = ops1.next();
                         },
                         Ordering::Equal => {
-                            new_delta.insert(&insert.s, new_attrs);
+                            new_delta.insert(&insert.s, composed_attrs);
                             next_op1 = ops1.next();
                             next_op2 = ops2.next();
                         },
                         Ordering::Greater => {
                             let chars = &mut insert.chars();
-                            new_delta
-                                .insert(&chars.take(j.n as usize).collect::<String>(), new_attrs);
+                            new_delta.insert(
+                                &chars.take(o_retain.num as usize).collect::<String>(),
+                                composed_attrs,
+                            );
                             next_op1 = Some(OpBuilder::insert(&chars.collect::<String>()).build());
                             next_op2 = ops2.next();
                         },
                     }
                 },
-                (Some(Operation::Retain(i)), Some(Operation::Delete(j))) => match i.cmp(&j) {
-                    Ordering::Less => {
-                        new_delta.delete(i.n);
-                        next_op2 = Some(OpBuilder::delete(*j - i.n).build());
-                        next_op1 = ops1.next();
-                    },
-                    Ordering::Equal => {
-                        new_delta.delete(*j);
-                        next_op2 = ops2.next();
-                        next_op1 = ops1.next();
-                    },
-                    Ordering::Greater => {
-                        new_delta.delete(*j);
-                        next_op1 = Some(OpBuilder::retain(i.n - *j).build());
-                        next_op2 = ops2.next();
-                    },
+                (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());
+                            next_op1 = ops1.next();
+                        },
+                        Ordering::Equal => {
+                            new_delta.delete(*o_num);
+                            next_op2 = ops2.next();
+                            next_op1 = ops1.next();
+                        },
+                        Ordering::Greater => {
+                            new_delta.delete(*o_num);
+                            next_op1 = Some(OpBuilder::retain(retain.num - *o_num).build());
+                            next_op2 = ops2.next();
+                        },
+                    }
                 },
             };
         }
@@ -268,15 +277,15 @@ impl Delta {
             match (&next_op1, &next_op2) {
                 (None, None) => break,
                 (Some(Operation::Insert(insert)), _) => {
-                    let new_attrs = compose_attributes(&next_op1, &next_op2, true);
-                    a_prime.insert(&insert.s, new_attrs.clone());
-                    b_prime.retain(insert.num_chars(), new_attrs.clone());
+                    // let composed_attrs = transform_attributes(&next_op1, &next_op2, true);
+                    a_prime.insert(&insert.s, insert.attributes.clone());
+                    b_prime.retain(insert.num_chars(), insert.attributes.clone());
                     next_op1 = ops1.next();
                 },
-                (_, Some(Operation::Insert(insert))) => {
-                    let new_attrs = compose_attributes(&next_op1, &next_op2, true);
-                    a_prime.retain(insert.num_chars(), new_attrs.clone());
-                    b_prime.insert(&insert.s, new_attrs.clone());
+                (_, Some(Operation::Insert(o_insert))) => {
+                    let composed_attrs = transform_attributes(&next_op1, &next_op2, true);
+                    a_prime.retain(o_insert.num_chars(), composed_attrs.clone());
+                    b_prime.insert(&o_insert.s, composed_attrs.clone());
                     next_op2 = ops2.next();
                 },
                 (None, _) => {
@@ -285,25 +294,25 @@ impl Delta {
                 (_, None) => {
                     return Err(OTError);
                 },
-                (Some(Operation::Retain(i)), Some(Operation::Retain(j))) => {
-                    let new_attrs = compose_attributes(&next_op1, &next_op2, true);
-                    match i.cmp(&j) {
+                (Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => {
+                    let composed_attrs = transform_attributes(&next_op1, &next_op2, true);
+                    match retain.cmp(&o_retain) {
                         Ordering::Less => {
-                            a_prime.retain(i.n, new_attrs.clone());
-                            b_prime.retain(i.n, new_attrs.clone());
-                            next_op2 = Some(OpBuilder::retain(j.n - i.n).build());
+                            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());
                             next_op1 = ops1.next();
                         },
                         Ordering::Equal => {
-                            a_prime.retain(i.n, new_attrs.clone());
-                            b_prime.retain(i.n, new_attrs.clone());
+                            a_prime.retain(retain.num, composed_attrs.clone());
+                            b_prime.retain(retain.num, composed_attrs.clone());
                             next_op1 = ops1.next();
                             next_op2 = ops2.next();
                         },
                         Ordering::Greater => {
-                            a_prime.retain(j.n, new_attrs.clone());
-                            b_prime.retain(j.n, new_attrs.clone());
-                            next_op1 = Some(OpBuilder::retain(i.n - j.n).build());
+                            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());
                             next_op2 = ops2.next();
                         },
                     };
@@ -322,11 +331,11 @@ impl Delta {
                         next_op2 = ops2.next();
                     },
                 },
-                (Some(Operation::Delete(i)), Some(Operation::Retain(j))) => {
-                    match i.cmp(&j) {
+                (Some(Operation::Delete(i)), Some(Operation::Retain(o_retain))) => {
+                    match i.cmp(&o_retain) {
                         Ordering::Less => {
                             a_prime.delete(*i);
-                            next_op2 = Some(OpBuilder::retain(j.n - *i).build());
+                            next_op2 = Some(OpBuilder::retain(o_retain.num - *i).build());
                             next_op1 = ops1.next();
                         },
                         Ordering::Equal => {
@@ -335,27 +344,27 @@ impl Delta {
                             next_op2 = ops2.next();
                         },
                         Ordering::Greater => {
-                            a_prime.delete(j.n);
-                            next_op1 = Some(OpBuilder::delete(*i - j.n).build());
+                            a_prime.delete(o_retain.num);
+                            next_op1 = Some(OpBuilder::delete(*i - o_retain.num).build());
                             next_op2 = ops2.next();
                         },
                     };
                 },
-                (Some(Operation::Retain(i)), Some(Operation::Delete(j))) => {
-                    match i.cmp(&j) {
+                (Some(Operation::Retain(retain)), Some(Operation::Delete(j))) => {
+                    match retain.cmp(&j) {
                         Ordering::Less => {
-                            b_prime.delete(i.n);
-                            next_op2 = Some(OpBuilder::delete(*j - i.n).build());
+                            b_prime.delete(retain.num);
+                            next_op2 = Some(OpBuilder::delete(*j - retain.num).build());
                             next_op1 = ops1.next();
                         },
                         Ordering::Equal => {
-                            b_prime.delete(i.n);
+                            b_prime.delete(retain.num);
                             next_op1 = ops1.next();
                             next_op2 = ops2.next();
                         },
                         Ordering::Greater => {
                             b_prime.delete(*j);
-                            next_op1 = Some(OpBuilder::retain(i.n - *j).build());
+                            next_op1 = Some(OpBuilder::retain(retain.num - *j).build());
                             next_op2 = ops2.next();
                         },
                     };
@@ -381,7 +390,7 @@ impl Delta {
         for op in &self.ops {
             match &op {
                 Operation::Retain(retain) => {
-                    for c in chars.take(retain.n as usize) {
+                    for c in chars.take(retain.num as usize) {
                         new_s.push(c);
                     }
                 },
@@ -406,8 +415,8 @@ impl Delta {
         for op in &self.ops {
             match &op {
                 Operation::Retain(retain) => {
-                    inverted.retain(retain.n, None);
-                    for _ in 0..retain.n {
+                    inverted.retain(retain.num, None);
+                    for _ in 0..retain.num {
                         chars.next();
                     }
                 },
@@ -417,7 +426,7 @@ impl Delta {
                 Operation::Delete(delete) => {
                     inverted.insert(
                         &chars.take(*delete as usize).collect::<String>(),
-                        op.attributes(),
+                        op.get_attributes(),
                     );
                 },
             }
@@ -470,12 +479,12 @@ fn merge_retain_or_new_op(
     attributes: Option<Attributes>,
 ) -> Option<Operation> {
     if attributes.is_none() {
-        retain.n += n;
+        retain.num += n;
         return None;
     }
-
+    // log::debug!("merge retain: {:?}, {:?}", retain.attributes, attributes);
     if retain.attributes == attributes {
-        retain.n += n;
+        retain.num += n;
         None
     } else {
         Some(OpBuilder::retain(n).attributes(attributes).build())

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

@@ -0,0 +1,200 @@
+use std::{
+    cmp::{max, min},
+    fmt,
+    ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
+};
+
+/// Representing a closed-open range;
+/// the interval [5, 7) is the set {5, 6}.
+///
+/// It is an invariant that `start <= end`. An interval where `end < start` is
+/// considered empty.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct Interval {
+    pub start: usize,
+    pub end: usize,
+}
+
+impl Interval {
+    /// Construct a new `Interval` representing the range [start..end).
+    /// It is an invariant that `start <= end`.
+    pub fn new(start: usize, end: usize) -> Interval {
+        debug_assert!(start <= end);
+        Interval { start, end }
+    }
+
+    pub fn start(&self) -> usize { self.start }
+
+    pub fn end(&self) -> usize { self.end }
+
+    pub fn start_end(&self) -> (usize, usize) { (self.start, self.end) }
+
+    pub fn is_before(&self, val: usize) -> bool { self.end <= val }
+
+    pub fn contains(&self, val: usize) -> bool { self.start <= val && val < self.end }
+
+    pub fn is_after(&self, val: usize) -> bool { self.start > val }
+
+    pub fn is_empty(&self) -> bool { self.end <= self.start }
+
+    pub fn intersect(&self, other: Interval) -> Interval {
+        let start = max(self.start, other.start);
+        let end = min(self.end, other.end);
+        Interval {
+            start,
+            end: max(start, end),
+        }
+    }
+
+    // the first half of self - other
+    pub fn prefix(&self, other: Interval) -> Interval {
+        Interval {
+            start: min(self.start, other.start),
+            end: min(self.end, other.start),
+        }
+    }
+
+    // the second half of self - other
+    pub fn suffix(&self, other: Interval) -> Interval {
+        Interval {
+            start: max(self.start, other.end),
+            end: max(self.end, other.end),
+        }
+    }
+
+    pub fn translate(&self, amount: usize) -> Interval {
+        Interval {
+            start: self.start + amount,
+            end: self.end + amount,
+        }
+    }
+
+    pub fn translate_neg(&self, amount: usize) -> Interval {
+        debug_assert!(self.start >= amount);
+        Interval {
+            start: self.start - amount,
+            end: self.end - amount,
+        }
+    }
+
+    pub fn size(&self) -> usize { self.end - self.start }
+}
+
+impl fmt::Display for Interval {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "[{}, {})", self.start(), self.end())
+    }
+}
+
+impl fmt::Debug for Interval {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
+}
+
+impl From<Range<usize>> for Interval {
+    fn from(src: Range<usize>) -> Interval {
+        let Range { start, end } = src;
+        Interval { start, end }
+    }
+}
+
+impl From<RangeTo<usize>> for Interval {
+    fn from(src: RangeTo<usize>) -> Interval { Interval::new(0, src.end) }
+}
+
+impl From<RangeInclusive<usize>> for Interval {
+    fn from(src: RangeInclusive<usize>) -> Interval {
+        Interval::new(*src.start(), src.end().saturating_add(1))
+    }
+}
+
+impl From<RangeToInclusive<usize>> for Interval {
+    fn from(src: RangeToInclusive<usize>) -> Interval {
+        Interval::new(0, src.end.saturating_add(1))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::interval::Interval;
+
+    #[test]
+    fn contains() {
+        let i = Interval::new(2, 42);
+        assert!(!i.contains(1));
+        assert!(i.contains(2));
+        assert!(i.contains(3));
+        assert!(i.contains(41));
+        assert!(!i.contains(42));
+        assert!(!i.contains(43));
+    }
+
+    #[test]
+    fn before() {
+        let i = Interval::new(2, 42);
+        assert!(!i.is_before(1));
+        assert!(!i.is_before(2));
+        assert!(!i.is_before(3));
+        assert!(!i.is_before(41));
+        assert!(i.is_before(42));
+        assert!(i.is_before(43));
+    }
+
+    #[test]
+    fn after() {
+        let i = Interval::new(2, 42);
+        assert!(i.is_after(1));
+        assert!(!i.is_after(2));
+        assert!(!i.is_after(3));
+        assert!(!i.is_after(41));
+        assert!(!i.is_after(42));
+        assert!(!i.is_after(43));
+    }
+
+    #[test]
+    fn translate() {
+        let i = Interval::new(2, 42);
+        assert_eq!(Interval::new(5, 45), i.translate(3));
+        assert_eq!(Interval::new(1, 41), i.translate_neg(1));
+    }
+
+    #[test]
+    fn empty() {
+        assert!(Interval::new(0, 0).is_empty());
+        assert!(Interval::new(1, 1).is_empty());
+        assert!(!Interval::new(1, 2).is_empty());
+    }
+
+    #[test]
+    fn intersect() {
+        assert_eq!(
+            Interval::new(2, 3),
+            Interval::new(1, 3).intersect(Interval::new(2, 4))
+        );
+        assert!(Interval::new(1, 2)
+            .intersect(Interval::new(2, 43))
+            .is_empty());
+    }
+
+    #[test]
+    fn prefix() {
+        assert_eq!(
+            Interval::new(1, 2),
+            Interval::new(1, 4).prefix(Interval::new(2, 3))
+        );
+    }
+
+    #[test]
+    fn suffix() {
+        assert_eq!(
+            Interval::new(3, 4),
+            Interval::new(1, 4).suffix(Interval::new(2, 3))
+        );
+    }
+
+    #[test]
+    fn size() {
+        assert_eq!(40, Interval::new(2, 42).size());
+        assert_eq!(0, Interval::new(1, 1).size());
+        assert_eq!(1, Interval::new(1, 2).size());
+    }
+}

+ 1 - 0
rust-lib/flowy-ot/src/lib.rs

@@ -1,5 +1,6 @@
 pub mod attributes;
 pub mod delta;
 pub mod errors;
+pub mod interval;
 pub mod operation;
 mod operation_serde;

+ 8 - 8
rust-lib/flowy-ot/src/operation.rs

@@ -29,7 +29,7 @@ impl Operation {
         }
     }
 
-    pub fn attributes(&self) -> Option<Attributes> {
+    pub fn get_attributes(&self) -> Option<Attributes> {
         match self {
             Operation::Delete(_) => None,
             Operation::Retain(retain) => retain.attributes.clone(),
@@ -49,12 +49,12 @@ impl Operation {
         }
     }
 
-    pub fn is_plain(&self) -> bool { self.attributes().is_none() }
+    pub fn is_plain(&self) -> bool { self.get_attributes().is_none() }
 
     pub fn length(&self) -> u64 {
         match self {
             Operation::Delete(n) => *n,
-            Operation::Retain(r) => r.n,
+            Operation::Retain(r) => r.num,
             Operation::Insert(i) => i.num_chars(),
         }
     }
@@ -94,15 +94,15 @@ impl OpBuilder {
 #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
 pub struct Retain {
     #[serde(rename(serialize = "retain", deserialize = "retain"))]
-    pub n: u64,
+    pub num: u64,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub(crate) attributes: Option<Attributes>,
+    pub attributes: Option<Attributes>,
 }
 
 impl std::convert::From<u64> for Retain {
     fn from(n: u64) -> Self {
         Retain {
-            n,
+            num: n,
             attributes: None,
         }
     }
@@ -111,11 +111,11 @@ impl std::convert::From<u64> for Retain {
 impl Deref for Retain {
     type Target = u64;
 
-    fn deref(&self) -> &Self::Target { &self.n }
+    fn deref(&self) -> &Self::Target { &self.num }
 }
 
 impl DerefMut for Retain {
-    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.n }
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.num }
 }
 
 #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]

+ 105 - 9
rust-lib/flowy-ot/tests/attribute_test.rs

@@ -1,16 +1,112 @@
+pub mod helper;
+
+use crate::{
+    helper::{MergeTestOp::*, *},
+    MergeTestOp::*,
+};
 use flowy_ot::{
-    attributes::{Attributes, AttributesBuilder},
+    attributes::{Attributes, AttrsBuilder},
     delta::Delta,
+    interval::Interval,
     operation::{OpBuilder, Operation, Retain},
 };
 
 #[test]
-fn attribute_insert_merge_test() {
-    let mut delta = Delta::default();
-    delta.insert("123", Some(AttributesBuilder::new().bold().build()));
-    delta.insert("456", Some(AttributesBuilder::new().bold().build()));
-    assert_eq!(
-        r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#,
-        serde_json::to_string(&delta).unwrap()
-    )
+fn delta_add_bold_attr1() {
+    let ops = vec![
+        Insert(0, "123"),
+        Bold(0, Interval::new(0, 3), true),
+        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
+        Bold(0, Interval::new(0, 3), false),
+        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_add_bold_attr2() {
+    let ops = vec![
+        Insert(0, "1234"),
+        Bold(0, Interval::new(0, 4), true),
+        AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
+        Bold(0, Interval::new(2, 4), false),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#,
+        ),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_add_bold_attr3() {
+    let ops = vec![
+        Insert(0, "1234"),
+        Bold(0, Interval::new(0, 4), true),
+        AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
+        Bold(0, Interval::new(0, 2), false),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12"},{"insert":"34","attributes":{"bold":"true"}}]"#,
+        ),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_add_bold_attr_and_invert() {
+    let ops = vec![
+        Insert(0, "1234"),
+        Bold(0, Interval::new(0, 4), true),
+        AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
+        Bold(0, Interval::new(2, 4), false),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#,
+        ),
+        Bold(0, Interval::new(2, 4), true),
+        AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_merge_inserted_text_with_same_attribute() {
+    let ops = vec![
+        InsertBold(0, "123", Interval::new(0, 3)),
+        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
+        InsertBold(0, "456", Interval::new(3, 6)),
+        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_compose_attr_delta_with_no_attr_delta_test() {
+    let expected = r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"7"}]"#;
+    let ops = vec![
+        InsertBold(0, "123456", Interval::new(0, 6)),
+        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        Insert(1, "7"),
+        AssertOpsJson(1, r#"[{"insert":"7"}]"#),
+        Transform(0, 1),
+        AssertOpsJson(0, expected),
+        AssertOpsJson(1, expected),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_compose_attr_delta_with_attr_delta_test() {
+    let ops = vec![
+        InsertBold(0, "123456", Interval::new(0, 6)),
+        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        InsertBold(1, "7", Interval::new(0, 1)),
+        AssertOpsJson(1, r#"[{"insert":"7","attributes":{"bold":"true"}}]"#),
+        Transform(0, 1),
+        AssertOpsJson(0, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#),
+        AssertOpsJson(1, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#),
+    ];
+
+    MergeTest::new().run_script(ops);
 }

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

@@ -1,5 +1,11 @@
-use flowy_ot::delta::Delta;
+use flowy_ot::{
+    attributes::{Attributes, AttrsBuilder},
+    delta::Delta,
+    interval::Interval,
+    operation::{OpBuilder, Operation},
+};
 use rand::{prelude::*, Rng as WrappedRng};
+use std::sync::Once;
 
 pub struct Rng(StdRng);
 
@@ -44,3 +50,140 @@ impl Rng {
         delta
     }
 }
+
+#[derive(Clone, Debug)]
+pub enum MergeTestOp {
+    Insert(usize, &'static str),
+    // delta_i, s, start, length,
+    InsertBold(usize, &'static str, Interval),
+    // delta_i, start, length, enable
+    Bold(usize, Interval, bool),
+    Transform(usize, usize),
+    AssertStr(usize, &'static str),
+    AssertOpsJson(usize, &'static str),
+}
+
+pub struct MergeTest {
+    deltas: Vec<Delta>,
+}
+
+impl MergeTest {
+    pub fn new() -> Self {
+        static INIT: Once = Once::new();
+        INIT.call_once(|| {
+            std::env::set_var("RUST_LOG", "debug");
+            env_logger::init();
+        });
+
+        let mut deltas = Vec::with_capacity(2);
+        for _ in 0..2 {
+            let delta = Delta::default();
+            deltas.push(delta);
+        }
+        Self { deltas }
+    }
+
+    pub fn run_op(&mut self, op: &MergeTestOp) {
+        match op {
+            MergeTestOp::Insert(delta_i, s) => {
+                let delta = &mut self.deltas[*delta_i];
+                delta.insert(s, None);
+            },
+            MergeTestOp::InsertBold(delta_i, s, interval) => {
+                let attrs = AttrsBuilder::new().bold().build();
+                let delta = &mut self.deltas[*delta_i];
+                delta.insert(s, Some(attrs));
+            },
+            MergeTestOp::Bold(delta_i, interval, enable) => {
+                let attrs = if *enable {
+                    AttrsBuilder::new().bold().build()
+                } else {
+                    AttrsBuilder::new().un_bold().build()
+                };
+                let delta = &mut self.deltas[*delta_i];
+                let delta_interval = Interval::new(0, delta.target_len);
+
+                let mut new_delta = Delta::default();
+                let prefix = delta_interval.prefix(*interval);
+                if prefix.is_empty() == false && prefix != *interval {
+                    let size = prefix.size();
+                    // get attr in prefix interval
+                    let attrs = attributes_in_interval(delta, &prefix);
+                    new_delta.retain(size as u64, Some(attrs));
+                }
+
+                let size = interval.size();
+                new_delta.retain(size as u64, Some(attrs));
+
+                let suffix = delta_interval.suffix(*interval);
+                if suffix.is_empty() == false {
+                    let size = suffix.size();
+                    let attrs = attributes_in_interval(delta, &suffix);
+                    new_delta.retain(size as u64, Some(attrs));
+                }
+
+                let a = delta.compose(&new_delta).unwrap();
+                self.deltas[*delta_i] = a;
+            },
+            MergeTestOp::Transform(delta_a_i, delta_b_i) => {
+                let delta_a = &self.deltas[*delta_a_i];
+                let delta_b = &self.deltas[*delta_b_i];
+
+                let (a_prime, b_prime) = delta_a.transform(delta_b).unwrap();
+                let new_delta_a = delta_a.compose(&b_prime).unwrap();
+                let new_delta_b = delta_b.compose(&a_prime).unwrap();
+
+                self.deltas[*delta_a_i] = new_delta_a;
+                self.deltas[*delta_b_i] = new_delta_b;
+            },
+            MergeTestOp::AssertStr(delta_i, expected) => {
+                let s = self.deltas[*delta_i].apply("").unwrap();
+                assert_eq!(&s, expected);
+            },
+
+            MergeTestOp::AssertOpsJson(delta_i, expected) => {
+                let s = serde_json::to_string(&self.deltas[*delta_i]).unwrap();
+                if &s != expected {
+                    log::error!("{}", s);
+                }
+
+                assert_eq!(&s, expected);
+            },
+        }
+    }
+
+    pub fn run_script(&mut self, script: Vec<MergeTestOp>) {
+        for (i, op) in script.iter().enumerate() {
+            self.run_op(op);
+        }
+    }
+}
+
+pub fn debug_print_delta(delta: &Delta) {
+    log::debug!("😁 {}", serde_json::to_string(delta).unwrap());
+}
+
+pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Attributes {
+    let mut attributes = Attributes::new();
+    let mut offset = 0;
+
+    delta.ops.iter().for_each(|op| match op {
+        Operation::Delete(n) => {},
+        Operation::Retain(retain) => {
+            if retain.attributes.is_some() {
+                if interval.contains(retain.num as usize) {
+                    attributes.extend(retain.attributes.as_ref().unwrap().clone());
+                }
+            }
+        },
+        Operation::Insert(insert) => {
+            if insert.attributes.is_some() {
+                if interval.start >= offset || insert.num_chars() > interval.end as u64 {
+                    attributes.extend(insert.attributes.as_ref().unwrap().clone());
+                }
+                offset += insert.num_chars() as usize;
+            }
+        },
+    });
+    attributes
+}

+ 40 - 0
rust-lib/flowy-ot/tests/op_test.rs

@@ -1,5 +1,6 @@
 pub mod helper;
 
+use crate::helper::MergeTestOp::*;
 use bytecount::num_chars;
 use flowy_ot::{
     attributes::*,
@@ -7,6 +8,7 @@ use flowy_ot::{
     operation::{OpBuilder, Operation},
 };
 use helper::*;
+use std::str::FromStr;
 
 #[test]
 fn lengths() {
@@ -91,6 +93,7 @@ fn invert() {
         assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);
     }
 }
+
 #[test]
 fn empty_ops() {
     let mut delta = Delta::default();
@@ -185,3 +188,40 @@ fn transform() {
         assert_eq!(after_ab_prime, after_ba_prime);
     }
 }
+
+#[test]
+fn transform2() {
+    let ops = vec![
+        Insert(0, "123"),
+        Insert(1, "456"),
+        Transform(0, 1),
+        AssertStr(0, "123456"),
+        AssertStr(1, "123456"),
+        AssertOpsJson(0, r#"[{"insert":"123456"}]"#),
+        AssertOpsJson(1, r#"[{"insert":"123456"}]"#),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_transform_test() {
+    let mut a = Delta::default();
+    let mut a_s = String::new();
+    a.insert("123", Some(AttrsBuilder::new().bold().build()));
+    a_s = a.apply(&a_s).unwrap();
+
+    let mut b = Delta::default();
+    let mut b_s = String::new();
+    b.insert("456", None);
+    b_s = a.apply(&b_s).unwrap();
+
+    let (a_prime, b_prime) = a.transform(&b).unwrap();
+    assert_eq!(
+        r#"[{"insert":"123","attributes":{"bold":"true"}},{"retain":3}]"#,
+        serde_json::to_string(&a_prime).unwrap()
+    );
+    assert_eq!(
+        r#"[{"retain":3,"attributes":{"bold":"true"}},{"insert":"456"}]"#,
+        serde_json::to_string(&b_prime).unwrap()
+    );
+}

+ 3 - 3
rust-lib/flowy-ot/tests/serde_test.rs

@@ -1,12 +1,12 @@
 use flowy_ot::{
-    attributes::{Attributes, AttributesBuilder},
+    attributes::{Attributes, AttrsBuilder},
     delta::Delta,
     operation::{OpBuilder, Operation, Retain},
 };
 
 #[test]
 fn operation_insert_serialize_test() {
-    let attributes = AttributesBuilder::new().bold().italic().build();
+    let attributes = AttrsBuilder::new().bold().italic().build();
     let operation = OpBuilder::insert("123")
         .attributes(Some(attributes))
         .build();
@@ -38,7 +38,7 @@ fn operation_delete_serialize_test() {
 fn delta_serialize_test() {
     let mut delta = Delta::default();
 
-    let attributes = AttributesBuilder::new().bold().italic().build();
+    let attributes = AttrsBuilder::new().bold().italic().build();
     let retain = OpBuilder::insert("123")
         .attributes(Some(attributes))
         .build();