Browse Source

insert text with index

appflowy 3 years ago
parent
commit
4ebeac13f5

+ 21 - 4
rust-lib/flowy-ot/src/attributes.rs

@@ -1,5 +1,5 @@
 use crate::operation::Operation;
-use std::collections::HashMap;
+use std::{collections::HashMap, fmt};
 
 const PLAIN: &'static str = "";
 fn is_plain(s: &str) -> bool { s == PLAIN }
@@ -15,7 +15,7 @@ pub enum Attributes {
 }
 
 impl Attributes {
-    pub fn merge(&self, other: Option<Attributes>) -> Attributes {
+    pub fn extend(&self, other: Option<Attributes>) -> Attributes {
         let other = other.unwrap_or(Attributes::Empty);
         match (self, &other) {
             (Attributes::Custom(data), Attributes::Custom(o_data)) => {
@@ -42,6 +42,23 @@ impl std::default::Default for Attributes {
     fn default() -> Self { Attributes::Empty }
 }
 
+impl fmt::Display for Attributes {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Attributes::Follow => {
+                f.write_str("")?;
+            },
+            Attributes::Custom(data) => {
+                f.write_fmt(format_args!("{:?}", data.inner))?;
+            },
+            Attributes::Empty => {
+                f.write_str("")?;
+            },
+        }
+        Ok(())
+    }
+}
+
 #[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
 pub struct AttributesData {
     #[serde(skip_serializing_if = "HashMap::is_empty")]
@@ -132,9 +149,9 @@ pub fn compose_attributes(left: &Option<Operation>, right: &Option<Operation>) -
     let mut attr = match (&attr_l, &attr_r) {
         (_, Some(Attributes::Custom(_))) => match &attr_l {
             None => attr_r.unwrap(),
-            Some(_) => attr_l.unwrap().merge(attr_r.clone()),
+            Some(_) => attr_l.unwrap().extend(attr_r.clone()),
         },
-        (Some(Attributes::Custom(_)), _) => attr_l.unwrap().merge(attr_r),
+        (Some(Attributes::Custom(_)), _) => attr_l.unwrap().extend(attr_r),
         _ => Attributes::Empty,
     };
 

+ 11 - 1
rust-lib/flowy-ot/src/delta.rs

@@ -1,6 +1,6 @@
 use crate::{attributes::*, errors::OTError, operation::*};
 use bytecount::num_chars;
-use std::{cmp::Ordering, iter::FromIterator, str::FromStr};
+use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr};
 
 #[derive(Clone, Debug, PartialEq)]
 pub struct Delta {
@@ -33,6 +33,16 @@ impl<T: AsRef<str>> From<T> for Delta {
     fn from(s: T) -> Delta { Delta::from_str(s.as_ref()).unwrap() }
 }
 
+impl fmt::Display for Delta {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?;
+        // for op in &self.ops {
+        //     f.write_fmt(format_args!("{}", op));
+        // }
+        Ok(())
+    }
+}
+
 impl FromIterator<Operation> for Delta {
     fn from_iter<T: IntoIterator<Item = Operation>>(ops: T) -> Self {
         let mut operations = Delta::default();

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

@@ -77,9 +77,25 @@ impl Interval {
         }
     }
 
+    pub fn union(&self, other: Interval) -> Interval {
+        if self.is_empty() {
+            return other;
+        }
+        if other.is_empty() {
+            return *self;
+        }
+        let start = min(self.start, other.start);
+        let end = max(self.end, other.end);
+        Interval { start, end }
+    }
+
     pub fn size(&self) -> usize { self.end - self.start }
 }
 
+impl std::default::Default for Interval {
+    fn default() -> Self { Interval::new(0, 0) }
+}
+
 impl fmt::Display for Interval {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "[{}, {})", self.start(), self.end())

+ 36 - 0
rust-lib/flowy-ot/src/operation.rs

@@ -1,6 +1,7 @@
 use crate::attributes::Attributes;
 use bytecount::num_chars;
 use std::{
+    fmt,
     ops::{Deref, DerefMut},
     str::Chars,
 };
@@ -35,6 +36,18 @@ impl Operation {
         }
     }
 
+    pub fn extend_attributes(&mut self, attributes: Attributes) {
+        match self {
+            Operation::Delete(_) => {},
+            Operation::Retain(retain) => {
+                retain.attributes.extend(Some(attributes));
+            },
+            Operation::Insert(insert) => {
+                insert.attributes.extend(Some(attributes));
+            },
+        }
+    }
+
     pub fn set_attributes(&mut self, attributes: Attributes) {
         match self {
             Operation::Delete(_) => {
@@ -67,6 +80,29 @@ impl Operation {
     pub fn is_empty(&self) -> bool { self.length() == 0 }
 }
 
+impl fmt::Display for Operation {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Operation::Delete(n) => {
+                f.write_fmt(format_args!("delete: {}", n))?;
+            },
+            Operation::Retain(r) => {
+                f.write_fmt(format_args!(
+                    "retain: {}, attributes: {}",
+                    r.num, r.attributes
+                ))?;
+            },
+            Operation::Insert(i) => {
+                f.write_fmt(format_args!(
+                    "insert: {}, attributes: {}",
+                    i.s, i.attributes
+                ))?;
+            },
+        }
+        Ok(())
+    }
+}
+
 pub struct OpBuilder {
     ty: Operation,
     attrs: Attributes,

+ 143 - 18
rust-lib/flowy-ot/tests/attribute_test.rs

@@ -6,10 +6,40 @@ use flowy_ot::{
     operation::{OpBuilder, Operation, Retain},
 };
 
+#[test]
+fn delta_insert_text() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(0, "456", 3),
+        AssertOpsJson(0, r#"[{"insert":"123456"}]"#),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_insert_text_at_head() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(0, "456", 0),
+        AssertOpsJson(0, r#"[{"insert":"456123"}]"#),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_insert_text_at_middle() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(0, "456", 2),
+        AssertOpsJson(0, r#"[{"insert":"124563"}]"#),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
 #[test]
 fn delta_add_bold_and_invert_all() {
     let ops = vec![
-        Insert(0, "123"),
+        Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
         AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
         Bold(0, Interval::new(0, 3), false),
@@ -21,7 +51,7 @@ fn delta_add_bold_and_invert_all() {
 #[test]
 fn delta_add_bold_and_invert_partial_suffix() {
     let ops = vec![
-        Insert(0, "1234"),
+        Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 4), true),
         AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
         Bold(0, Interval::new(2, 4), false),
@@ -36,7 +66,7 @@ fn delta_add_bold_and_invert_partial_suffix() {
 #[test]
 fn delta_add_bold_and_invert_partial_suffix2() {
     let ops = vec![
-        Insert(0, "1234"),
+        Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 4), true),
         AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
         Bold(0, Interval::new(2, 4), false),
@@ -53,7 +83,7 @@ fn delta_add_bold_and_invert_partial_suffix2() {
 #[test]
 fn delta_add_bold_and_invert_partial_prefix() {
     let ops = vec![
-        Insert(0, "1234"),
+        Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 4), true),
         AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
         Bold(0, Interval::new(0, 2), false),
@@ -68,7 +98,7 @@ fn delta_add_bold_and_invert_partial_prefix() {
 #[test]
 fn delta_add_bold_consecutive() {
     let ops = vec![
-        Insert(0, "1234"),
+        Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 1), true),
         AssertOpsJson(
             0,
@@ -93,14 +123,14 @@ fn delta_add_bold_empty_str() {
 #[test]
 fn delta_add_bold_italic() {
     let ops = vec![
-        Insert(0, "1234"),
+        Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 4), true),
         Italic(0, Interval::new(0, 4), true),
         AssertOpsJson(
             0,
             r#"[{"insert":"1234","attributes":{"italic":"true","bold":"true"}}]"#,
         ),
-        Insert(0, "5678"),
+        Insert(0, "5678", 4),
         AssertOpsJson(
             0,
             r#"[{"insert":"12345678","attributes":{"italic":"true","bold":"true"}}]"#,
@@ -114,6 +144,74 @@ fn delta_add_bold_italic() {
     MergeTest::new().run_script(ops);
 }
 
+#[test]
+fn delta_add_bold_italic2() {
+    let ops = vec![
+        Insert(0, "123456", 0),
+        Bold(0, Interval::new(0, 6), true),
+        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        Italic(0, Interval::new(0, 2), true),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12","attributes":{"italic":"true","bold":"true"}},{"insert":"3456","attributes":{"bold":"true"}}]"#,
+        ),
+        Italic(0, Interval::new(4, 6), true),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12","attributes":{"bold":"true","italic":"true"}},{"insert":"34","attributes":{"bold":"true"}},{"insert":"56","attributes":{"italic":"true"}}]"#,
+        ),
+    ];
+
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_add_bold_italic3() {
+    let ops = vec![
+        Insert(0, "123456789", 0),
+        Bold(0, Interval::new(0, 5), true),
+        Italic(0, Interval::new(0, 2), true),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12","attributes":{"bold":"true","italic":"true"}},{"insert":"345","attributes":{"bold":"true"}},{"insert":"6789"}]"#,
+        ),
+        Italic(0, Interval::new(2, 4), true),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"1234","attributes":{"bold":"true","italic":"true"}},{"insert":"5","attributes":{"bold":"true"}},{"insert":"6789"}]"#,
+        ),
+        Bold(0, Interval::new(7, 9), true),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"1234","attributes":{"bold":"true","italic":"true"}},{"insert":"5","attributes":{"bold":"true"}},{"insert":"67"},{"insert":"89","attributes":{"bold":"true"}}]"#,
+        ),
+    ];
+
+    MergeTest::new().run_script(ops);
+}
+
+#[test]
+fn delta_add_bold_italic_delete() {
+    let ops = vec![
+        Insert(0, "123456789", 0),
+        Bold(0, Interval::new(0, 5), true),
+        Italic(0, Interval::new(0, 2), true),
+        Italic(0, Interval::new(2, 4), true),
+        Bold(0, Interval::new(7, 9), true),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"1234","attributes":{"bold":"true","italic":"true"}},{"insert":"5","attributes":{"bold":"true"}},{"insert":"67"},{"insert":"89","attributes":{"bold":"true"}}]"#,
+        ),
+        Delete(0, Interval::new(0, 5)),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"67","attributes":{"bold":"true"}},{"insert":"89"}]"#,
+        ),
+    ];
+
+    MergeTest::new().run_script(ops);
+}
+
 #[test]
 fn delta_merge_inserted_text_with_same_attribute() {
     let ops = vec![
@@ -126,35 +224,62 @@ fn delta_merge_inserted_text_with_same_attribute() {
 }
 
 #[test]
-fn delta_compose_attr_delta_with_no_attr_delta_test() {
-    let expected = r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"7"}]"#;
+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"}}]"#),
-        Insert(1, "7"),
-        AssertOpsJson(1, r#"[{"insert":"7"}]"#),
+        InsertBold(1, "7", Interval::new(0, 1)),
+        AssertOpsJson(1, r#"[{"insert":"7","attributes":{"bold":"true"}}]"#),
         Transform(0, 1),
-        AssertOpsJson(0, expected),
-        AssertOpsJson(1, expected),
+        AssertOpsJson(0, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#),
+        AssertOpsJson(1, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#),
     ];
+
     MergeTest::new().run_script(ops);
 }
 
 #[test]
-fn delta_compose_attr_delta_with_attr_delta_test() {
+fn delta_compose_attr_delta_with_attr_delta_test2() {
     let ops = vec![
-        InsertBold(0, "123456", Interval::new(0, 6)),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        Insert(0, "123456", 0),
+        Bold(0, Interval::new(0, 6), true),
+        Italic(0, Interval::new(0, 2), true),
+        Italic(0, Interval::new(4, 6), true),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12","attributes":{"bold":"true","italic":"true"}},{"insert":"34","attributes":{"bold":"true"}},{"insert":"56","attributes":{"italic":"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"}}]"#),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12","attributes":{"italic":"true","bold":"true"}},{"insert":"34","attributes":{"bold":"true"}},{"insert":"56","attributes":{"italic":"true"}},{"insert":"7","attributes":{"bold":"true"}}]"#,
+        ),
+        AssertOpsJson(
+            1,
+            r#"[{"insert":"12","attributes":{"italic":"true","bold":"true"}},{"insert":"34","attributes":{"bold":"true"}},{"insert":"56","attributes":{"italic":"true"}},{"insert":"7","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", 0),
+        AssertOpsJson(1, r#"[{"insert":"7"}]"#),
+        Transform(0, 1),
+        AssertOpsJson(0, expected),
+        AssertOpsJson(1, expected),
+    ];
+    MergeTest::new().run_script(ops);
+}
+
 #[test]
 fn delta_delete_heading() {
     let ops = vec![

+ 109 - 27
rust-lib/flowy-ot/tests/helper/mod.rs

@@ -9,7 +9,7 @@ use std::sync::Once;
 
 #[derive(Clone, Debug)]
 pub enum MergeTestOp {
-    Insert(usize, &'static str),
+    Insert(usize, &'static str, usize),
     // delta_i, s, start, length,
     InsertBold(usize, &'static str, Interval),
     // delta_i, start, length, enable
@@ -43,9 +43,8 @@ impl MergeTest {
 
     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, Attributes::Follow);
+            MergeTestOp::Insert(delta_i, s, index) => {
+                self.update_delta_with_insert(*delta_i, s, *index);
             },
             MergeTestOp::Delete(delta_i, interval) => {
                 //
@@ -88,8 +87,8 @@ impl MergeTest {
                 let target_delta: Delta = serde_json::from_str(&delta_i_json).unwrap();
 
                 if expected_delta != target_delta {
-                    log::error!("✅ {}", expected);
-                    log::error!("❌ {}", delta_i_json);
+                    log::error!("✅ expect: {}", expected,);
+                    log::error!("❌ receive: {}", delta_i_json);
                 }
                 assert_eq!(target_delta, expected_delta);
             },
@@ -102,6 +101,43 @@ impl MergeTest {
         }
     }
 
+    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);
+        if old_delta.target_len < index {
+            log::error!("{} out of bounds {}", index, target_interval);
+        }
+
+        let mut attributes = attributes_in_delta(old_delta, &Interval::new(index, index + 1));
+        if attributes == Attributes::Empty {
+            attributes = Attributes::Follow;
+        }
+        let insert = OpBuilder::insert(s).attributes(attributes).build();
+
+        let mut new_delta = Delta::default();
+        let prefix = Interval::new(0, index);
+        let suffix = Interval::new(index, old_delta.target_len);
+
+        split_interval_with_delta(old_delta, &prefix)
+            .into_iter()
+            .for_each(|interval| {
+                let attrs = attributes_in_delta(old_delta, &interval);
+                new_delta.retain(interval.size() as u64, attrs);
+            });
+
+        new_delta.add(insert);
+
+        split_interval_with_delta(old_delta, &suffix)
+            .into_iter()
+            .for_each(|interval| {
+                let attrs = attributes_in_delta(old_delta, &interval);
+                new_delta.retain(interval.size() as u64, attrs);
+            });
+
+        new_delta = old_delta.compose(&new_delta).unwrap();
+        self.deltas[delta_index] = new_delta;
+    }
+
     pub fn update_delta_with_attribute(
         &mut self,
         delta_index: usize,
@@ -109,58 +145,93 @@ impl MergeTest {
         interval: &Interval,
     ) {
         let old_delta = &self.deltas[delta_index];
-        let retain = OpBuilder::retain(interval.size() as u64)
+        let mut retain = OpBuilder::retain(interval.size() as u64)
             .attributes(attributes)
             .build();
-        let new_delta = make_delta_with_op(old_delta, retain, interval);
+
+        let attrs = attributes_in_delta(old_delta, &interval);
+        retain.extend_attributes(attrs);
+
+        let new_delta = new_delta_with_op(old_delta, retain, interval);
         self.deltas[delta_index] = new_delta;
     }
 
     pub fn update_delta_with_delete(&mut self, delta_index: usize, interval: &Interval) {
         let old_delta = &self.deltas[delta_index];
-        let delete = OpBuilder::delete(interval.size() as u64).build();
-        let new_delta = make_delta_with_op(old_delta, delete, interval);
+        let mut delete = OpBuilder::delete(interval.size() as u64).build();
+        let attrs = attributes_in_delta(old_delta, &interval);
+        delete.extend_attributes(attrs);
+
+        let new_delta = new_delta_with_op(old_delta, delete, interval);
         self.deltas[delta_index] = new_delta;
     }
 }
 
-pub fn make_delta_with_op(delta: &Delta, op: Operation, interval: &Interval) -> Delta {
+fn new_delta_with_op(delta: &Delta, op: Operation, interval: &Interval) -> Delta {
     let mut new_delta = Delta::default();
-    let (prefix, suffix) = length_split_with_interval(delta.target_len, interval);
+    let (prefix, interval, suffix) = target_length_split_with_interval(delta.target_len, *interval);
 
     // prefix
-    if prefix.is_empty() == false && prefix != *interval {
-        let size = prefix.size();
-        let attrs = attributes_in_interval(delta, &prefix);
-        new_delta.retain(size as u64, attrs);
+    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);
+        });
     }
 
     new_delta.add(op);
 
     // suffix
     if suffix.is_empty() == false {
-        let size = suffix.size();
-        let attrs = attributes_in_interval(delta, &suffix);
-        new_delta.retain(size as u64, attrs);
+        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);
+        });
     }
 
     delta.compose(&new_delta).unwrap()
 }
 
-pub fn length_split_with_interval(length: usize, interval: &Interval) -> (Interval, Interval) {
+fn split_interval_with_delta(delta: &Delta, interval: &Interval) -> Vec<Interval> {
+    let mut start = 0;
+    let mut new_intervals = vec![];
+    delta.ops.iter().for_each(|op| match op {
+        Operation::Delete(_) => {},
+        Operation::Retain(_) => {},
+        Operation::Insert(insert) => {
+            let len = insert.num_chars() as usize;
+            let end = start + len;
+            let insert_interval = Interval::new(start, end);
+            let new_interval = interval.intersect(insert_interval);
+
+            if !new_interval.is_empty() {
+                new_intervals.push(new_interval)
+            }
+            start += len;
+        },
+    });
+    new_intervals
+}
+
+pub fn target_length_split_with_interval(
+    length: usize,
+    interval: Interval,
+) -> (Interval, Interval, Interval) {
     let original_interval = Interval::new(0, length);
-    let prefix = original_interval.prefix(*interval);
-    let suffix = original_interval.suffix(*interval);
-    (prefix, suffix)
+    let prefix = original_interval.prefix(interval);
+    let suffix = original_interval.suffix(interval);
+    (prefix, interval, suffix)
 }
 
 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 {
+pub fn attributes_in_delta(delta: &Delta, interval: &Interval) -> Attributes {
     let mut attributes_data = AttributesData::new();
-    let mut offset = 0;
+    let mut offset: usize = 0;
 
     delta.ops.iter().for_each(|op| match op {
         Operation::Delete(_n) => {},
@@ -178,10 +249,14 @@ pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Attributes
         Operation::Insert(insert) => match &insert.attributes {
             Attributes::Follow => {},
             Attributes::Custom(data) => {
-                if interval.start >= offset && insert.num_chars() > (interval.end as u64 - 1) {
+                let end = insert.num_chars() as usize;
+                if !interval
+                    .intersect(Interval::new(offset, offset + end))
+                    .is_empty()
+                {
                     attributes_data.extend(data.clone());
                 }
-                offset += insert.num_chars() as usize;
+                offset += end;
             },
             Attributes::Empty => {},
         },
@@ -193,6 +268,13 @@ pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Attributes
         Attributes::Custom(attributes_data)
     }
 }
+fn attributes_in_operation(op: &Operation, interval: &Interval) -> Attributes {
+    match op {
+        Operation::Delete(_) => Attributes::Empty,
+        Operation::Retain(retain) => Attributes::Empty,
+        Operation::Insert(insert) => Attributes::Empty,
+    }
+}
 
 pub struct Rng(StdRng);
 

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

@@ -2,7 +2,7 @@ pub mod helper;
 
 use crate::helper::MergeTestOp::*;
 use bytecount::num_chars;
-use flowy_ot::{attributes::*, delta::Delta, operation::OpBuilder};
+use flowy_ot::{attributes::*, delta::Delta, interval::Interval, operation::OpBuilder};
 use helper::*;
 
 #[test]
@@ -188,8 +188,8 @@ fn transform() {
 #[test]
 fn transform2() {
     let ops = vec![
-        Insert(0, "123"),
-        Insert(1, "456"),
+        Insert(0, "123", 0),
+        Insert(1, "456", 0),
         Transform(0, 1),
         AssertStr(0, "123456"),
         AssertStr(1, "123456"),