Ver código fonte

preserve line format on merge && add merge test

appflowy 3 anos atrás
pai
commit
8272e2e8f6

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

@@ -53,7 +53,7 @@ class Rules {
     const PreserveInlineStylesRule(),
     const CatchAllInsertRule(),
     // const EnsureEmbedLineRule(),
-    // const PreserveLineStyleOnMergeRule(),
+    const PreserveLineStyleOnMergeRule(),
     const CatchAllDeleteRule(),
   ]);
 

+ 3 - 3
rust-lib/flowy-ot/src/client/extensions/delete/default_delete.rs

@@ -3,9 +3,9 @@ use crate::{
     core::{Delta, DeltaBuilder, Interval},
 };
 
-pub struct DefaultDeleteExt {}
-impl DeleteExt for DefaultDeleteExt {
-    fn ext_name(&self) -> &str { "DeleteExt" }
+pub struct DefaultDelete {}
+impl DeleteExt for DefaultDelete {
+    fn ext_name(&self) -> &str { "DefaultDelete" }
 
     fn apply(&self, _delta: &Delta, interval: Interval) -> Option<Delta> {
         Some(

+ 2 - 0
rust-lib/flowy-ot/src/client/extensions/delete/mod.rs

@@ -1,3 +1,5 @@
 mod default_delete;
+mod preserve_line_format_merge;
 
 pub use default_delete::*;
+pub use preserve_line_format_merge::*;

+ 59 - 0
rust-lib/flowy-ot/src/client/extensions/delete/preserve_line_format_merge.rs

@@ -0,0 +1,59 @@
+use crate::{
+    client::{extensions::DeleteExt, util::is_newline},
+    core::{Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter, Interval, Operation, NEW_LINE},
+};
+
+pub struct PreserveLineFormatOnMerge {}
+impl DeleteExt for PreserveLineFormatOnMerge {
+    fn ext_name(&self) -> &str { "PreserveLineFormatOnMerge" }
+
+    fn apply(&self, delta: &Delta, interval: Interval) -> Option<Delta> {
+        if interval.is_empty() {
+            return None;
+        }
+
+        // seek to the  interval start pos. e.g. You backspace enter pos
+        let mut iter = DeltaIter::from_offset(delta, interval.start);
+
+        // op will be the "\n"
+        let newline_op = iter.next_op_with_len(1)?;
+        if !is_newline(newline_op.get_data()) {
+            return None;
+        }
+
+        iter.seek::<CharMetric>(interval.size() - 1);
+        let mut new_delta = DeltaBuilder::new()
+            .retain(interval.start)
+            .delete(interval.size())
+            .build();
+
+        while iter.has_next() {
+            match iter.next() {
+                None => log::error!("op must be not None when has_next() return true"),
+                Some(op) => {
+                    //
+                    match op.get_data().find(NEW_LINE) {
+                        None => {
+                            new_delta.retain(op.len(), Attributes::empty());
+                            continue;
+                        },
+                        Some(line_break) => {
+                            let mut attributes = op.get_attributes();
+                            attributes.mark_all_as_removed_except(None);
+
+                            if newline_op.has_attribute() {
+                                attributes.extend(newline_op.get_attributes());
+                            }
+
+                            new_delta.retain(line_break, Attributes::empty());
+                            new_delta.retain(1, attributes);
+                            break;
+                        },
+                    }
+                },
+            }
+        }
+
+        Some(new_delta)
+    }
+}

+ 2 - 2
rust-lib/flowy-ot/src/client/extensions/format/format_at_position.rs

@@ -1,6 +1,6 @@
 use crate::{
     client::extensions::FormatExt,
-    core::{Attribute, AttributeKey, CharMetric, Delta, DeltaBuilder, DeltaIter, Interval},
+    core::{Attribute, AttributeKey, Delta, DeltaBuilder, DeltaIter, Interval},
 };
 
 pub struct FormatLinkAtCaretPositionExt {}
@@ -14,7 +14,7 @@ impl FormatExt for FormatLinkAtCaretPositionExt {
         }
 
         let mut iter = DeltaIter::from_offset(delta, interval.start);
-        let (before, after) = (iter.last_op_before_index(interval.size()), iter.next_op());
+        let (before, after) = (iter.next_op_with_len(interval.size()), iter.next_op());
         let mut start = interval.end;
         let mut retain = 0;
 

+ 2 - 11
rust-lib/flowy-ot/src/client/extensions/format/resolve_block_format.rs

@@ -3,16 +3,7 @@ use crate::{
         extensions::{format::helper::line_break, FormatExt},
         util::find_newline,
     },
-    core::{
-        Attribute,
-        AttributeScope,
-        Attributes,
-        CharMetric,
-        Delta,
-        DeltaBuilder,
-        DeltaIter,
-        Interval,
-    },
+    core::{Attribute, AttributeScope, Attributes, Delta, DeltaBuilder, DeltaIter, Interval},
 };
 
 pub struct ResolveBlockFormat {}
@@ -29,7 +20,7 @@ impl FormatExt for ResolveBlockFormat {
         let mut start = 0;
         let end = interval.size();
         while start < end && iter.has_next() {
-            let next_op = iter.last_op_before_index(end - start).unwrap();
+            let next_op = iter.next_op_with_len(end - start).unwrap();
             match find_newline(next_op.get_data()) {
                 None => new_delta.retain(next_op.len(), Attributes::empty()),
                 Some(_) => {

+ 2 - 2
rust-lib/flowy-ot/src/client/extensions/format/resolve_inline_format.rs

@@ -3,7 +3,7 @@ use crate::{
         extensions::{format::helper::line_break, FormatExt},
         util::find_newline,
     },
-    core::{Attribute, AttributeScope, CharMetric, Delta, DeltaBuilder, DeltaIter, Interval},
+    core::{Attribute, AttributeScope, Delta, DeltaBuilder, DeltaIter, Interval},
 };
 
 pub struct ResolveInlineFormat {}
@@ -20,7 +20,7 @@ impl FormatExt for ResolveInlineFormat {
         let end = interval.size();
 
         while start < end && iter.has_next() {
-            let next_op = iter.last_op_before_index(end - start).unwrap();
+            let next_op = iter.next_op_with_len(end - start).unwrap();
             match find_newline(next_op.get_data()) {
                 None => new_delta.retain(next_op.len(), attribute.clone().into()),
                 Some(_) => {

+ 3 - 12
rust-lib/flowy-ot/src/client/extensions/insert/auto_exit_block.rs

@@ -1,15 +1,6 @@
 use crate::{
     client::{extensions::InsertExt, util::is_newline},
-    core::{
-        AttributeKey,
-        AttributeValue,
-        Attributes,
-        CharMetric,
-        Delta,
-        DeltaBuilder,
-        DeltaIter,
-        Operation,
-    },
+    core::{AttributeKey, Delta, DeltaBuilder, DeltaIter, Operation},
 };
 
 use crate::core::{attributes_except_header, is_empty_line_at_index};
@@ -42,7 +33,7 @@ impl InsertExt for AutoExitBlock {
             return None;
         }
 
-        match iter.first_newline_op() {
+        match iter.next_op_with_newline() {
             None => {},
             Some((newline_op, _)) => {
                 let newline_attributes = attributes_except_header(&newline_op);
@@ -52,7 +43,7 @@ impl InsertExt for AutoExitBlock {
             },
         }
 
-        attributes.mark_as_removed_except(&AttributeKey::Header);
+        attributes.mark_all_as_removed_except(Some(AttributeKey::Header));
 
         Some(
             DeltaBuilder::new()

+ 2 - 2
rust-lib/flowy-ot/src/client/extensions/insert/auto_format.rs

@@ -13,7 +13,7 @@ impl InsertExt for AutoFormatExt {
             return None;
         }
         let mut iter = DeltaIter::new(delta);
-        if let Some(prev) = iter.last_op_before_index(index) {
+        if let Some(prev) = iter.next_op_with_len(index) {
             match AutoFormat::parse(prev.get_data()) {
                 None => {},
                 Some(formatter) => {
@@ -50,7 +50,7 @@ impl InsertExt for AutoFormatExt {
     }
 }
 
-use crate::core::{Attribute, AttributeBuilder, Attributes, DeltaBuilder, Operation};
+use crate::core::{AttributeBuilder, Attributes, DeltaBuilder, Operation};
 use bytecount::num_chars;
 use std::cmp::min;
 use url::Url;

+ 1 - 2
rust-lib/flowy-ot/src/client/extensions/insert/preserve_block_format.rs

@@ -2,7 +2,6 @@ use crate::{
     client::{extensions::InsertExt, util::is_newline},
     core::{
         attributes_except_header,
-        AttributeBuilder,
         AttributeKey,
         Attributes,
         Delta,
@@ -23,7 +22,7 @@ impl InsertExt for PreserveBlockFormatOnInsert {
         }
 
         let mut iter = DeltaIter::from_offset(delta, index);
-        match iter.first_newline_op() {
+        match iter.next_op_with_newline() {
             None => {},
             Some((newline_op, offset)) => {
                 let newline_attributes = newline_op.get_attributes();

+ 4 - 13
rust-lib/flowy-ot/src/client/extensions/insert/preserve_inline_format.rs

@@ -3,16 +3,7 @@ use crate::{
         extensions::InsertExt,
         util::{contain_newline, is_newline},
     },
-    core::{
-        AttributeKey,
-        Attributes,
-        Delta,
-        DeltaBuilder,
-        DeltaIter,
-        OpNewline,
-        Operation,
-        NEW_LINE,
-    },
+    core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter, OpNewline, NEW_LINE},
 };
 
 pub struct PreserveInlineFormat {}
@@ -25,7 +16,7 @@ impl InsertExt for PreserveInlineFormat {
         }
 
         let mut iter = DeltaIter::new(delta);
-        let prev = iter.last_op_before_index(index)?;
+        let prev = iter.next_op_with_len(index)?;
         if OpNewline::parse(&prev).is_contain() {
             return None;
         }
@@ -69,7 +60,7 @@ impl InsertExt for PreserveLineFormatOnSplit {
         }
 
         let mut iter = DeltaIter::new(delta);
-        let prev = iter.last_op_before_index(index)?;
+        let prev = iter.next_op_with_len(index)?;
         if OpNewline::parse(&prev).is_end() {
             return None;
         }
@@ -89,7 +80,7 @@ impl InsertExt for PreserveLineFormatOnSplit {
             return Some(new_delta);
         }
 
-        match iter.first_newline_op() {
+        match iter.next_op_with_newline() {
             None => {},
             Some((newline_op, _)) => {
                 new_delta.insert(NEW_LINE, newline_op.get_attributes());

+ 2 - 1
rust-lib/flowy-ot/src/client/extensions/insert/reset_format_on_new_line.rs

@@ -13,7 +13,8 @@ impl InsertExt for ResetLineFormatOnNewLine {
         }
 
         let mut iter = DeltaIter::new(delta);
-        let next_op = iter.first_op_after_index(index)?;
+        iter.seek::<CharMetric>(index);
+        let next_op = iter.next_op()?;
         if !next_op.get_data().starts_with(NEW_LINE) {
             return None;
         }

+ 1 - 4
rust-lib/flowy-ot/src/client/util.rs

@@ -1,4 +1,4 @@
-use crate::core::{Operation, NEW_LINE, WHITESPACE};
+use crate::core::{NEW_LINE, WHITESPACE};
 
 #[inline]
 pub fn find_newline(s: &str) -> Option<usize> {
@@ -8,9 +8,6 @@ pub fn find_newline(s: &str) -> Option<usize> {
     }
 }
 
-#[inline]
-pub fn is_op_contains_newline(op: &Operation) -> bool { contain_newline(op.get_data()) }
-
 #[inline]
 pub fn is_newline(s: &str) -> bool { s == NEW_LINE }
 

+ 2 - 2
rust-lib/flowy-ot/src/client/view.rs

@@ -102,7 +102,7 @@ fn construct_format_exts() -> Vec<FormatExtension> {
 
 fn construct_delete_exts() -> Vec<DeleteExtension> {
     vec![
-        //
-        Box::new(DefaultDeleteExt {}),
+        Box::new(PreserveLineFormatOnMerge {}),
+        Box::new(DefaultDelete {}),
     ]
 }

+ 15 - 7
rust-lib/flowy-ot/src/core/attributes/attributes.rs

@@ -42,13 +42,21 @@ impl Attributes {
         self.inner.insert(key.clone(), value);
     }
 
-    pub fn mark_as_removed_except(&mut self, attribute: &AttributeKey) {
-        self.inner.iter_mut().for_each(|(k, v)| {
-            if k != attribute {
-                v.0 = REMOVE_FLAG.into();
-            }
-            v.0 = REMOVE_FLAG.into();
-        });
+    pub fn mark_all_as_removed_except(&mut self, attribute: Option<AttributeKey>) {
+        match attribute {
+            None => {
+                self.inner
+                    .iter_mut()
+                    .for_each(|(k, v)| v.0 = REMOVE_FLAG.into());
+            },
+            Some(attribute) => {
+                self.inner.iter_mut().for_each(|(k, v)| {
+                    if k != &attribute {
+                        v.0 = REMOVE_FLAG.into();
+                    }
+                });
+            },
+        }
     }
 
     pub fn remove(&mut self, key: AttributeKey) { self.inner.retain(|k, _| k != &key); }

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

@@ -5,7 +5,7 @@ use crate::{
 use std::{cmp::min, iter::Enumerate, slice::Iter};
 
 #[derive(Debug)]
-pub struct Cursor<'a> {
+pub struct OpCursor<'a> {
     pub(crate) delta: &'a Delta,
     pub(crate) origin_iv: Interval,
     pub(crate) consume_iv: Interval,
@@ -15,8 +15,8 @@ pub struct Cursor<'a> {
     next_op: Option<Operation>,
 }
 
-impl<'a> Cursor<'a> {
-    pub fn new(delta: &'a Delta, interval: Interval) -> Cursor<'a> {
+impl<'a> OpCursor<'a> {
+    pub fn new(delta: &'a Delta, interval: Interval) -> OpCursor<'a> {
         // debug_assert!(interval.start <= delta.target_len);
         let mut cursor = Self {
             delta,
@@ -32,51 +32,56 @@ impl<'a> Cursor<'a> {
     }
 
     // get the next operation interval
-    pub fn next_iv(&self) -> Interval { self.next_iv_before(None) }
+    pub fn next_iv(&self) -> Interval { self.next_iv_before(None).unwrap_or(Interval::new(0, 0)) }
 
-    pub fn next_op(&mut self) -> Option<Operation> { self.last_op_before_index(None) }
+    pub fn next(&mut self) -> Option<Operation> { self.next_with_len(None) }
 
-    // get the last operation before the index
-    pub fn last_op_before_index(&mut self, index: Option<usize>) -> Option<Operation> {
+    // get the last operation before the end.
+    // checkout the delta_next_op_with_len_cross_op_return_last test for more detail
+    pub fn next_with_len(&mut self, force_end: Option<usize>) -> Option<Operation> {
         let mut find_op = None;
         let holder = self.next_op.clone();
         let mut next_op = holder.as_ref();
 
         if next_op.is_none() {
-            next_op = find_next_op(self);
+            next_op = find_next(self);
         }
 
-        let mut pos = 0;
+        let mut consume_len = 0;
         while find_op.is_none() && next_op.is_some() {
             let op = next_op.take().unwrap();
-            let interval = self.next_iv_before(index);
+            let interval = self
+                .next_iv_before(force_end)
+                .unwrap_or(Interval::new(0, 0));
+
+            // cache the op if the interval is empty. e.g. last_op_before(Some(0))
             if interval.is_empty() {
                 self.next_op = Some(op.clone());
                 break;
             }
-
             find_op = op.shrink(interval);
-            self.next_op = None;
-
             let suffix = Interval::new(0, op.len()).suffix(interval);
-            if !suffix.is_empty() {
+            if suffix.is_empty() {
+                self.next_op = None;
+            } else {
                 self.next_op = op.shrink(suffix);
             }
 
-            pos += interval.end;
+            consume_len += interval.end;
             self.consume_count += interval.end;
             self.consume_iv.start = self.consume_count;
 
+            // continue to find the op in next iteration
             if find_op.is_none() {
-                next_op = find_next_op(self);
+                next_op = find_next(self);
             }
         }
 
-        if find_op.is_some() && index.is_some() {
-            // try to find the next op before the index if iter_char_count less than index
-            let end = index.unwrap();
-            if end > pos && self.has_next() {
-                return self.last_op_before_index(Some(end - pos));
+        if find_op.is_some() && force_end.is_some() {
+            // try to find the next op before the index if consume_len less than index
+            let end = force_end.unwrap();
+            if end > consume_len && self.has_next() {
+                return self.next_with_len(Some(end - consume_len));
             }
         }
         return find_op;
@@ -104,6 +109,19 @@ impl<'a> Cursor<'a> {
         }
     }
 
+    fn next_iv_before(&self, force_end: Option<usize>) -> Option<Interval> {
+        let op = self.next_iter_op()?;
+        let start = self.consume_count;
+        let end = match force_end {
+            None => self.consume_count + op.len(),
+            Some(index) => self.consume_count + min(index, op.len()),
+        };
+
+        let intersect = Interval::new(start, end).intersect(self.consume_iv);
+        let interval = intersect.translate_neg(start);
+        Some(interval)
+    }
+
     pub fn next_iter_op(&self) -> Option<&Operation> {
         let mut next_op = self.next_op.as_ref();
         if next_op.is_none() {
@@ -118,27 +136,9 @@ impl<'a> Cursor<'a> {
         }
         next_op
     }
-
-    fn next_iv_before(&self, index: Option<usize>) -> Interval {
-        let next_op = self.next_iter_op();
-        if next_op.is_none() {
-            return Interval::new(0, 0);
-        }
-
-        let op = next_op.unwrap();
-        let start = self.consume_count;
-        let end = match index {
-            None => self.consume_count + op.len(),
-            Some(index) => self.consume_count + min(index, op.len()),
-        };
-
-        let intersect = Interval::new(start, end).intersect(self.consume_iv);
-        let interval = intersect.translate_neg(start);
-        interval
-    }
 }
 
-fn find_next_op<'a>(cursor: &mut Cursor<'a>) -> Option<&'a Operation> {
+fn find_next<'a>(cursor: &mut OpCursor<'a>) -> Option<&'a Operation> {
     match cursor.iter.next() {
         None => None,
         Some((o_index, op)) => {
@@ -150,15 +150,15 @@ fn find_next_op<'a>(cursor: &mut Cursor<'a>) -> Option<&'a Operation> {
 
 type SeekResult = Result<(), OTError>;
 pub trait Metric {
-    fn seek(cursor: &mut Cursor, index: usize) -> SeekResult;
+    fn seek(cursor: &mut OpCursor, index: usize) -> SeekResult;
 }
 
 pub struct OpMetric {}
 
 impl Metric for OpMetric {
-    fn seek(cursor: &mut Cursor, index: usize) -> SeekResult {
+    fn seek(cursor: &mut OpCursor, index: usize) -> SeekResult {
         let _ = check_bound(cursor.op_index, index)?;
-        let mut seek_cursor = Cursor::new(cursor.delta, cursor.origin_iv);
+        let mut seek_cursor = OpCursor::new(cursor.delta, cursor.origin_iv);
         let mut offset = 0;
         while let Some((_, op)) = seek_cursor.iter.next() {
             offset += op.len();
@@ -174,9 +174,9 @@ impl Metric for OpMetric {
 pub struct CharMetric {}
 
 impl Metric for CharMetric {
-    fn seek(cursor: &mut Cursor, index: usize) -> SeekResult {
+    fn seek(cursor: &mut OpCursor, index: usize) -> SeekResult {
         let _ = check_bound(cursor.consume_count, index)?;
-        let _ = cursor.last_op_before_index(Some(index));
+        let _ = cursor.next_with_len(Some(index));
 
         Ok(())
     }

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

@@ -168,11 +168,11 @@ impl Delta {
             );
 
             let op = iter
-                .last_op_before_index(length)
+                .next_op_with_len(length)
                 .unwrap_or(OpBuilder::retain(length).build());
 
             let other_op = other_iter
-                .last_op_before_index(length)
+                .next_op_with_len(length)
                 .unwrap_or(OpBuilder::retain(length).build());
 
             debug_assert_eq!(op.len(), other_op.len());

+ 11 - 16
rust-lib/flowy-ot/src/core/delta/iterator.rs

@@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut};
 pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize;
 
 pub struct DeltaIter<'a> {
-    cursor: Cursor<'a>,
+    cursor: OpCursor<'a>,
 }
 
 impl<'a> DeltaIter<'a> {
@@ -22,7 +22,7 @@ impl<'a> DeltaIter<'a> {
     }
 
     pub fn from_interval(delta: &'a Delta, interval: Interval) -> Self {
-        let cursor = Cursor::new(delta, interval);
+        let cursor = OpCursor::new(delta, interval);
         Self { cursor }
     }
 
@@ -37,8 +37,14 @@ impl<'a> DeltaIter<'a> {
         }
     }
 
+    pub fn next_op(&mut self) -> Option<Operation> { self.cursor.next() }
+
+    pub fn next_op_with_len(&mut self, len: usize) -> Option<Operation> {
+        self.cursor.next_with_len(Some(len))
+    }
+
     // find next op contains NEW_LINE
-    pub fn first_newline_op(&mut self) -> Option<(Operation, usize)> {
+    pub fn next_op_with_newline(&mut self) -> Option<(Operation, usize)> {
         let mut offset = 0;
         while self.has_next() {
             if let Some(op) = self.next_op() {
@@ -52,17 +58,6 @@ impl<'a> DeltaIter<'a> {
         None
     }
 
-    pub fn next_op(&mut self) -> Option<Operation> { self.cursor.next_op() }
-
-    pub fn last_op_before_index(&mut self, index: usize) -> Option<Operation> {
-        self.cursor.last_op_before_index(Some(index))
-    }
-
-    pub fn first_op_after_index(&mut self, index: usize) -> Option<Operation> {
-        self.seek::<CharMetric>(index);
-        self.next_op()
-    }
-
     pub fn seek<M: Metric>(&mut self, index: usize) {
         match M::seek(&mut self.cursor, index) {
             Ok(_) => {},
@@ -101,7 +96,7 @@ impl<'a> Iterator for DeltaIter<'a> {
 
 pub fn is_empty_line_at_index(delta: &Delta, index: usize) -> bool {
     let mut iter = DeltaIter::new(delta);
-    let (prev, next) = (iter.last_op_before_index(index), iter.next_op());
+    let (prev, next) = (iter.next_op_with_len(index), iter.next_op());
     if prev.is_none() {
         return true;
     }
@@ -151,7 +146,7 @@ impl<'a> DerefMut for AttributesIter<'a> {
 impl<'a> Iterator for AttributesIter<'a> {
     type Item = (usize, Attributes);
     fn next(&mut self) -> Option<Self::Item> {
-        let next_op = self.delta_iter.next();
+        let next_op = self.delta_iter.next_op();
         if next_op.is_none() {
             return None;
         }

+ 20 - 0
rust-lib/flowy-ot/tests/attribute_test.rs

@@ -762,3 +762,23 @@ fn attributes_preserve_block_when_insert_newline_inside() {
 
     OpTester::new().run_script_with_newline(ops);
 }
+
+#[test]
+fn attributes_preserve_line_format_on_merge() {
+    let ops = vec![
+        Insert(0, "123456", 0),
+        Header(0, Interval::new(0, 6), 1, true),
+        Insert(0, NEW_LINE, 3),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"123"},{"insert":"\n","attributes":{"header":"1"}},{"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]"#,
+        ),
+        Delete(0, Interval::new(3, 4)),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":"1"}}]"#,
+        ),
+    ];
+
+    OpTester::new().run_script_with_newline(ops);
+}

+ 28 - 13
rust-lib/flowy-ot/tests/op_test.rs

@@ -153,7 +153,7 @@ fn delta_get_ops_in_interval_7() {
 
     let mut iter_2 = DeltaIter::new(&delta);
     assert_eq!(
-        iter_2.last_op_before_index(2).unwrap(),
+        iter_2.next_op_with_len(2).unwrap(),
         OpBuilder::insert("12").build()
     );
     assert_eq!(iter_2.next_op().unwrap(), OpBuilder::insert("345").build());
@@ -180,7 +180,7 @@ fn delta_seek_2() {
 
     let mut iter = DeltaIter::new(&delta);
     assert_eq!(
-        iter.last_op_before_index(1).unwrap(),
+        iter.next_op_with_len(1).unwrap(),
         OpBuilder::insert("1").build()
     );
 }
@@ -192,21 +192,21 @@ fn delta_seek_3() {
 
     let mut iter = DeltaIter::new(&delta);
     assert_eq!(
-        iter.last_op_before_index(2).unwrap(),
+        iter.next_op_with_len(2).unwrap(),
         OpBuilder::insert("12").build()
     );
 
     assert_eq!(
-        iter.last_op_before_index(2).unwrap(),
+        iter.next_op_with_len(2).unwrap(),
         OpBuilder::insert("34").build()
     );
 
     assert_eq!(
-        iter.last_op_before_index(2).unwrap(),
+        iter.next_op_with_len(2).unwrap(),
         OpBuilder::insert("5").build()
     );
 
-    assert_eq!(iter.last_op_before_index(1), None);
+    assert_eq!(iter.next_op_with_len(1), None);
 }
 
 #[test]
@@ -217,7 +217,7 @@ fn delta_seek_4() {
     let mut iter = DeltaIter::new(&delta);
     iter.seek::<CharMetric>(3);
     assert_eq!(
-        iter.last_op_before_index(2).unwrap(),
+        iter.next_op_with_len(2).unwrap(),
         OpBuilder::insert("45").build()
     );
 }
@@ -237,7 +237,7 @@ fn delta_seek_5() {
     iter.seek::<CharMetric>(0);
 
     assert_eq!(
-        iter.last_op_before_index(4).unwrap(),
+        iter.next_op_with_len(4).unwrap(),
         OpBuilder::insert("1234").attributes(attributes).build(),
     );
 }
@@ -251,7 +251,7 @@ fn delta_next_op_len_test() {
     iter.seek::<CharMetric>(3);
     assert_eq!(iter.next_op_len().unwrap(), 2);
     assert_eq!(
-        iter.last_op_before_index(1).unwrap(),
+        iter.next_op_with_len(1).unwrap(),
         OpBuilder::insert("4").build()
     );
     assert_eq!(iter.next_op_len().unwrap(), 1);
@@ -266,22 +266,37 @@ fn delta_next_op_len_test2() {
 
     assert_eq!(iter.next_op_len().unwrap(), 5);
     assert_eq!(
-        iter.last_op_before_index(5).unwrap(),
+        iter.next_op_with_len(5).unwrap(),
         OpBuilder::insert("12345").build()
     );
     assert_eq!(iter.next_op_len(), None);
 }
 
 #[test]
-fn delta_next_op_len_test3() {
+fn delta_next_op_with_len_zero() {
     let mut delta = Delta::default();
     delta.add(OpBuilder::insert("12345").build());
     let mut iter = DeltaIter::new(&delta);
-
-    assert_eq!(iter.last_op_before_index(0), None,);
+    assert_eq!(iter.next_op_with_len(0), None,);
     assert_eq!(iter.next_op_len().unwrap(), 5);
 }
 
+#[test]
+fn delta_next_op_with_len_cross_op_return_last() {
+    let mut delta = Delta::default();
+    delta.add(OpBuilder::insert("12345").build());
+    delta.add(OpBuilder::retain(1).build());
+    delta.add(OpBuilder::insert("678").build());
+
+    let mut iter = DeltaIter::new(&delta);
+    iter.seek::<CharMetric>(4);
+    assert_eq!(iter.next_op_len().unwrap(), 1);
+    assert_eq!(
+        iter.next_op_with_len(2).unwrap(),
+        OpBuilder::retain(1).build()
+    );
+}
+
 #[test]
 fn lengths() {
     let mut delta = Delta::default();