浏览代码

add extensions

appflowy 3 年之前
父节点
当前提交
15c3a821ec

+ 0 - 3
app_flowy/packages/flowy_editor/lib/src/model/document/history.dart

@@ -53,10 +53,7 @@ class History {
 
     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;
     }

+ 15 - 15
app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart

@@ -40,21 +40,21 @@ class Rules {
   final List<Rule> _rules;
 
   static final Rules _instance = Rules([
-    const FormatLinkAtCaretPositionRule(),
-    const ResolveLineFormatRule(),
-    const ResolveInlineFormatRule(),
-    const InsertEmbedsRule(),
-    const ForceNewlineForInsertsAroundEmbedRule(),
-    const AutoExitBlockRule(),
-    const PreserveBlockStyleOnInsertRule(),
-    const PreserveLineStyleOnSplitRule(),
-    const ResetLineFormatOnNewLineRule(),
-    const AutoFormatLinksRule(),
-    const PreserveInlineStylesRule(),
-    const CatchAllInsertRule(),
-    const EnsureEmbedLineRule(),
-    const PreserveLineStyleOnMergeRule(),
-    const CatchAllDeleteRule(),
+    // const FormatLinkAtCaretPositionRule(),
+    // const ResolveLineFormatRule(),
+    // const ResolveInlineFormatRule(),
+    // const InsertEmbedsRule(),
+    // const ForceNewlineForInsertsAroundEmbedRule(),
+    // const AutoExitBlockRule(),
+    // const PreserveBlockStyleOnInsertRule(),
+    // const PreserveLineStyleOnSplitRule(),
+    // const ResetLineFormatOnNewLineRule(),
+    // const AutoFormatLinksRule(),
+    // const PreserveInlineStylesRule(),
+    // const CatchAllInsertRule(),
+    // const EnsureEmbedLineRule(),
+    // const PreserveLineStyleOnMergeRule(),
+    // const CatchAllDeleteRule(),
   ]);
 
   static Rules getInstance() => _instance;

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

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

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

@@ -24,7 +24,7 @@ impl Document {
         }
     }
 
-    pub fn edit(&mut self, index: usize, text: &str) -> Result<(), OTError> {
+    pub fn insert(&mut self, index: usize, text: &str) -> Result<(), OTError> {
         if self.data.target_len < index {
             log::error!(
                 "{} out of bounds. should 0..{}",
@@ -59,6 +59,21 @@ impl Document {
         self.update_with_attribute(attributes, 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 can_undo(&self) -> bool { self.history.can_undo() }
 
     pub fn can_redo(&self) -> bool { self.history.can_redo() }
@@ -93,21 +108,6 @@ impl Document {
         }
     }
 
-    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() }
 
     pub fn to_string(&self) -> String { self.data.apply("").unwrap() }

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

@@ -1,5 +1,6 @@
 mod document;
 mod history;
+mod view;
 
 pub use document::*;
 pub use history::*;

+ 23 - 0
rust-lib/flowy-ot/src/client/view/extension.rs

@@ -0,0 +1,23 @@
+use crate::{
+    client::{view::insert_ext::*, Document},
+    core::{Attributes, Interval},
+};
+use lazy_static::lazy_static;
+
+pub trait InsertExt {
+    fn apply(document: &Document, s: &str, interval: Interval);
+}
+
+pub trait FormatExt {
+    fn apply(document: &Document, interval: Interval, attributes: Attributes);
+}
+
+pub trait DeleteExt {
+    fn apply(document: &Document, interval: Interval);
+}
+
+lazy_static! {
+    static ref INSERT_EXT: Vec<Box<InsertExt>> = vec![PreserveInlineStyleExt::new(),];
+    static ref FORMAT_EXT: Vec<Box<FormatExt>> = vec![];
+    static ref DELETE_EXT: Vec<Box<DeleteExt>> = vec![];
+}

+ 14 - 0
rust-lib/flowy-ot/src/client/view/insert_ext.rs

@@ -0,0 +1,14 @@
+use crate::{
+    client::{view::InsertExt, Document},
+    core::Interval,
+};
+
+pub struct PreserveInlineStyleExt {}
+
+impl PreserveInlineStyleExt {
+    pub fn new() -> Self {}
+}
+
+impl InsertExt for PreserveInlineStyleExt {
+    fn apply(document: &Document, s: &str, interval: Interval) { unimplemented!() }
+}

+ 6 - 0
rust-lib/flowy-ot/src/client/view/mod.rs

@@ -0,0 +1,6 @@
+mod extension;
+mod insert_ext;
+
+pub use extension::*;
+
+pub use insert_ext::*;

+ 98 - 0
rust-lib/flowy-ot/src/core/delta/cursor.rs

@@ -0,0 +1,98 @@
+use crate::core::{Delta, Interval, Operation};
+use std::{cmp::min, slice::Iter};
+
+pub struct Cursor<'a> {
+    delta: &'a Delta,
+    interval: Interval,
+    iterator: Iter<'a, Operation>,
+    offset: usize,
+}
+
+impl<'a> Cursor<'a> {
+    pub fn new(delta: &'a Delta, interval: Interval) -> Cursor<'a> {
+        let mut cursor = Self {
+            delta,
+            interval,
+            iterator: delta.ops.iter(),
+            offset: 0,
+        };
+        cursor
+    }
+
+    pub fn next_op(&mut self) -> Option<Operation> {
+        let mut next_op = self.iterator.next();
+        let mut find_op = None;
+
+        while find_op.is_none() && next_op.is_some() {
+            let op = next_op.unwrap();
+            if self.offset < self.interval.start {
+                let intersect =
+                    Interval::new(self.offset, self.offset + op.length()).intersect(self.interval);
+                if intersect.is_empty() {
+                    self.offset += op.length();
+                } else {
+                    if let Some(new_op) = op.shrink(intersect.translate_neg(self.offset)) {
+                        // shrink the op to fit the intersect range
+                        // ┌──────────────┐
+                        // │ 1 2 3 4 5 6  │
+                        // └───────▲───▲──┘
+                        //         │   │
+                        //        [3, 5)
+                        // op = "45"
+                        find_op = Some(new_op);
+                    }
+                    self.offset = intersect.end;
+                }
+            } else {
+                // the interval passed in the shrink function is base on the op not the delta.
+                if let Some(new_op) = op.shrink(self.interval.translate_neg(self.offset)) {
+                    find_op = Some(new_op);
+                }
+                // for example: extract the ops from three insert ops with interval [2,5). the
+                // 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  │
+                // └──────┘  └─▲────┘  └───▲──┘
+                //             │  [2, 5)   │
+                //
+                self.offset += min(self.interval.size(), op.length());
+            }
+
+            match find_op {
+                None => next_op = self.iterator.next(),
+                Some(_) => self.interval.start = self.offset,
+            }
+        }
+
+        find_op
+    }
+}
+
+pub struct DeltaIter<'a> {
+    cursor: Cursor<'a>,
+    interval: Interval,
+}
+
+impl<'a> DeltaIter<'a> {
+    pub fn new(delta: &'a Delta, interval: Interval) -> Self {
+        let cursor = Cursor::new(delta, interval);
+        Self { cursor, interval }
+    }
+
+    pub fn ops(&mut self) -> Vec<Operation> { self.collect::<Vec<_>>() }
+}
+
+impl<'a> Iterator for DeltaIter<'a> {
+    type Item = Operation;
+
+    fn next(&mut self) -> Option<Self::Item> { self.cursor.next_op() }
+}
+#[cfg(test)]
+mod tests {
+
+    #[test]
+    fn test() {}
+}

+ 2 - 55
rust-lib/flowy-ot/src/core/delta.rs → rust-lib/flowy-ot/src/core/delta/delta.rs

@@ -1,5 +1,5 @@
 use crate::{
-    core::{attributes::*, operation::*, Interval},
+    core::{attributes::*, operation::*, DeltaIter, Interval},
     errors::{ErrorBuilder, OTError, OTErrorCode},
 };
 use bytecount::num_chars;
@@ -530,59 +530,6 @@ 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!("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();
-        let mut offset: usize = 0;
-
-        while maybe_next_op.is_some() {
-            let next_op = maybe_next_op.take().unwrap();
-            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() {
-                    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);
-                    }
-                    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);
-                }
-                // for example: extract the ops from three insert ops with interval [2,5). the
-                // 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  │
-                // └──────┘  └─▲────┘  └───▲──┘
-                //             │  [2, 5)   │
-                //
-                offset += min(interval.size(), next_op.length());
-                interval = Interval::new(offset, interval.end);
-            }
-            maybe_next_op = ops_iter.next();
-        }
-        log::debug!("did get ops : {:?}", ops);
-        ops
-    }
-
     pub fn get_attributes(&self, interval: Interval) -> Attributes {
         let mut attributes_data = AttributesData::new();
         let mut offset: usize = 0;
@@ -626,7 +573,7 @@ fn invert_from_other(
     end: usize,
 ) {
     log::debug!("invert op: {} [{}:{}]", operation, start, end);
-    let other_ops = other.ops_in_interval(Interval::new(start, end));
+    let other_ops = DeltaIter::new(other, Interval::new(start, end)).ops();
     other_ops.into_iter().for_each(|other_op| match operation {
         Operation::Delete(n) => {
             log::debug!("invert delete: {} by add {}", n, other_op);

+ 5 - 0
rust-lib/flowy-ot/src/core/delta/mod.rs

@@ -0,0 +1,5 @@
+mod cursor;
+mod delta;
+
+pub use cursor::*;
+pub use delta::*;

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

@@ -73,7 +73,7 @@ impl OpTester {
         match op {
             TestOp::Insert(delta_i, s, index) => {
                 let document = &mut self.documents[*delta_i];
-                document.edit(*index, s).unwrap();
+                document.insert(*index, s).unwrap();
             },
             TestOp::Delete(delta_i, interval) => {
                 let document = &mut self.documents[*delta_i];
@@ -85,7 +85,7 @@ impl OpTester {
             },
             TestOp::InsertBold(delta_i, s, interval) => {
                 let document = &mut self.documents[*delta_i];
-                document.edit(interval.start, s).unwrap();
+                document.insert(interval.start, s).unwrap();
                 document.format(*interval, Attribute::Bold, true).unwrap();
             },
             TestOp::Bold(delta_i, interval, enable) => {

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

@@ -1,156 +0,0 @@
-pub mod helper;
-use crate::helper::{TestOp::*, *};
-use flowy_ot::core::{Builder, Delta, Interval};
-
-#[test]
-fn delta_invert_no_attribute_delta() {
-    let mut delta = Delta::default();
-    delta.add(Builder::insert("123").build());
-
-    let mut change = Delta::default();
-    change.add(Builder::retain(3).build());
-    change.add(Builder::insert("456").build());
-    let undo = change.invert(&delta);
-
-    let new_delta = delta.compose(&change).unwrap();
-    let delta_after_undo = new_delta.compose(&undo).unwrap();
-
-    assert_eq!(delta_after_undo, delta);
-}
-
-#[test]
-fn delta_invert_no_attribute_delta2() {
-    let ops = vec![
-        Insert(0, "123", 0),
-        Insert(1, "4567", 0),
-        Invert(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
-    ];
-    OpTester::new().run_script(ops);
-}
-
-#[test]
-fn delta_invert_attribute_delta_with_no_attribute_delta() {
-    let ops = vec![
-        Insert(0, "123", 0),
-        Bold(0, Interval::new(0, 3), true),
-        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
-        Insert(1, "4567", 0),
-        Invert(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
-    ];
-    OpTester::new().run_script(ops);
-}
-
-#[test]
-fn delta_invert_attribute_delta_with_no_attribute_delta2() {
-    let ops = vec![
-        Insert(0, "123", 0),
-        Bold(0, Interval::new(0, 3), true),
-        Insert(0, "456", 3),
-        AssertOpsJson(
-            0,
-            r#"[
-            {"insert":"123456","attributes":{"bold":"true"}}]
-            "#,
-        ),
-        Italic(0, Interval::new(2, 4), true),
-        AssertOpsJson(
-            0,
-            r#"[
-            {"insert":"12","attributes":{"bold":"true"}}, 
-            {"insert":"34","attributes":{"bold":"true","italic":"true"}},
-            {"insert":"56","attributes":{"bold":"true"}}
-            ]"#,
-        ),
-        Insert(1, "abc", 0),
-        Invert(0, 1),
-        AssertOpsJson(
-            0,
-            r#"[
-            {"insert":"12","attributes":{"bold":"true"}},
-            {"insert":"34","attributes":{"bold":"true","italic":"true"}},
-            {"insert":"56","attributes":{"bold":"true"}}
-            ]"#,
-        ),
-    ];
-    OpTester::new().run_script(ops);
-}
-
-#[test]
-fn delta_invert_no_attribute_delta_with_attribute_delta() {
-    let ops = vec![
-        Insert(0, "123", 0),
-        Insert(1, "4567", 0),
-        Bold(1, Interval::new(0, 3), true),
-        AssertOpsJson(
-            1,
-            r#"[{"insert":"456","attributes":{"bold":"true"}},{"insert":"7"}]"#,
-        ),
-        Invert(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
-    ];
-    OpTester::new().run_script(ops);
-}
-
-#[test]
-fn delta_invert_no_attribute_delta_with_attribute_delta2() {
-    let ops = vec![
-        Insert(0, "123", 0),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
-        Insert(1, "abc", 0),
-        Bold(1, Interval::new(0, 3), true),
-        Insert(1, "d", 3),
-        Italic(1, Interval::new(1, 3), true),
-        AssertOpsJson(
-            1,
-            r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":
-{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"
-}}]"#,
-        ),
-        Invert(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
-    ];
-    OpTester::new().run_script(ops);
-}
-
-#[test]
-fn delta_invert_attribute_delta_with_attribute_delta() {
-    let ops = vec![
-        Insert(0, "123", 0),
-        Bold(0, Interval::new(0, 3), true),
-        Insert(0, "456", 3),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
-        Italic(0, Interval::new(2, 4), true),
-        AssertOpsJson(
-            0,
-            r#"[
-            {"insert":"12","attributes":{"bold":"true"}},
-            {"insert":"34","attributes":{"bold":"true","italic":"true"}},
-            {"insert":"56","attributes":{"bold":"true"}}
-            ]"#,
-        ),
-        Insert(1, "abc", 0),
-        Bold(1, Interval::new(0, 3), true),
-        Insert(1, "d", 3),
-        Italic(1, Interval::new(1, 3), true),
-        AssertOpsJson(
-            1,
-            r#"[
-            {"insert":"a","attributes":{"bold":"true"}},
-            {"insert":"bc","attributes":{"bold":"true","italic":"true"}},
-            {"insert":"d","attributes":{"bold":"true"}}
-            ]"#,
-        ),
-        Invert(0, 1),
-        AssertOpsJson(
-            0,
-            r#"[
-            {"insert":"12","attributes":{"bold":"true"}},
-            {"insert":"34","attributes":{"bold":"true","italic":"true"}},
-            {"insert":"56","attributes":{"bold":"true"}}
-            ]"#,
-        ),
-    ];
-    OpTester::new().run_script(ops);
-}

+ 177 - 17
rust-lib/flowy-ot/tests/op_test.rs

@@ -14,10 +14,8 @@ fn delta_get_ops_in_interval_1() {
     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()]
-    );
+    let mut iterator = DeltaIter::new(&delta, Interval::new(0, 4));
+    assert_eq!(iterator.ops(), delta.ops);
 }
 
 #[test]
@@ -34,27 +32,27 @@ fn delta_get_ops_in_interval_2() {
     delta.add(insert_c.clone());
 
     assert_eq!(
-        delta.ops_in_interval(Interval::new(0, 2)),
+        DeltaIter::new(&delta, Interval::new(0, 2)).ops(),
         vec![Builder::insert("12").build()]
     );
 
     assert_eq!(
-        delta.ops_in_interval(Interval::new(0, 3)),
+        DeltaIter::new(&delta, Interval::new(0, 3)).ops(),
         vec![insert_a.clone()]
     );
 
     assert_eq!(
-        delta.ops_in_interval(Interval::new(0, 4)),
+        DeltaIter::new(&delta, Interval::new(0, 4)).ops(),
         vec![insert_a.clone(), Builder::retain(1).build()]
     );
 
     assert_eq!(
-        delta.ops_in_interval(Interval::new(0, 6)),
+        DeltaIter::new(&delta, Interval::new(0, 6)).ops(),
         vec![insert_a.clone(), retain_a.clone()]
     );
 
     assert_eq!(
-        delta.ops_in_interval(Interval::new(0, 7)),
+        DeltaIter::new(&delta, Interval::new(0, 7)).ops(),
         vec![insert_a.clone(), retain_a.clone(), insert_b.clone()]
     );
 }
@@ -65,7 +63,7 @@ fn delta_get_ops_in_interval_3() {
     let insert_a = Builder::insert("123456").build();
     delta.add(insert_a.clone());
     assert_eq!(
-        delta.ops_in_interval(Interval::new(3, 5)),
+        DeltaIter::new(&delta, Interval::new(3, 5)).ops(),
         vec![Builder::insert("45").build()]
     );
 }
@@ -81,12 +79,21 @@ fn delta_get_ops_in_interval_4() {
     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!(
+        DeltaIter::new(&delta, Interval::new(0, 2)).ops(),
+        vec![insert_a]
+    );
+    assert_eq!(
+        DeltaIter::new(&delta, Interval::new(2, 4)).ops(),
+        vec![insert_b]
+    );
+    assert_eq!(
+        DeltaIter::new(&delta, Interval::new(4, 6)).ops(),
+        vec![insert_c]
+    );
 
     assert_eq!(
-        delta.ops_in_interval(Interval::new(2, 5)),
+        DeltaIter::new(&delta, Interval::new(2, 5)).ops(),
         vec![Builder::insert("34").build(), Builder::insert("5").build()]
     );
 }
@@ -99,12 +106,12 @@ fn delta_get_ops_in_interval_5() {
     delta.ops.push(insert_a.clone());
     delta.ops.push(insert_b.clone());
     assert_eq!(
-        delta.ops_in_interval(Interval::new(4, 8)),
+        DeltaIter::new(&delta, Interval::new(4, 8)).ops(),
         vec![Builder::insert("56").build(), Builder::insert("78").build()]
     );
 
     assert_eq!(
-        delta.ops_in_interval(Interval::new(8, 9)),
+        DeltaIter::new(&delta, Interval::new(8, 9)).ops(),
         vec![Builder::insert("9").build()]
     );
 }
@@ -115,7 +122,7 @@ fn delta_get_ops_in_interval_6() {
     let insert_a = Builder::insert("12345678").build();
     delta.add(insert_a.clone());
     assert_eq!(
-        delta.ops_in_interval(Interval::new(4, 6)),
+        DeltaIter::new(&delta, Interval::new(4, 6)).ops(),
         vec![Builder::insert("56").build()]
     );
 }
@@ -336,3 +343,156 @@ fn delta_transform_test() {
         serde_json::to_string(&b_prime).unwrap()
     );
 }
+
+#[test]
+fn delta_invert_no_attribute_delta() {
+    let mut delta = Delta::default();
+    delta.add(Builder::insert("123").build());
+
+    let mut change = Delta::default();
+    change.add(Builder::retain(3).build());
+    change.add(Builder::insert("456").build());
+    let undo = change.invert(&delta);
+
+    let new_delta = delta.compose(&change).unwrap();
+    let delta_after_undo = new_delta.compose(&undo).unwrap();
+
+    assert_eq!(delta_after_undo, delta);
+}
+
+#[test]
+fn delta_invert_no_attribute_delta2() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(1, "4567", 0),
+        Invert(0, 1),
+        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_invert_attribute_delta_with_no_attribute_delta() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Bold(0, Interval::new(0, 3), true),
+        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
+        Insert(1, "4567", 0),
+        Invert(0, 1),
+        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_invert_attribute_delta_with_no_attribute_delta2() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Bold(0, Interval::new(0, 3), true),
+        Insert(0, "456", 3),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"123456","attributes":{"bold":"true"}}]
+            "#,
+        ),
+        Italic(0, Interval::new(2, 4), true),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"12","attributes":{"bold":"true"}}, 
+            {"insert":"34","attributes":{"bold":"true","italic":"true"}},
+            {"insert":"56","attributes":{"bold":"true"}}
+            ]"#,
+        ),
+        Insert(1, "abc", 0),
+        Invert(0, 1),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"12","attributes":{"bold":"true"}},
+            {"insert":"34","attributes":{"bold":"true","italic":"true"}},
+            {"insert":"56","attributes":{"bold":"true"}}
+            ]"#,
+        ),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_invert_no_attribute_delta_with_attribute_delta() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(1, "4567", 0),
+        Bold(1, Interval::new(0, 3), true),
+        AssertOpsJson(
+            1,
+            r#"[{"insert":"456","attributes":{"bold":"true"}},{"insert":"7"}]"#,
+        ),
+        Invert(0, 1),
+        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_invert_no_attribute_delta_with_attribute_delta2() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+        Insert(1, "abc", 0),
+        Bold(1, Interval::new(0, 3), true),
+        Insert(1, "d", 3),
+        Italic(1, Interval::new(1, 3), true),
+        AssertOpsJson(
+            1,
+            r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":
+{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"
+}}]"#,
+        ),
+        Invert(0, 1),
+        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+    ];
+    OpTester::new().run_script(ops);
+}
+
+#[test]
+fn delta_invert_attribute_delta_with_attribute_delta() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Bold(0, Interval::new(0, 3), true),
+        Insert(0, "456", 3),
+        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        Italic(0, Interval::new(2, 4), true),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"12","attributes":{"bold":"true"}},
+            {"insert":"34","attributes":{"bold":"true","italic":"true"}},
+            {"insert":"56","attributes":{"bold":"true"}}
+            ]"#,
+        ),
+        Insert(1, "abc", 0),
+        Bold(1, Interval::new(0, 3), true),
+        Insert(1, "d", 3),
+        Italic(1, Interval::new(1, 3), true),
+        AssertOpsJson(
+            1,
+            r#"[
+            {"insert":"a","attributes":{"bold":"true"}},
+            {"insert":"bc","attributes":{"bold":"true","italic":"true"}},
+            {"insert":"d","attributes":{"bold":"true"}}
+            ]"#,
+        ),
+        Invert(0, 1),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"12","attributes":{"bold":"true"}},
+            {"insert":"34","attributes":{"bold":"true","italic":"true"}},
+            {"insert":"56","attributes":{"bold":"true"}}
+            ]"#,
+        ),
+    ];
+    OpTester::new().run_script(ops);
+}