Browse Source

fix undo redo bugs, merge last undo if need

appflowy 3 years ago
parent
commit
ca81c990bf

+ 4 - 2
app_flowy/packages/flowy_editor/lib/src/model/document/history.dart

@@ -51,10 +51,12 @@ class History {
     var undoDelta = change.invert(before);
     final timestamp = DateTime.now().millisecondsSinceEpoch;
 
-    if (timestamp - lastRecorded < minRecordThreshold &&
-        stack.undo.isNotEmpty) {
+    if (stack.undo.isNotEmpty) {
       final lastDelta = stack.undo.removeLast();
+      print("undoDelta: $undoDelta");
+      print("lastDelta: $lastDelta");
       undoDelta = undoDelta.compose(lastDelta);
+      print("compose result: $undoDelta");
     } else {
       lastRecorded = timestamp;
     }

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

@@ -12,6 +12,7 @@ serde_json = {version = "1.0"}
 derive_more = {version = "0.99", features = ["display"]}
 log = "0.4"
 color-eyre = { version = "0.5", default-features = false }
+chrono = "0.4.19"
 
 [dev-dependencies]
 criterion = "0.3"

+ 92 - 49
rust-lib/flowy-ot/src/client/document.rs

@@ -1,22 +1,16 @@
 use crate::{
     client::{History, RevId, UndoResult},
-    core::{
-        Attribute,
-        Attributes,
-        AttributesDataRule,
-        AttrsBuilder,
-        Builder,
-        Delta,
-        Interval,
-        Operation,
-    },
+    core::*,
     errors::{ErrorBuilder, OTError, OTErrorCode::*},
 };
 
+pub const RECORD_THRESHOLD: usize = 400; // in milliseconds
+
 pub struct Document {
     data: Delta,
     history: History,
     rev_id_counter: usize,
+    last_edit_time: usize,
 }
 
 impl Document {
@@ -26,6 +20,7 @@ impl Document {
             data: delta,
             history: History::new(),
             rev_id_counter: 1,
+            last_edit_time: 0,
         }
     }
 
@@ -42,10 +37,12 @@ impl Document {
         if attributes == Attributes::Empty {
             attributes = Attributes::Follow;
         }
+        let mut delta = Delta::new();
         let insert = Builder::insert(text).attributes(attributes).build();
         let interval = Interval::new(index, index);
+        delta.add(insert);
 
-        self.update_with_op(insert, interval)
+        self.update_with_op(&delta, interval)
     }
 
     pub fn format(
@@ -68,15 +65,14 @@ impl Document {
 
     pub fn undo(&mut self) -> Result<UndoResult, OTError> {
         match self.history.undo() {
-            None => Err(ErrorBuilder::new(UndoFail).build()),
+            None => Err(ErrorBuilder::new(UndoFail)
+                .msg("Undo stack is empty")
+                .build()),
             Some(undo_delta) => {
-                log::debug!("undo: {:?}", undo_delta);
-                let composed_delta = self.data.compose(&undo_delta)?;
-                let redo_delta = undo_delta.invert(&self.data);
-                log::debug!("computed redo: {:?}", redo_delta);
-                let result = UndoResult::success(composed_delta.target_len as usize);
-                self.data = composed_delta;
-                self.history.add_redo(redo_delta);
+                let (new_delta, inverted_delta) = self.invert_change(&undo_delta)?;
+                let result = UndoResult::success(new_delta.target_len as usize);
+                self.data = new_delta;
+                self.history.add_redo(inverted_delta);
 
                 Ok(result)
             },
@@ -87,22 +83,29 @@ impl Document {
         match self.history.redo() {
             None => Err(ErrorBuilder::new(RedoFail).build()),
             Some(redo_delta) => {
-                log::debug!("redo: {:?}", redo_delta);
-                let new_delta = self.data.compose(&redo_delta)?;
+                let (new_delta, inverted_delta) = self.invert_change(&redo_delta)?;
                 let result = UndoResult::success(new_delta.target_len as usize);
-                let undo_delta = redo_delta.invert(&self.data);
-                log::debug!("computed undo: {:?}", undo_delta);
                 self.data = new_delta;
 
-                self.history.add_undo(undo_delta);
+                self.history.add_undo(inverted_delta);
                 Ok(result)
             },
         }
     }
 
-    pub fn delete(&mut self, interval: Interval) -> Result<(), OTError> {
-        let delete = Builder::delete(interval.size()).build();
-        self.update_with_op(delete, interval)
+    pub fn replace(&mut self, interval: Interval, s: &str) -> Result<(), OTError> {
+        let mut delta = Delta::default();
+        if !s.is_empty() {
+            let insert = Builder::insert(s).attributes(Attributes::Follow).build();
+            delta.add(insert);
+        }
+
+        if !interval.is_empty() {
+            let delete = Builder::delete(interval.size()).build();
+            delta.add(delete);
+        }
+
+        self.update_with_op(&delta, interval)
     }
 
     pub fn to_json(&self) -> String { self.data.to_json() }
@@ -113,7 +116,7 @@ impl Document {
 
     pub fn set_data(&mut self, data: Delta) { self.data = data; }
 
-    fn update_with_op(&mut self, op: Operation, interval: Interval) -> Result<(), OTError> {
+    fn update_with_op(&mut self, delta: &Delta, interval: Interval) -> Result<(), OTError> {
         let mut new_delta = Delta::default();
         let (prefix, interval, suffix) = split_length_with_interval(self.data.target_len, interval);
 
@@ -122,33 +125,26 @@ impl Document {
             let intervals = split_interval_with_delta(&self.data, &prefix);
             intervals.into_iter().for_each(|i| {
                 let attributes = self.data.get_attributes(i);
-                log::debug!("prefix attribute: {:?}, interval: {:?}", attributes, i);
+                log::trace!("prefix attribute: {:?}, interval: {:?}", attributes, i);
                 new_delta.retain(i.size() as usize, attributes);
             });
         }
 
-        log::debug!("add new op: {:?}", op);
-        new_delta.add(op);
+        delta.ops.iter().for_each(|op| {
+            new_delta.add(op.clone());
+        });
 
         // suffix
         if suffix.is_empty() == false {
             let intervals = split_interval_with_delta(&self.data, &suffix);
             intervals.into_iter().for_each(|i| {
                 let attributes = self.data.get_attributes(i);
-                log::debug!("suffix attribute: {:?}, interval: {:?}", attributes, i);
+                log::trace!("suffix attribute: {:?}, interval: {:?}", attributes, i);
                 new_delta.retain(i.size() as usize, attributes);
             });
         }
 
-        // c = a.compose(b)
-        // d = b.invert(a)
-        // a = c.compose(d)
-        let composed_delta = self.data.compose(&new_delta)?;
-        let undo_delta = new_delta.invert(&self.data);
-
-        self.rev_id_counter += 1;
-        self.history.record(undo_delta);
-        self.data = composed_delta;
+        self.data = self.record_change(&new_delta)?;
         Ok(())
     }
 
@@ -159,7 +155,7 @@ impl Document {
     ) -> Result<(), OTError> {
         let old_attributes = self.data.get_attributes(interval);
         log::debug!(
-            "merge attributes: {:?}, with old: {:?}",
+            "combine attributes: {:?} : {:?}",
             attributes,
             old_attributes
         );
@@ -172,21 +168,54 @@ impl Document {
             Attributes::Empty => Attributes::Empty,
         };
 
-        log::debug!("new attributes: {:?}", new_attributes);
+        log::debug!("combined result: {:?}", new_attributes);
         let retain = Builder::retain(interval.size())
             .attributes(new_attributes)
             .build();
 
-        log::debug!(
-            "Update delta with new attributes: {:?} at: {:?}",
-            retain,
-            interval
-        );
+        let mut delta = Delta::new();
+        delta.add(retain);
 
-        self.update_with_op(retain, interval)
+        self.update_with_op(&delta, interval)
     }
 
     fn next_rev_id(&self) -> RevId { RevId(self.rev_id_counter) }
+
+    fn record_change(&mut self, delta: &Delta) -> Result<Delta, OTError> {
+        let (composed_delta, mut undo_delta) = self.invert_change(&delta)?;
+        self.rev_id_counter += 1;
+
+        let now = chrono::Utc::now().timestamp_millis() as usize;
+        if now - self.last_edit_time < RECORD_THRESHOLD {
+            if let Some(last_delta) = self.history.undo() {
+                log::debug!("compose previous change");
+                log::debug!("current = {}", undo_delta);
+                log::debug!("previous = {}", last_delta);
+                undo_delta = undo_delta.compose(&last_delta)?;
+            }
+        } else {
+            self.last_edit_time = now;
+        }
+
+        if !undo_delta.is_empty() {
+            log::debug!("record change: {}", undo_delta);
+            self.history.record(undo_delta);
+        }
+
+        Ok(composed_delta)
+    }
+
+    fn invert_change(&self, change: &Delta) -> Result<(Delta, Delta), OTError> {
+        // c = a.compose(b)
+        // d = b.invert(a)
+        // a = c.compose(d)
+        log::debug!("👉invert change {}", change);
+        let new_delta = self.data.compose(change)?;
+        let mut inverted_delta = change.invert(&self.data);
+        // trim(&mut inverted_delta);
+
+        Ok((new_delta, inverted_delta))
+    }
 }
 
 fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) {
@@ -216,3 +245,17 @@ fn split_interval_with_delta(delta: &Delta, interval: &Interval) -> Vec<Interval
     });
     new_intervals
 }
+
+pub fn trim(delta: &mut Delta) {
+    let remove_last = match delta.ops.last() {
+        None => false,
+        Some(op) => match op {
+            Operation::Delete(_) => false,
+            Operation::Retain(retain) => retain.is_plain(),
+            Operation::Insert(_) => false,
+        },
+    };
+    if remove_last {
+        delta.ops.pop();
+    }
+}

+ 6 - 6
rust-lib/flowy-ot/src/client/history.rs

@@ -36,7 +36,7 @@ impl UndoResult {
 pub struct History {
     cur_undo: usize,
     undos: Vec<Delta>,
-    redos: Vec<Delta>,
+    redoes: Vec<Delta>,
     capacity: usize,
 }
 
@@ -45,25 +45,25 @@ impl History {
         History {
             cur_undo: 1,
             undos: Vec::new(),
-            redos: Vec::new(),
+            redoes: Vec::new(),
             capacity: 20,
         }
     }
 
     pub fn can_undo(&self) -> bool { !self.undos.is_empty() }
 
-    pub fn can_redo(&self) -> bool { !self.redos.is_empty() }
+    pub fn can_redo(&self) -> bool { !self.redoes.is_empty() }
 
     pub fn add_undo(&mut self, delta: Delta) { self.undos.push(delta); }
 
-    pub fn add_redo(&mut self, delta: Delta) { self.redos.push(delta); }
+    pub fn add_redo(&mut self, delta: Delta) { self.redoes.push(delta); }
 
     pub fn record(&mut self, delta: Delta) {
         if delta.ops.is_empty() {
             return;
         }
 
-        self.redos.clear();
+        self.redoes.clear();
         self.add_undo(delta);
 
         if self.undos.len() > self.capacity {
@@ -84,7 +84,7 @@ impl History {
             return None;
         }
 
-        let delta = self.redos.pop().unwrap();
+        let delta = self.redoes.pop().unwrap();
         Some(delta)
     }
 }

+ 12 - 19
rust-lib/flowy-ot/src/core/attributes/attributes.rs

@@ -1,5 +1,5 @@
 use crate::core::{Attribute, AttributesData, AttributesRule, Operation};
-use std::{collections::HashMap, fmt};
+use std::{collections::HashMap, fmt, fmt::Formatter};
 
 #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
 #[serde(untagged)]
@@ -11,6 +11,16 @@ pub enum Attributes {
     Empty,
 }
 
+impl fmt::Display for Attributes {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match self {
+            Attributes::Follow => f.write_str("follow"),
+            Attributes::Custom(data) => f.write_fmt(format_args!("{:?}", data.inner)),
+            Attributes::Empty => f.write_str("empty"),
+        }
+    }
+}
+
 impl Attributes {
     pub fn data(&self) -> Option<AttributesData> {
         match self {
@@ -25,23 +35,6 @@ 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(())
-    }
-}
-
 pub(crate) fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
     match operation {
         None => None,
@@ -139,7 +132,7 @@ fn compose_attributes(left: Attributes, right: Attributes) -> Attributes {
     };
 
     log::trace!("composed_attributes: a: {:?}", attr);
-    attr.apply_rule()
+    attr
 }
 
 fn transform_attributes(left: Attributes, right: Attributes) -> Attributes {

+ 1 - 3
rust-lib/flowy-ot/src/core/attributes/data.rs

@@ -18,9 +18,7 @@ impl AttributesData {
         }
     }
 
-    pub fn is_plain(&self) -> bool {
-        self.inner.values().filter(|v| !should_remove(v)).count() == 0
-    }
+    pub fn is_plain(&self) -> bool { self.inner.is_empty() }
 
     pub fn remove(&mut self, attribute: &Attribute) {
         self.inner.insert(attribute.clone(), REMOVE_FLAG.to_owned());

+ 76 - 56
rust-lib/flowy-ot/src/core/delta.rs

@@ -43,10 +43,12 @@ impl<T: AsRef<str>> From<T> for Delta {
 
 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));
-        // }
+        // f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?;
+        f.write_str("[ ")?;
+        for op in &self.ops {
+            f.write_fmt(format_args!("{} ", op));
+        }
+        f.write_str("]")?;
         Ok(())
     }
 }
@@ -184,7 +186,11 @@ impl Delta {
                     match retain.cmp(&o_retain) {
                         Ordering::Less => {
                             new_delta.retain(retain.n, composed_attrs);
-                            next_op2 = Some(Builder::retain(o_retain.n - retain.n).build());
+                            next_op2 = Some(
+                                Builder::retain(o_retain.n - retain.n)
+                                    .attributes(o_retain.attributes.clone())
+                                    .build(),
+                            );
                             next_op1 = ops1.next();
                         },
                         std::cmp::Ordering::Equal => {
@@ -225,11 +231,13 @@ impl Delta {
                     }
                 },
                 (Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => {
-                    let composed_attrs = compose_operation(&next_op1, &next_op2);
+                    let mut composed_attrs = compose_operation(&next_op1, &next_op2);
+                    composed_attrs = composed_attrs.apply_rule();
+
                     log::debug!(
-                        "[insert:{} - retain:{}]: {:?}",
-                        insert.s,
-                        o_retain.n,
+                        "compose: [{} - {}], composed_attrs: {}",
+                        insert,
+                        o_retain,
                         composed_attrs
                     );
                     match (insert.num_chars()).cmp(o_retain) {
@@ -481,7 +489,9 @@ impl Delta {
         if other.is_empty() {
             return inverted;
         }
-
+        log::debug!("🌜Calculate invert delta");
+        log::debug!("current: {}", self);
+        log::debug!("other: {}", other);
         let mut index = 0;
         for op in &self.ops {
             let len: usize = op.length() as usize;
@@ -494,19 +504,25 @@ impl Delta {
                     match op.has_attribute() {
                         true => invert_from_other(&mut inverted, other, op, index, index + len),
                         false => {
-                            log::debug!("invert retain op: {:?}", op);
+                            log::debug!(
+                                "invert retain: {} by retain {} {}",
+                                op,
+                                len,
+                                op.get_attributes()
+                            );
                             inverted.retain(len as usize, op.get_attributes())
                         },
                     }
                     index += len;
                 },
                 Operation::Insert(_) => {
-                    log::debug!("invert insert op: {:?}", op);
+                    log::debug!("invert insert: {} by delete {}", op, len);
                     inverted.delete(len as usize);
                 },
             }
         }
 
+        log::debug!("🌛invert result: {}", inverted);
         inverted
     }
 
@@ -523,7 +539,8 @@ impl Delta {
     pub fn is_empty(&self) -> bool { self.ops.is_empty() }
 
     pub fn ops_in_interval(&self, mut interval: Interval) -> Vec<Operation> {
-        log::debug!("ops in delta: {:?}, at {:?}", self, interval);
+        log::debug!("try get ops in delta: {} at {}", self, interval);
+
         let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
         let mut ops_iter = self.ops.iter();
         let mut maybe_next_op = ops_iter.next();
@@ -534,32 +551,32 @@ impl Delta {
             if offset < interval.start {
                 let next_op_i = Interval::new(offset, offset + next_op.length());
                 let intersect = next_op_i.intersect(interval);
-                if !intersect.is_empty() {
-                    // if the op's length larger than the interval size, just shrink the op to that
-                    // interval Checkout the delta_get_ops_in_interval_3 test for more details.
-                    // ┌──────────────┐
-                    // │ 1 2 3 4 5 6  │
-                    // └───────▲───▲──┘
-                    //         │   │
-                    //        [3, 5)
-                    // op = "45"
-                    if let Some(new_op) = next_op.shrink(intersect) {
-                        offset += min(intersect.end, next_op.length());
-                        interval = Interval::new(offset, interval.end);
+                if intersect.is_empty() {
+                    offset += next_op_i.size();
+                } else {
+                    if let Some(new_op) = next_op.shrink(intersect.translate_neg(offset)) {
+                        // shrink the op to fit the intersect range
+                        // ┌──────────────┐
+                        // │ 1 2 3 4 5 6  │
+                        // └───────▲───▲──┘
+                        //         │   │
+                        //        [3, 5)
+                        // op = "45"
                         ops.push(new_op);
                     }
-                } else {
-                    offset += next_op_i.size();
+                    offset = intersect.end;
+                    interval = Interval::new(offset, interval.end);
                 }
             } else {
                 // the interval passed in the shrink function is base on the op not the delta.
                 if let Some(new_op) = next_op.shrink(interval.translate_neg(offset)) {
                     ops.push(new_op);
                 }
-                // take the small value between interval.size() and next_op.length().
                 // for example: extract the ops from three insert ops with interval [2,5). the
-                // interval size is larger than the op. Run the
-                // delta_get_ops_in_interval_4 for more details.
+                // interval size is larger than the op. Moving the offset to extract each part.
+                // Each step would be the small value between interval.size() and
+                // next_op.length(). Checkout the delta_get_ops_in_interval_4 for more details.
+                //
                 // ┌──────┐  ┌──────┐  ┌──────┐
                 // │ 1 2  │  │ 3 4  │  │ 5 6  │
                 // └──────┘  └─▲────┘  └───▲──┘
@@ -570,6 +587,7 @@ impl Delta {
             }
             maybe_next_op = ops_iter.next();
         }
+        log::debug!("did get ops : {:?}", ops);
         ops
     }
 
@@ -614,36 +632,38 @@ impl Delta {
 }
 
 fn invert_from_other(
-    inverted: &mut Delta,
+    base: &mut Delta,
     other: &Delta,
     operation: &Operation,
     start: usize,
     end: usize,
 ) {
-    log::debug!("invert op: {:?} [{}:{}]", operation, start, end);
-    let ops = other.ops_in_interval(Interval::new(start, end));
-    ops.into_iter().for_each(|other_op| {
-        match operation {
-            Operation::Delete(_) => {
-                log::debug!("add: {}", other_op);
-                inverted.add(other_op);
-            },
-            Operation::Retain(_) => {
-                log::debug!(
-                    "Start invert attributes: {:?}, {:?}",
-                    operation.get_attributes(),
-                    other_op.get_attributes()
-                );
-                let inverted_attrs =
-                    invert_attributes(operation.get_attributes(), other_op.get_attributes());
-                log::debug!("End invert attributes: {:?}", inverted_attrs);
-                log::debug!("invert retain: {}, {}", other_op.length(), inverted_attrs);
-                inverted.retain(other_op.length(), inverted_attrs);
-            },
-            Operation::Insert(_) => {
-                // Impossible to here
-                panic!()
-            },
-        }
+    log::debug!("invert op: {} [{}:{}]", operation, start, end);
+    let other_ops = other.ops_in_interval(Interval::new(start, end));
+    other_ops.into_iter().for_each(|other_op| match operation {
+        Operation::Delete(n) => {
+            log::debug!("invert delete: {} by add {}", n, other_op);
+            base.add(other_op);
+        },
+        Operation::Retain(retain) => {
+            log::debug!(
+                "invert attributes: {:?}, {:?}",
+                operation.get_attributes(),
+                other_op.get_attributes()
+            );
+            let inverted_attrs =
+                invert_attributes(operation.get_attributes(), other_op.get_attributes());
+            log::debug!("invert result: {:?}", inverted_attrs);
+            log::debug!(
+                "invert retain: {} by retain len: {}, {}",
+                retain,
+                other_op.length(),
+                inverted_attrs
+            );
+            base.retain(other_op.length(), inverted_attrs);
+        },
+        Operation::Insert(_) => {
+            log::error!("Impossible to here. Insert operation should be treated as delete")
+        },
     });
 }

+ 45 - 19
rust-lib/flowy-ot/src/core/operation/operation.rs

@@ -1,5 +1,6 @@
 use crate::core::{Attributes, Builder, Interval};
 use bytecount::num_chars;
+use serde::__private::Formatter;
 use std::{
     cmp::min,
     fmt,
@@ -60,14 +61,6 @@ impl Operation {
         }
     }
 
-    pub fn is_plain(&self) -> bool {
-        match self.get_attributes() {
-            Attributes::Follow => true,
-            Attributes::Custom(data) => data.is_plain(),
-            Attributes::Empty => true,
-        }
-    }
-
     pub fn length(&self) -> usize {
         match self {
             Operation::Delete(n) => *n,
@@ -105,23 +98,19 @@ impl Operation {
 
 impl fmt::Display for Operation {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str("{");
         match self {
             Operation::Delete(n) => {
                 f.write_fmt(format_args!("delete: {}", n))?;
             },
             Operation::Retain(r) => {
-                f.write_fmt(format_args!(
-                    "retain: {}, attributes: {}",
-                    r.n, r.attributes
-                ))?;
+                f.write_fmt(format_args!("{}", r))?;
             },
             Operation::Insert(i) => {
-                f.write_fmt(format_args!(
-                    "insert: {}, attributes: {}",
-                    i.s, i.attributes
-                ))?;
+                f.write_fmt(format_args!("{}", i))?;
             },
         }
+        f.write_str("}");
         Ok(())
     }
 }
@@ -134,9 +123,23 @@ pub struct Retain {
     pub attributes: Attributes,
 }
 
+impl fmt::Display for Retain {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.write_fmt(format_args!(
+            "retain: {}, attributes: {}",
+            self.n, self.attributes
+        ))
+    }
+}
+
 impl Retain {
     pub fn merge_or_new_op(&mut self, n: usize, attributes: Attributes) -> Option<Operation> {
-        log::debug!("merge_retain_or_new_op: {:?}, {:?}", n, attributes);
+        log::debug!(
+            "merge_retain_or_new_op: len: {:?}, l: {} - r: {}",
+            n,
+            self.attributes,
+            attributes
+        );
 
         match &attributes {
             Attributes::Follow => {
@@ -146,16 +149,22 @@ impl Retain {
             },
             Attributes::Custom(_) | Attributes::Empty => {
                 if self.attributes == attributes {
-                    log::debug!("Attribute equal");
                     self.n += n;
                     None
                 } else {
-                    log::debug!("New retain op");
                     Some(Builder::retain(n).attributes(attributes).build())
                 }
             },
         }
     }
+
+    pub fn is_plain(&self) -> bool {
+        match &self.attributes {
+            Attributes::Follow => true,
+            Attributes::Custom(data) => data.is_plain(),
+            Attributes::Empty => true,
+        }
+    }
 }
 
 impl std::convert::From<usize> for Retain {
@@ -186,6 +195,23 @@ pub struct Insert {
     pub attributes: Attributes,
 }
 
+impl fmt::Display for Insert {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let mut s = self.s.clone();
+        if s.ends_with("\n") {
+            s.pop();
+            if s.is_empty() {
+                s = "new_line".to_owned();
+            }
+        }
+
+        f.write_fmt(format_args!(
+            "insert: {}, attributes: {}",
+            s, self.attributes
+        ))
+    }
+}
+
 impl Insert {
     pub fn as_bytes(&self) -> &[u8] { self.s.as_bytes() }
 

+ 22 - 4
rust-lib/flowy-ot/tests/attribute_test.rs

@@ -352,7 +352,7 @@ fn delta_compose_attr_delta_with_no_attr_delta_test() {
 }
 
 #[test]
-fn delta_delete_heading() {
+fn delta_replace_heading() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
         AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@@ -364,7 +364,7 @@ fn delta_delete_heading() {
 }
 
 #[test]
-fn delta_delete_trailing() {
+fn delta_replace_trailing() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
         AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@@ -376,7 +376,7 @@ fn delta_delete_trailing() {
 }
 
 #[test]
-fn delta_delete_middle() {
+fn delta_replace_middle() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
         AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@@ -390,7 +390,7 @@ fn delta_delete_middle() {
 }
 
 #[test]
-fn delta_delete_all() {
+fn delta_replace_all() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
         AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@@ -400,3 +400,21 @@ fn delta_delete_all() {
 
     OpTester::new().run_script(ops);
 }
+
+#[test]
+fn delta_replace_with_text() {
+    let ops = vec![
+        InsertBold(0, "123456", Interval::new(0, 6)),
+        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        Replace(0, Interval::new(0, 3), "ab"),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"ab"},
+            {"insert":"456","attributes":{"bold":"true"}}]
+            "#,
+        ),
+    ];
+
+    OpTester::new().run_script(ops);
+}

+ 16 - 3
rust-lib/flowy-ot/tests/helper/mod.rs

@@ -1,7 +1,7 @@
 use derive_more::Display;
 use flowy_ot::{client::Document, core::*};
 use rand::{prelude::*, Rng as WrappedRng};
-use std::sync::Once;
+use std::{sync::Once, thread::sleep_ms, time::Duration};
 
 #[derive(Clone, Debug, Display)]
 pub enum TestOp {
@@ -19,6 +19,9 @@ pub enum TestOp {
     #[display(fmt = "Delete")]
     Delete(usize, Interval),
 
+    #[display(fmt = "Replace")]
+    Replace(usize, Interval, &'static str),
+
     #[display(fmt = "Italic")]
     Italic(usize, Interval, bool),
 
@@ -35,6 +38,9 @@ pub enum TestOp {
     #[display(fmt = "Redo")]
     Redo(usize),
 
+    #[display(fmt = "Wait")]
+    Wait(usize),
+
     #[display(fmt = "AssertStr")]
     AssertStr(usize, &'static str),
 
@@ -51,7 +57,7 @@ impl OpTester {
         static INIT: Once = Once::new();
         INIT.call_once(|| {
             color_eyre::install().unwrap();
-            std::env::set_var("RUST_LOG", "info");
+            std::env::set_var("RUST_LOG", "debug");
             env_logger::init();
         });
 
@@ -71,7 +77,11 @@ impl OpTester {
             },
             TestOp::Delete(delta_i, interval) => {
                 let document = &mut self.documents[*delta_i];
-                document.delete(*interval).unwrap();
+                document.replace(*interval, "").unwrap();
+            },
+            TestOp::Replace(delta_i, interval, s) => {
+                let document = &mut self.documents[*delta_i];
+                document.replace(*interval, s).unwrap();
             },
             TestOp::InsertBold(delta_i, s, interval) => {
                 let document = &mut self.documents[*delta_i];
@@ -131,6 +141,9 @@ impl OpTester {
             TestOp::Redo(delta_i) => {
                 self.documents[*delta_i].redo().unwrap();
             },
+            TestOp::Wait(mills_sec) => {
+                std::thread::sleep(Duration::from_millis(*mills_sec as u64));
+            },
             TestOp::AssertStr(delta_i, expected) => {
                 assert_eq!(&self.documents[*delta_i].to_string(), expected);
             },

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

@@ -154,113 +154,3 @@ fn delta_invert_attribute_delta_with_attribute_delta() {
     ];
     OpTester::new().run_script(ops);
 }
-
-#[test]
-fn delta_get_ops_in_interval_1() {
-    let mut delta = Delta::default();
-    let insert_a = Builder::insert("123").build();
-    let insert_b = Builder::insert("4").build();
-
-    delta.add(insert_a.clone());
-    delta.add(insert_b.clone());
-
-    assert_eq!(
-        delta.ops_in_interval(Interval::new(0, 4)),
-        vec![delta.ops.last().unwrap().clone()]
-    );
-}
-
-#[test]
-fn delta_get_ops_in_interval_2() {
-    let mut delta = Delta::default();
-    let insert_a = Builder::insert("123").build();
-    let insert_b = Builder::insert("4").build();
-    let insert_c = Builder::insert("5").build();
-    let retain_a = Builder::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, 2)),
-        vec![Builder::insert("12").build()]
-    );
-
-    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(), Builder::retain(1).build()]
-    );
-
-    assert_eq!(
-        delta.ops_in_interval(Interval::new(0, 6)),
-        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.clone()]
-    );
-}
-
-#[test]
-fn delta_get_ops_in_interval_3() {
-    let mut delta = Delta::default();
-    let insert_a = Builder::insert("123456").build();
-    delta.add(insert_a.clone());
-    assert_eq!(
-        delta.ops_in_interval(Interval::new(3, 5)),
-        vec![Builder::insert("45").build()]
-    );
-}
-
-#[test]
-fn delta_get_ops_in_interval_4() {
-    let mut delta = Delta::default();
-    let insert_a = Builder::insert("12").build();
-    let insert_b = Builder::insert("34").build();
-    let insert_c = Builder::insert("56").build();
-
-    delta.ops.push(insert_a.clone());
-    delta.ops.push(insert_b.clone());
-    delta.ops.push(insert_c.clone());
-
-    assert_eq!(delta.ops_in_interval(Interval::new(0, 2)), vec![insert_a]);
-    assert_eq!(delta.ops_in_interval(Interval::new(2, 4)), vec![insert_b]);
-    assert_eq!(delta.ops_in_interval(Interval::new(4, 6)), vec![insert_c]);
-
-    assert_eq!(
-        delta.ops_in_interval(Interval::new(2, 5)),
-        vec![Builder::insert("34").build(), Builder::insert("5").build()]
-    );
-}
-
-#[test]
-fn delta_get_ops_in_interval_5() {
-    let mut delta = Delta::default();
-    let insert_a = Builder::insert("123456").build();
-    let insert_b = Builder::insert("789").build();
-    delta.ops.push(insert_a.clone());
-    delta.ops.push(insert_b.clone());
-    assert_eq!(
-        delta.ops_in_interval(Interval::new(4, 8)),
-        vec![Builder::insert("56").build(), Builder::insert("78").build()]
-    );
-}
-
-#[test]
-fn delta_get_ops_in_interval_6() {
-    let mut delta = Delta::default();
-    let insert_a = Builder::insert("12345678").build();
-    delta.add(insert_a.clone());
-    assert_eq!(
-        delta.ops_in_interval(Interval::new(4, 6)),
-        vec![Builder::insert("56").build()]
-    );
-}

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

@@ -5,6 +5,121 @@ use bytecount::num_chars;
 use flowy_ot::core::*;
 use helper::*;
 
+#[test]
+fn delta_get_ops_in_interval_1() {
+    let mut delta = Delta::default();
+    let insert_a = Builder::insert("123").build();
+    let insert_b = Builder::insert("4").build();
+
+    delta.add(insert_a.clone());
+    delta.add(insert_b.clone());
+
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(0, 4)),
+        vec![delta.ops.last().unwrap().clone()]
+    );
+}
+
+#[test]
+fn delta_get_ops_in_interval_2() {
+    let mut delta = Delta::default();
+    let insert_a = Builder::insert("123").build();
+    let insert_b = Builder::insert("4").build();
+    let insert_c = Builder::insert("5").build();
+    let retain_a = Builder::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, 2)),
+        vec![Builder::insert("12").build()]
+    );
+
+    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(), Builder::retain(1).build()]
+    );
+
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(0, 6)),
+        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.clone()]
+    );
+}
+
+#[test]
+fn delta_get_ops_in_interval_3() {
+    let mut delta = Delta::default();
+    let insert_a = Builder::insert("123456").build();
+    delta.add(insert_a.clone());
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(3, 5)),
+        vec![Builder::insert("45").build()]
+    );
+}
+
+#[test]
+fn delta_get_ops_in_interval_4() {
+    let mut delta = Delta::default();
+    let insert_a = Builder::insert("12").build();
+    let insert_b = Builder::insert("34").build();
+    let insert_c = Builder::insert("56").build();
+
+    delta.ops.push(insert_a.clone());
+    delta.ops.push(insert_b.clone());
+    delta.ops.push(insert_c.clone());
+
+    assert_eq!(delta.ops_in_interval(Interval::new(0, 2)), vec![insert_a]);
+    assert_eq!(delta.ops_in_interval(Interval::new(2, 4)), vec![insert_b]);
+    assert_eq!(delta.ops_in_interval(Interval::new(4, 6)), vec![insert_c]);
+
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(2, 5)),
+        vec![Builder::insert("34").build(), Builder::insert("5").build()]
+    );
+}
+
+#[test]
+fn delta_get_ops_in_interval_5() {
+    let mut delta = Delta::default();
+    let insert_a = Builder::insert("123456").build();
+    let insert_b = Builder::insert("789").build();
+    delta.ops.push(insert_a.clone());
+    delta.ops.push(insert_b.clone());
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(4, 8)),
+        vec![Builder::insert("56").build(), Builder::insert("78").build()]
+    );
+
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(8, 9)),
+        vec![Builder::insert("9").build()]
+    );
+}
+
+#[test]
+fn delta_get_ops_in_interval_6() {
+    let mut delta = Delta::default();
+    let insert_a = Builder::insert("12345678").build();
+    delta.add(insert_a.clone());
+    assert_eq!(
+        delta.ops_in_interval(Interval::new(4, 6)),
+        vec![Builder::insert("56").build()]
+    );
+}
+
 #[test]
 fn lengths() {
     let mut delta = Delta::default();

+ 121 - 7
rust-lib/flowy-ot/tests/undo_redo_test.rs

@@ -1,7 +1,7 @@
 pub mod helper;
 
 use crate::helper::{TestOp::*, *};
-use flowy_ot::core::Interval;
+use flowy_ot::{client::RECORD_THRESHOLD, core::Interval};
 
 #[test]
 fn delta_undo_insert() {
@@ -19,6 +19,7 @@ fn delta_undo_insert2() {
     let ops = vec![
         Insert(0, "\n", 0),
         Insert(0, "123", 0),
+        Wait(RECORD_THRESHOLD),
         Insert(0, "456", 0),
         Undo(0),
         AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
@@ -43,11 +44,13 @@ fn delta_redo_insert() {
 }
 
 #[test]
-fn delta_redo_insert2() {
+fn delta_redo_insert_with_lagging() {
     let ops = vec![
         Insert(0, "\n", 0),
         Insert(0, "123", 0),
+        Wait(RECORD_THRESHOLD),
         Insert(0, "456", 3),
+        Wait(RECORD_THRESHOLD),
         AssertStr(0, "123456\n"),
         AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#),
         Undo(0),
@@ -67,6 +70,19 @@ fn delta_undo_attributes() {
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
         Undo(0),
+        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_undo_attributes_with_lagging() {
+    let ops = vec![
+        Insert(0, "\n", 0),
+        Insert(0, "123", 0),
+        Wait(RECORD_THRESHOLD),
+        Bold(0, Interval::new(0, 3), true),
+        Undo(0),
         AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
     ];
     OpTester::new().run_script(ops);
@@ -79,7 +95,7 @@ fn delta_redo_attributes() {
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
         Redo(0),
         AssertOpsJson(
             0,
@@ -90,31 +106,64 @@ fn delta_redo_attributes() {
 }
 
 #[test]
-fn delta_undo_delete() {
+fn delta_redo_attributes_with_lagging() {
     let ops = vec![
         Insert(0, "\n", 0),
         Insert(0, "123", 0),
+        Wait(RECORD_THRESHOLD),
         Bold(0, Interval::new(0, 3), true),
+        Undo(0),
+        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        Redo(0),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
+        ),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_undo_delete() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
         Delete(0, Interval::new(0, 3)),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertOpsJson(0, r#"[]"#),
         Undo(0),
+        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_undo_delete2() {
+    let ops = vec![
+        Insert(0, "\n", 0),
+        Insert(0, "123", 0),
+        Bold(0, Interval::new(0, 3), true),
+        Delete(0, Interval::new(0, 1)),
         AssertOpsJson(
             0,
             r#"[
-            {"insert":"123","attributes":{"bold":"true"}},
+            {"insert":"23","attributes":{"bold":"true"}},
             {"insert":"\n"}]
             "#,
         ),
+        Undo(0),
+        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
     ];
     OpTester::new().run_script(ops);
 }
 
 #[test]
-fn delta_undo_delete2() {
+fn delta_undo_delete2_with_lagging() {
     let ops = vec![
         Insert(0, "\n", 0),
         Insert(0, "123", 0),
+        Wait(RECORD_THRESHOLD),
         Bold(0, Interval::new(0, 3), true),
+        Wait(RECORD_THRESHOLD),
         Delete(0, Interval::new(0, 1)),
         AssertOpsJson(
             0,
@@ -148,3 +197,68 @@ fn delta_redo_delete() {
     ];
     OpTester::new().run_script(ops);
 }
+
+#[test]
+fn delta_undo_replace() {
+    let ops = vec![
+        Insert(0, "\n", 0),
+        Insert(0, "123", 0),
+        Bold(0, Interval::new(0, 3), true),
+        Replace(0, Interval::new(0, 2), "ab"),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"ab"},
+            {"insert":"3","attributes":{"bold":"true"}},{"insert":"\n"}]
+            "#,
+        ),
+        Undo(0),
+        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_undo_replace_with_lagging() {
+    let ops = vec![
+        Insert(0, "\n", 0),
+        Insert(0, "123", 0),
+        Wait(RECORD_THRESHOLD),
+        Bold(0, Interval::new(0, 3), true),
+        Wait(RECORD_THRESHOLD),
+        Replace(0, Interval::new(0, 2), "ab"),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"ab"},
+            {"insert":"3","attributes":{"bold":"true"}},{"insert":"\n"}]
+            "#,
+        ),
+        Undo(0),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
+        ),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_redo_replace() {
+    let ops = vec![
+        Insert(0, "\n", 0),
+        Insert(0, "123", 0),
+        Bold(0, Interval::new(0, 3), true),
+        Replace(0, Interval::new(0, 2), "ab"),
+        Undo(0),
+        Redo(0),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"ab"},
+            {"insert":"3","attributes":{"bold":"true"}},{"insert":"\n"}]
+            "#,
+        ),
+    ];
+    OpTester::new().run_script(ops);
+}