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 CatchAllInsertRule(),
// const EnsureEmbedLineRule(),
// const PreserveLineStyleOnMergeRule(),
const PreserveLineStyleOnMergeRule(),
const CatchAllDeleteRule(),
]);

View File

@ -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(

View File

@ -1,3 +1,5 @@
mod default_delete;
mod preserve_line_format_merge;
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::{
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;

View File

@ -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(_) => {

View File

@ -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(_) => {

View File

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

View File

@ -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;

View File

@ -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();

View File

@ -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());

View File

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

View File

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

View File

@ -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 {}),
]
}

View File

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

View File

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

View File

@ -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());

View File

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

View File

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

View File

@ -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();