From 80a94880fcd7087e6ff06a012997bd14403e481d Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 4 Aug 2021 15:09:04 +0800 Subject: [PATCH] config ot invert detal --- rust-lib/flowy-ot/src/core/attributes.rs | 68 +++--- rust-lib/flowy-ot/src/core/delta.rs | 240 +++++++++++++--------- rust-lib/flowy-ot/src/core/interval.rs | 4 + rust-lib/flowy-ot/src/core/operation.rs | 55 ++++- rust-lib/flowy-ot/tests/attribute_test.rs | 46 ++--- rust-lib/flowy-ot/tests/helper/mod.rs | 90 +++----- rust-lib/flowy-ot/tests/invert_test.rs | 68 ++++++ rust-lib/flowy-ot/tests/op_test.rs | 4 +- 8 files changed, 357 insertions(+), 218 deletions(-) create mode 100644 rust-lib/flowy-ot/tests/invert_test.rs diff --git a/rust-lib/flowy-ot/src/core/attributes.rs b/rust-lib/flowy-ot/src/core/attributes.rs index 3e3f23f9ac..6a9b016bc7 100644 --- a/rust-lib/flowy-ot/src/core/attributes.rs +++ b/rust-lib/flowy-ot/src/core/attributes.rs @@ -27,6 +27,7 @@ impl Attributes { } } // remove attribute if the value is PLAIN + // { "k": PLAIN } -> {} pub fn remove_plain(&mut self) { match self { Attributes::Follow => {}, @@ -36,6 +37,14 @@ impl Attributes { Attributes::Empty => {}, } } + + pub fn get_attributes_data(&self) -> Option { + match self { + Attributes::Follow => None, + Attributes::Custom(data) => Some(data.clone()), + Attributes::Empty => None, + } + } } impl std::default::Default for Attributes { @@ -131,7 +140,7 @@ impl AttrsBuilder { pub fn build(self) -> Attributes { Attributes::Custom(self.inner) } } -pub fn attributes_from(operation: &Option) -> Option { +pub(crate) fn attributes_from(operation: &Option) -> Option { match operation { None => None, Some(operation) => Some(operation.get_attributes()), @@ -196,6 +205,37 @@ pub fn transform_attributes( } } +pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes { + let attr = attr.get_attributes_data(); + let base = base.get_attributes_data(); + + if attr.is_none() && base.is_none() { + return Attributes::Empty; + } + + let attr = attr.unwrap_or(AttributesData::new()); + let base = base.unwrap_or(AttributesData::new()); + + let base_inverted = base + .iter() + .fold(AttributesData::new(), |mut attributes, (k, v)| { + if base.get(k) != attr.get(k) && attr.contains_key(k) { + attributes.insert(k.clone(), v.clone()); + } + attributes + }); + + let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, _)| { + if base.get(k) != attr.get(k) && !base.contains_key(k) { + // attributes.insert(k.clone(), "".to_owned()); + attributes.remove(k); + } + attributes + }); + + return Attributes::Custom(inverted); +} + fn transform_attribute_data(left: AttributesData, right: AttributesData) -> AttributesData { let result = right .iter() @@ -207,29 +247,3 @@ fn transform_attribute_data(left: AttributesData, right: AttributesData) -> Attr }); result } - -// pub fn invert_attributes( -// attr: Option, -// base: Option, -// ) -> AttributesData { -// let attr = attr.unwrap_or(AttributesData::new()); -// let base = base.unwrap_or(AttributesData::new()); -// -// let base_inverted = base -// .iter() -// .fold(AttributesData::new(), |mut attributes, (k, v)| { -// if base.get(k) != attr.get(k) && attr.contains_key(k) { -// attributes.insert(k.clone(), v.clone()); -// } -// attributes -// }); -// -// let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, _)| { -// if base.get(k) != attr.get(k) && !base.contains_key(k) { -// attributes.insert(k.clone(), "".to_owned()); -// } -// attributes -// }); -// -// return inverted; -// } diff --git a/rust-lib/flowy-ot/src/core/delta.rs b/rust-lib/flowy-ot/src/core/delta.rs index cb98bcccab..2f8c7046f0 100644 --- a/rust-lib/flowy-ot/src/core/delta.rs +++ b/rust-lib/flowy-ot/src/core/delta.rs @@ -1,5 +1,5 @@ use crate::{ - core::{attributes::*, operation::*}, + core::{attributes::*, operation::*, Interval}, errors::OTError, }; use bytecount::num_chars; @@ -70,7 +70,7 @@ impl Delta { match op { Operation::Delete(i) => self.delete(i), Operation::Insert(i) => self.insert(&i.s, i.attributes), - Operation::Retain(r) => self.retain(r.num, r.attributes), + Operation::Retain(r) => self.retain(r.n, r.attributes), } } @@ -95,11 +95,11 @@ impl Delta { let new_last = match self.ops.as_mut_slice() { [.., Operation::Insert(insert)] => { // - merge_insert_or_new_op(insert, s, attrs) + insert.merge_or_new_op(s, attrs) }, [.., Operation::Insert(pre_insert), Operation::Delete(_)] => { // - merge_insert_or_new_op(pre_insert, s, attrs) + pre_insert.merge_or_new_op(s, attrs) }, [.., op_last @ Operation::Delete(_)] => { let new_last = op_last.clone(); @@ -123,9 +123,8 @@ impl Delta { self.target_len += n as usize; if let Some(Operation::Retain(retain)) = self.ops.last_mut() { - match merge_retain_or_new_op(retain, n, attrs) { - None => {}, - Some(new_op) => self.ops.push(new_op), + if let Some(new_op) = retain.merge_or_new_op(n, attrs) { + self.ops.push(new_op); } } else { self.ops @@ -175,24 +174,24 @@ impl Delta { let composed_attrs = compose_attributes(&next_op1, &next_op2); log::debug!( "[retain:{} - retain:{}]: {:?}", - retain.num, - o_retain.num, + retain.n, + o_retain.n, composed_attrs ); match retain.cmp(&o_retain) { Ordering::Less => { - new_delta.retain(retain.num, composed_attrs); - next_op2 = Some(OpBuilder::retain(o_retain.num - retain.num).build()); + new_delta.retain(retain.n, composed_attrs); + next_op2 = Some(OpBuilder::retain(o_retain.n - retain.n).build()); next_op1 = ops1.next(); }, std::cmp::Ordering::Equal => { - new_delta.retain(retain.num, composed_attrs); + new_delta.retain(retain.n, composed_attrs); next_op1 = ops1.next(); next_op2 = ops2.next(); }, std::cmp::Ordering::Greater => { - new_delta.retain(o_retain.num, composed_attrs); - next_op1 = Some(OpBuilder::retain(retain.num - o_retain.num).build()); + new_delta.retain(o_retain.n, composed_attrs); + next_op1 = Some(OpBuilder::retain(retain.n - o_retain.n).build()); next_op2 = ops2.next(); }, } @@ -227,14 +226,14 @@ impl Delta { log::debug!( "[insert:{} - retain:{}]: {:?}", insert.s, - o_retain.num, + o_retain.n, composed_attrs ); match (insert.num_chars()).cmp(o_retain) { Ordering::Less => { new_delta.insert(&insert.s, composed_attrs.clone()); next_op2 = Some( - OpBuilder::retain(o_retain.num - insert.num_chars()) + OpBuilder::retain(o_retain.n - insert.num_chars()) .attributes(composed_attrs.clone()) .build(), ); @@ -248,7 +247,7 @@ impl Delta { Ordering::Greater => { let chars = &mut insert.chars(); new_delta.insert( - &chars.take(o_retain.num as usize).collect::(), + &chars.take(o_retain.n as usize).collect::(), composed_attrs, ); next_op1 = Some( @@ -263,8 +262,8 @@ impl Delta { (Some(Operation::Retain(retain)), Some(Operation::Delete(o_num))) => { match retain.cmp(&o_num) { Ordering::Less => { - new_delta.delete(retain.num); - next_op2 = Some(OpBuilder::delete(*o_num - retain.num).build()); + new_delta.delete(retain.n); + next_op2 = Some(OpBuilder::delete(*o_num - retain.n).build()); next_op1 = ops1.next(); }, Ordering::Equal => { @@ -274,7 +273,7 @@ impl Delta { }, Ordering::Greater => { new_delta.delete(*o_num); - next_op1 = Some(OpBuilder::retain(retain.num - *o_num).build()); + next_op1 = Some(OpBuilder::retain(retain.n - *o_num).build()); next_op2 = ops2.next(); }, } @@ -331,21 +330,21 @@ impl Delta { let composed_attrs = transform_attributes(&next_op1, &next_op2, true); match retain.cmp(&o_retain) { Ordering::Less => { - a_prime.retain(retain.num, composed_attrs.clone()); - b_prime.retain(retain.num, composed_attrs.clone()); - next_op2 = Some(OpBuilder::retain(o_retain.num - retain.num).build()); + a_prime.retain(retain.n, composed_attrs.clone()); + b_prime.retain(retain.n, composed_attrs.clone()); + next_op2 = Some(OpBuilder::retain(o_retain.n - retain.n).build()); next_op1 = ops1.next(); }, Ordering::Equal => { - a_prime.retain(retain.num, composed_attrs.clone()); - b_prime.retain(retain.num, composed_attrs.clone()); + a_prime.retain(retain.n, composed_attrs.clone()); + b_prime.retain(retain.n, composed_attrs.clone()); next_op1 = ops1.next(); next_op2 = ops2.next(); }, Ordering::Greater => { - a_prime.retain(o_retain.num, composed_attrs.clone()); - b_prime.retain(o_retain.num, composed_attrs.clone()); - next_op1 = Some(OpBuilder::retain(retain.num - o_retain.num).build()); + a_prime.retain(o_retain.n, composed_attrs.clone()); + b_prime.retain(o_retain.n, composed_attrs.clone()); + next_op1 = Some(OpBuilder::retain(retain.n - o_retain.n).build()); next_op2 = ops2.next(); }, }; @@ -368,7 +367,7 @@ impl Delta { match i.cmp(&o_retain) { Ordering::Less => { a_prime.delete(*i); - next_op2 = Some(OpBuilder::retain(o_retain.num - *i).build()); + next_op2 = Some(OpBuilder::retain(o_retain.n - *i).build()); next_op1 = ops1.next(); }, Ordering::Equal => { @@ -377,8 +376,8 @@ impl Delta { next_op2 = ops2.next(); }, Ordering::Greater => { - a_prime.delete(o_retain.num); - next_op1 = Some(OpBuilder::delete(*i - o_retain.num).build()); + a_prime.delete(o_retain.n); + next_op1 = Some(OpBuilder::delete(*i - o_retain.n).build()); next_op2 = ops2.next(); }, }; @@ -386,18 +385,18 @@ impl Delta { (Some(Operation::Retain(retain)), Some(Operation::Delete(j))) => { match retain.cmp(&j) { Ordering::Less => { - b_prime.delete(retain.num); - next_op2 = Some(OpBuilder::delete(*j - retain.num).build()); + b_prime.delete(retain.n); + next_op2 = Some(OpBuilder::delete(*j - retain.n).build()); next_op1 = ops1.next(); }, Ordering::Equal => { - b_prime.delete(retain.num); + b_prime.delete(retain.n); next_op1 = ops1.next(); next_op2 = ops2.next(); }, Ordering::Greater => { b_prime.delete(*j); - next_op1 = Some(OpBuilder::retain(retain.num - *j).build()); + next_op1 = Some(OpBuilder::retain(retain.n - *j).build()); next_op2 = ops2.next(); }, }; @@ -423,7 +422,7 @@ impl Delta { for op in &self.ops { match &op { Operation::Retain(retain) => { - for c in chars.take(retain.num as usize) { + for c in chars.take(retain.n as usize) { new_s.push(c); } }, @@ -448,8 +447,11 @@ impl Delta { for op in &self.ops { match &op { Operation::Retain(retain) => { - inverted.retain(retain.num, Attributes::Follow); - for _ in 0..retain.num { + inverted.retain(retain.n, Attributes::Follow); + + // TODO: use advance_by instead, but it's unstable now + // chars.advance_by(retain.num) + for _ in 0..retain.n { chars.next(); } }, @@ -467,6 +469,54 @@ impl Delta { inverted } + pub fn invert_delta(&self, other: &Delta) -> Delta { + let mut inverted = Delta::default(); + if other.is_empty() { + return inverted; + } + + let a = |inverted: &mut Delta, op: &Operation, index: usize, op_len: usize| { + let ops = other.ops_in_interval(Interval::new(index, op_len)); + ops.into_iter().for_each(|other_op| match op { + Operation::Delete(_) => { + inverted.add(other_op); + }, + Operation::Retain(_) => {}, + Operation::Insert(_) => { + if !op.is_plain() { + let inverted_attrs = + invert_attributes(op.get_attributes(), other_op.get_attributes()); + inverted.retain(other_op.length(), inverted_attrs); + } + }, + }); + }; + + let mut index = 0; + for op in &self.ops { + let op_len: usize = op.length() as usize; + match op { + Operation::Delete(_) => { + a(&mut inverted, op, index, op_len); + index += op_len; + }, + Operation::Retain(_) => { + if op.is_plain() { + inverted.retain(op_len as u64, Attributes::Empty); + } else { + a(&mut inverted, op, index, op_len as usize); + } + index += op_len; + }, + Operation::Insert(insert) => { + inverted.delete(op_len as u64); + }, + } + } + + inverted + } + /// Checks if this operation has no effect. #[inline] pub fn is_noop(&self) -> bool { @@ -477,66 +527,64 @@ impl Delta { } } - /// Returns the length of a string these operations can be applied to - #[inline] - pub fn base_len(&self) -> usize { self.base_len } - - /// Returns the length of the resulting string after the operations have - /// been applied. - #[inline] - pub fn target_len(&self) -> usize { self.target_len } - - /// Returns the wrapped sequence of operations. - #[inline] - pub fn ops(&self) -> &[Operation] { &self.ops } - pub fn is_empty(&self) -> bool { self.ops.is_empty() } -} -fn merge_insert_or_new_op( - insert: &mut Insert, - s: &str, - attributes: Attributes, -) -> Option { - match &attributes { - Attributes::Follow => { - insert.s += s; - return None; - }, - Attributes::Custom(_) | Attributes::Empty => { - if insert.attributes == attributes { - insert.s += s; - None - } else { - Some(OpBuilder::insert(s).attributes(attributes).build()) + pub fn ops_in_interval(&self, interval: Interval) -> Vec { + let mut ops: Vec = Vec::with_capacity(self.ops.len()); + let mut offset: usize = 0; + let mut ops_iter = self.ops.iter(); + let mut op = ops_iter.next(); + + while offset < interval.end && op.is_some() { + if let Some(op) = op { + if offset < interval.start { + offset += op.length() as usize; + } else { + ops.push(op.clone()); + offset += op.length() as usize; + } } - }, - } -} - -fn merge_retain_or_new_op( - retain: &mut Retain, - n: u64, - attributes: Attributes, -) -> Option { - log::debug!( - "merge_retain_or_new_op: {:?}, {:?}", - retain.attributes, - attributes - ); - - match &attributes { - Attributes::Follow => { - retain.num += n; - None - }, - Attributes::Custom(_) | Attributes::Empty => { - if retain.attributes == attributes { - retain.num += n; - None - } else { - Some(OpBuilder::retain(n).attributes(attributes).build()) - } - }, + op = ops_iter.next(); + } + + ops + } + + pub fn attributes_in_interval(&self, interval: Interval) -> Attributes { + let mut attributes_data = AttributesData::new(); + let mut offset: usize = 0; + + self.ops.iter().for_each(|op| match op { + Operation::Delete(_n) => {}, + Operation::Retain(_retain) => { + unimplemented!() + // if interval.contains(retain.n as usize) { + // match &retain.attributes { + // Attributes::Follow => {}, + // Attributes::Custom(data) => { + // attributes_data.extend(data.clone()); + // }, + // Attributes::Empty => {}, + // } + // } + }, + Operation::Insert(insert) => match &insert.attributes { + Attributes::Follow => {}, + Attributes::Custom(data) => { + let end = insert.num_chars() as usize; + if interval.contains_range(offset, offset + end) { + attributes_data.extend(data.clone()); + } + offset += end; + }, + Attributes::Empty => {}, + }, + }); + + if attributes_data.is_plain() { + Attributes::Empty + } else { + Attributes::Custom(attributes_data) + } } } diff --git a/rust-lib/flowy-ot/src/core/interval.rs b/rust-lib/flowy-ot/src/core/interval.rs index 795816a5ce..4875b560c7 100644 --- a/rust-lib/flowy-ot/src/core/interval.rs +++ b/rust-lib/flowy-ot/src/core/interval.rs @@ -33,6 +33,10 @@ impl Interval { pub fn contains(&self, val: usize) -> bool { self.start <= val && val < self.end } + pub fn contains_range(&self, start: usize, end: usize) -> bool { + !self.intersect(Interval::new(start, end)).is_empty() + } + pub fn is_after(&self, val: usize) -> bool { self.start > val } pub fn is_empty(&self) -> bool { self.end <= self.start } diff --git a/rust-lib/flowy-ot/src/core/operation.rs b/rust-lib/flowy-ot/src/core/operation.rs index d1c157af23..848c3f1f2b 100644 --- a/rust-lib/flowy-ot/src/core/operation.rs +++ b/rust-lib/flowy-ot/src/core/operation.rs @@ -73,10 +73,11 @@ impl Operation { pub fn length(&self) -> u64 { match self { Operation::Delete(n) => *n, - Operation::Retain(r) => r.num, + Operation::Retain(r) => r.n, Operation::Insert(i) => i.num_chars(), } } + pub fn is_empty(&self) -> bool { self.length() == 0 } } @@ -89,7 +90,7 @@ impl fmt::Display for Operation { Operation::Retain(r) => { f.write_fmt(format_args!( "retain: {}, attributes: {}", - r.num, r.attributes + r.n, r.attributes ))?; }, Operation::Insert(i) => { @@ -141,15 +142,40 @@ impl OpBuilder { #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Retain { #[serde(rename(serialize = "retain", deserialize = "retain"))] - pub num: u64, + pub n: u64, #[serde(skip_serializing_if = "is_empty")] pub attributes: Attributes, } +impl Retain { + pub fn merge_or_new_op(&mut self, n: u64, attributes: Attributes) -> Option { + log::debug!( + "merge_retain_or_new_op: {:?}, {:?}", + self.attributes, + attributes + ); + + match &attributes { + Attributes::Follow => { + self.n += n; + None + }, + Attributes::Custom(_) | Attributes::Empty => { + if self.attributes == attributes { + self.n += n; + None + } else { + Some(OpBuilder::retain(n).attributes(attributes).build()) + } + }, + } + } +} + impl std::convert::From for Retain { fn from(n: u64) -> Self { Retain { - num: n, + n, attributes: Attributes::default(), } } @@ -158,11 +184,11 @@ impl std::convert::From for Retain { impl Deref for Retain { type Target = u64; - fn deref(&self) -> &Self::Target { &self.num } + fn deref(&self) -> &Self::Target { &self.n } } impl DerefMut for Retain { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.num } + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.n } } #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] @@ -180,6 +206,23 @@ impl Insert { pub fn chars(&self) -> Chars<'_> { self.s.chars() } pub fn num_chars(&self) -> u64 { num_chars(self.s.as_bytes()) as _ } + + pub fn merge_or_new_op(&mut self, s: &str, attributes: Attributes) -> Option { + match &attributes { + Attributes::Follow => { + self.s += s; + return None; + }, + Attributes::Custom(_) | Attributes::Empty => { + if self.attributes == attributes { + self.s += s; + None + } else { + Some(OpBuilder::insert(s).attributes(attributes).build()) + } + }, + } + } } impl std::convert::From for Insert { diff --git a/rust-lib/flowy-ot/tests/attribute_test.rs b/rust-lib/flowy-ot/tests/attribute_test.rs index 2c386dee8e..bfea4b49d4 100644 --- a/rust-lib/flowy-ot/tests/attribute_test.rs +++ b/rust-lib/flowy-ot/tests/attribute_test.rs @@ -1,6 +1,6 @@ pub mod helper; -use crate::helper::{MergeTestOp::*, *}; +use crate::helper::{TestOp::*, *}; use flowy_ot::core::Interval; #[test] @@ -10,7 +10,7 @@ fn delta_insert_text() { Insert(0, "456", 3), AssertOpsJson(0, r#"[{"insert":"123456"}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -20,7 +20,7 @@ fn delta_insert_text_at_head() { Insert(0, "456", 0), AssertOpsJson(0, r#"[{"insert":"456123"}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -30,7 +30,7 @@ fn delta_insert_text_at_middle() { Insert(0, "456", 1), AssertOpsJson(0, r#"[{"insert":"145623"}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -49,7 +49,7 @@ fn delta_insert_text_with_attr() { r#"[{"insert":"1abc2","attributes":{"bold":"true"}},{"insert":"345"}]"#, ), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -61,7 +61,7 @@ fn delta_add_bold_and_invert_all() { Bold(0, Interval::new(0, 3), false), AssertOpsJson(0, r#"[{"insert":"123"}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -76,7 +76,7 @@ fn delta_add_bold_and_invert_partial_suffix() { r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#, ), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -93,7 +93,7 @@ fn delta_add_bold_and_invert_partial_suffix2() { Bold(0, Interval::new(2, 4), true), AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -108,7 +108,7 @@ fn delta_add_bold_and_invert_partial_prefix() { r#"[{"insert":"12"},{"insert":"34","attributes":{"bold":"true"}}]"#, ), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -126,14 +126,14 @@ fn delta_add_bold_consecutive() { r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#, ), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] #[should_panic] fn delta_add_bold_empty_str() { let ops = vec![Bold(0, Interval::new(0, 4), true)]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -157,7 +157,7 @@ fn delta_add_bold_italic() { r#"[{"insert":"1234","attributes":{"italic":"true","bold":"true"}},{"insert":"56"},{"insert":"78","attributes":{"bold":"true","italic":"true"}}]"#, ), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -178,7 +178,7 @@ fn delta_add_bold_italic2() { ), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -203,7 +203,7 @@ fn delta_add_bold_italic3() { ), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -225,7 +225,7 @@ fn delta_add_bold_italic_delete() { ), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -236,7 +236,7 @@ fn delta_merge_inserted_text_with_same_attribute() { InsertBold(0, "456", Interval::new(3, 6)), AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -251,7 +251,7 @@ fn delta_compose_attr_delta_with_attr_delta_test() { AssertOpsJson(1, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -278,7 +278,7 @@ fn delta_compose_attr_delta_with_attr_delta_test2() { ), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -293,7 +293,7 @@ fn delta_compose_attr_delta_with_no_attr_delta_test() { AssertOpsJson(0, expected), AssertOpsJson(1, expected), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -305,7 +305,7 @@ fn delta_delete_heading() { AssertOpsJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -317,7 +317,7 @@ fn delta_delete_trailing() { AssertOpsJson(0, r#"[{"insert":"12345","attributes":{"bold":"true"}}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -331,7 +331,7 @@ fn delta_delete_middle() { AssertOpsJson(0, r#"[{"insert":"34","attributes":{"bold":"true"}}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test] @@ -343,5 +343,5 @@ fn delta_delete_all() { AssertOpsJson(0, r#"[]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index 3cd5df3c20..0445833348 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -3,7 +3,7 @@ use rand::{prelude::*, Rng as WrappedRng}; use std::sync::Once; #[derive(Clone, Debug)] -pub enum MergeTestOp { +pub enum TestOp { Insert(usize, &'static str, usize), // delta_i, s, start, length, InsertBold(usize, &'static str, Interval), @@ -16,11 +16,11 @@ pub enum MergeTestOp { AssertOpsJson(usize, &'static str), } -pub struct MergeTest { +pub struct OpTester { deltas: Vec, } -impl MergeTest { +impl OpTester { pub fn new() -> Self { static INIT: Once = Once::new(); INIT.call_once(|| { @@ -36,29 +36,29 @@ impl MergeTest { Self { deltas } } - pub fn run_op(&mut self, op: &MergeTestOp) { + pub fn run_op(&mut self, op: &TestOp) { match op { - MergeTestOp::Insert(delta_i, s, index) => { + TestOp::Insert(delta_i, s, index) => { self.update_delta_with_insert(*delta_i, s, *index); }, - MergeTestOp::Delete(delta_i, interval) => { + TestOp::Delete(delta_i, interval) => { // self.update_delta_with_delete(*delta_i, interval); }, - MergeTestOp::InsertBold(delta_i, s, _interval) => { + TestOp::InsertBold(delta_i, s, _interval) => { let attrs = AttrsBuilder::new().bold(true).build(); let delta = &mut self.deltas[*delta_i]; delta.insert(s, attrs); }, - MergeTestOp::Bold(delta_i, interval, enable) => { + TestOp::Bold(delta_i, interval, enable) => { let attrs = AttrsBuilder::new().bold(*enable).build(); self.update_delta_with_attribute(*delta_i, attrs, interval); }, - MergeTestOp::Italic(delta_i, interval, enable) => { + TestOp::Italic(delta_i, interval, enable) => { let attrs = AttrsBuilder::new().italic(*enable).build(); self.update_delta_with_attribute(*delta_i, attrs, interval); }, - MergeTestOp::Transform(delta_a_i, delta_b_i) => { + TestOp::Transform(delta_a_i, delta_b_i) => { let delta_a = &self.deltas[*delta_a_i]; let delta_b = &self.deltas[*delta_b_i]; @@ -70,12 +70,12 @@ impl MergeTest { self.deltas[*delta_a_i] = new_delta_a; self.deltas[*delta_b_i] = new_delta_b; }, - MergeTestOp::AssertStr(delta_i, expected) => { + TestOp::AssertStr(delta_i, expected) => { let s = self.deltas[*delta_i].apply("").unwrap(); assert_eq!(&s, expected); }, - MergeTestOp::AssertOpsJson(delta_i, expected) => { + TestOp::AssertOpsJson(delta_i, expected) => { let delta_i_json = serde_json::to_string(&self.deltas[*delta_i]).unwrap(); let expected_delta: Delta = serde_json::from_str(expected).unwrap(); @@ -90,12 +90,14 @@ impl MergeTest { } } - pub fn run_script(&mut self, script: Vec) { + pub fn run_script(&mut self, script: Vec) { for (_i, op) in script.iter().enumerate() { self.run_op(op); } } + pub fn get_delta(&mut self, index: usize) -> &mut Delta { &mut self.deltas[index] } + pub fn update_delta_with_insert(&mut self, delta_index: usize, s: &str, index: usize) { let old_delta = &mut self.deltas[delta_index]; let target_interval = Interval::new(0, old_delta.target_len); @@ -103,7 +105,7 @@ impl MergeTest { log::error!("{} out of bounds {}", index, target_interval); } - let mut attributes = attributes_in_delta(old_delta, &Interval::new(index, index + 1)); + let mut attributes = old_delta.attributes_in_interval(Interval::new(index, index + 1)); if attributes == Attributes::Empty { attributes = Attributes::Follow; } @@ -123,8 +125,8 @@ impl MergeTest { .attributes(attributes) .build(); - let attrs = attributes_in_delta(old_delta, &interval); - retain.extend_attributes(attrs); + let attributes = old_delta.attributes_in_interval(*interval); + retain.extend_attributes(attributes); let new_delta = new_delta_with_op(old_delta, retain, *interval); self.deltas[delta_index] = new_delta; @@ -133,8 +135,8 @@ impl MergeTest { pub fn update_delta_with_delete(&mut self, delta_index: usize, interval: &Interval) { let old_delta = &self.deltas[delta_index]; let mut delete = OpBuilder::delete(interval.size() as u64).build(); - let attrs = attributes_in_delta(old_delta, &interval); - delete.extend_attributes(attrs); + let attributes = old_delta.attributes_in_interval(*interval); + delete.extend_attributes(attributes); let new_delta = new_delta_with_op(old_delta, delete, *interval); self.deltas[delta_index] = new_delta; @@ -149,8 +151,8 @@ fn new_delta_with_op(delta: &Delta, op: Operation, interval: Interval) -> Delta if prefix.is_empty() == false && prefix != interval { let intervals = split_interval_with_delta(delta, &prefix); intervals.into_iter().for_each(|interval| { - let attrs = attributes_in_delta(delta, &interval); - new_delta.retain(interval.size() as u64, attrs); + let attributes = delta.attributes_in_interval(interval); + new_delta.retain(interval.size() as u64, attributes); }); } @@ -160,8 +162,8 @@ fn new_delta_with_op(delta: &Delta, op: Operation, interval: Interval) -> Delta if suffix.is_empty() == false { let intervals = split_interval_with_delta(delta, &suffix); intervals.into_iter().for_each(|interval| { - let attrs = attributes_in_delta(delta, &interval); - new_delta.retain(interval.size() as u64, attrs); + let attributes = delta.attributes_in_interval(interval); + new_delta.retain(interval.size() as u64, attributes); }); } @@ -200,47 +202,7 @@ pub fn target_length_split_with_interval( } pub fn debug_print_delta(delta: &Delta) { - log::debug!("😁 {}", serde_json::to_string(delta).unwrap()); -} - -pub fn attributes_in_delta(delta: &Delta, interval: &Interval) -> Attributes { - let mut attributes_data = AttributesData::new(); - let mut offset: usize = 0; - - delta.ops.iter().for_each(|op| match op { - Operation::Delete(_n) => {}, - Operation::Retain(retain) => { - if interval.contains(retain.num as usize) { - match &retain.attributes { - Attributes::Follow => {}, - Attributes::Custom(data) => { - attributes_data.extend(data.clone()); - }, - Attributes::Empty => {}, - } - } - }, - Operation::Insert(insert) => match &insert.attributes { - Attributes::Follow => {}, - Attributes::Custom(data) => { - let end = insert.num_chars() as usize; - if !interval - .intersect(Interval::new(offset, offset + end)) - .is_empty() - { - attributes_data.extend(data.clone()); - } - offset += end; - }, - Attributes::Empty => {}, - }, - }); - - if attributes_data.is_plain() { - Attributes::Empty - } else { - Attributes::Custom(attributes_data) - } + eprintln!("😁 {}", serde_json::to_string(delta).unwrap()); } pub struct Rng(StdRng); @@ -259,7 +221,7 @@ impl Rng { pub fn gen_delta(&mut self, s: &str) -> Delta { let mut delta = Delta::default(); loop { - let left = s.chars().count() - delta.base_len(); + let left = s.chars().count() - delta.base_len; if left == 0 { break; } diff --git a/rust-lib/flowy-ot/tests/invert_test.rs b/rust-lib/flowy-ot/tests/invert_test.rs new file mode 100644 index 0000000000..03aea3a6f7 --- /dev/null +++ b/rust-lib/flowy-ot/tests/invert_test.rs @@ -0,0 +1,68 @@ +pub mod helper; +use crate::helper::{TestOp::*, *}; +use flowy_ot::core::{Delta, Interval, OpBuilder}; + +#[test] +fn delta_invert_delta_test() { + let mut delta = Delta::default(); + delta.add(OpBuilder::insert("123").build()); + + let mut change = Delta::default(); + change.add(OpBuilder::retain(3).build()); + change.add(OpBuilder::insert("456").build()); + let undo = change.invert_delta(&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_get_ops_in_interval_1() { + let mut delta = Delta::default(); + let insert_a = OpBuilder::insert("123").build(); + let insert_b = OpBuilder::insert("4").build(); + + delta.add(insert_a.clone()); + delta.add(insert_b.clone()); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 3)), + vec![delta.ops.last().unwrap().clone()] + ); +} + +#[test] +fn delta_get_ops_in_interval_2() { + let mut delta = Delta::default(); + let insert_a = OpBuilder::insert("123").build(); + let insert_b = OpBuilder::insert("4").build(); + let insert_c = OpBuilder::insert("5").build(); + let retain_a = OpBuilder::retain(3).build(); + + delta.add(insert_a.clone()); + delta.add(retain_a.clone()); + delta.add(insert_b.clone()); + delta.add(insert_c.clone()); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 3)), + vec![insert_a.clone()] + ); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 4)), + vec![insert_a.clone(), retain_a.clone()] + ); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 7)), + vec![ + insert_a.clone(), + retain_a.clone(), + // insert_b and insert_c will be merged into one. insert: "45" + delta.ops.last().unwrap().clone() + ] + ); +} diff --git a/rust-lib/flowy-ot/tests/op_test.rs b/rust-lib/flowy-ot/tests/op_test.rs index da43e74aae..327580c13b 100644 --- a/rust-lib/flowy-ot/tests/op_test.rs +++ b/rust-lib/flowy-ot/tests/op_test.rs @@ -1,6 +1,6 @@ pub mod helper; -use crate::helper::MergeTestOp::*; +use crate::helper::TestOp::*; use bytecount::num_chars; use flowy_ot::core::*; use helper::*; @@ -196,7 +196,7 @@ fn transform2() { AssertOpsJson(0, r#"[{"insert":"123456"}]"#), AssertOpsJson(1, r#"[{"insert":"123456"}]"#), ]; - MergeTest::new().run_script(ops); + OpTester::new().run_script(ops); } #[test]