preserve line format on merge && add merge test

This commit is contained in:
appflowy 2021-08-18 11:26:52 +08:00
parent 4d3eabb761
commit 8272e2e8f6
20 changed files with 207 additions and 138 deletions

View File

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

View File

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

View File

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

View File

@ -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)
}
}

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
client::extensions::FormatExt, client::extensions::FormatExt,
core::{Attribute, AttributeKey, CharMetric, Delta, DeltaBuilder, DeltaIter, Interval}, core::{Attribute, AttributeKey, Delta, DeltaBuilder, DeltaIter, Interval},
}; };
pub struct FormatLinkAtCaretPositionExt {} pub struct FormatLinkAtCaretPositionExt {}
@ -14,7 +14,7 @@ impl FormatExt for FormatLinkAtCaretPositionExt {
} }
let mut iter = DeltaIter::from_offset(delta, interval.start); 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 start = interval.end;
let mut retain = 0; let mut retain = 0;

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ impl InsertExt for AutoFormatExt {
return None; return None;
} }
let mut iter = DeltaIter::new(delta); 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()) { match AutoFormat::parse(prev.get_data()) {
None => {}, None => {},
Some(formatter) => { 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 bytecount::num_chars;
use std::cmp::min; use std::cmp::min;
use url::Url; use url::Url;

View File

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

View File

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

View File

@ -13,7 +13,8 @@ impl InsertExt for ResetLineFormatOnNewLine {
} }
let mut iter = DeltaIter::new(delta); 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) { if !next_op.get_data().starts_with(NEW_LINE) {
return None; return None;
} }

View File

@ -1,4 +1,4 @@
use crate::core::{Operation, NEW_LINE, WHITESPACE}; use crate::core::{NEW_LINE, WHITESPACE};
#[inline] #[inline]
pub fn find_newline(s: &str) -> Option<usize> { 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] #[inline]
pub fn is_newline(s: &str) -> bool { s == NEW_LINE } pub fn is_newline(s: &str) -> bool { s == NEW_LINE }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -762,3 +762,23 @@ fn attributes_preserve_block_when_insert_newline_inside() {
OpTester::new().run_script_with_newline(ops); 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);
}

View File

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