diff --git a/rust-lib/flowy-ot/src/attributes.rs b/rust-lib/flowy-ot/src/attributes.rs index cd0cb9433e..5583dc986e 100644 --- a/rust-lib/flowy-ot/src/attributes.rs +++ b/rust-lib/flowy-ot/src/attributes.rs @@ -1,58 +1,97 @@ use crate::operation::Operation; -use std::collections::{hash_map::RandomState, HashMap}; +use std::collections::HashMap; + +const PLAIN: &'static str = ""; +fn is_plain(s: &str) -> bool { s == PLAIN } + +#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +pub enum Attributes { + #[serde(skip)] + Follow, + Custom(AttributesData), + #[serde(skip)] + Empty, +} + +impl Attributes { + pub fn merge(&self, other: Option) -> Attributes { + let other = other.unwrap_or(Attributes::Empty); + match (self, &other) { + (Attributes::Custom(data), Attributes::Custom(o_data)) => { + let mut data = data.clone(); + data.extend(o_data.clone()); + Attributes::Custom(data) + }, + _ => other, + } + } + // remove attribute if the value is PLAIN + pub fn remove_plain(&mut self) { + match self { + Attributes::Follow => {}, + Attributes::Custom(data) => { + data.remove_plain(); + }, + Attributes::Empty => {}, + } + } +} + +impl std::default::Default for Attributes { + fn default() -> Self { Attributes::Empty } +} #[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct Attributes { +pub struct AttributesData { #[serde(skip_serializing_if = "HashMap::is_empty")] #[serde(flatten)] inner: HashMap, } -impl Attributes { +impl AttributesData { pub fn new() -> Self { - Attributes { + AttributesData { inner: HashMap::new(), } } - pub fn remove_empty(&mut self) { self.inner.retain(|_, v| v.is_empty() == false); } + pub fn remove_plain(&mut self) { self.inner.retain(|_, v| !is_plain(v)); } - pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); } + pub fn extend(&mut self, other: AttributesData) { self.inner.extend(other.inner); } - pub fn is_empty(&self) -> bool { self.inner.is_empty() } + pub fn is_plain(&self) -> bool { self.inner.values().filter(|v| !is_plain(v)).count() == 0 } } -impl std::convert::From> for Attributes { - fn from(attributes: HashMap) -> Self { - Attributes { inner: attributes } - } +impl std::convert::From> for AttributesData { + fn from(attributes: HashMap) -> Self { AttributesData { inner: attributes } } } -impl std::ops::Deref for Attributes { +impl std::ops::Deref for AttributesData { type Target = HashMap; fn deref(&self) -> &Self::Target { &self.inner } } -impl std::ops::DerefMut for Attributes { +impl std::ops::DerefMut for AttributesData { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } pub struct AttrsBuilder { - inner: Attributes, + inner: AttributesData, } impl AttrsBuilder { pub fn new() -> Self { Self { - inner: Attributes::default(), + inner: AttributesData::default(), } } pub fn bold(mut self, bold: bool) -> Self { let val = match bold { true => "true", - false => "", + false => PLAIN, }; self.inner.insert("bold".to_owned(), val.to_owned()); self @@ -61,7 +100,7 @@ impl AttrsBuilder { pub fn italic(mut self, italic: bool) -> Self { let val = match italic { true => "true", - false => "", + false => PLAIN, }; self.inner.insert("italic".to_owned(), val.to_owned()); self @@ -72,98 +111,108 @@ impl AttrsBuilder { self } - pub fn build(self) -> Attributes { self.inner } + pub fn build(self) -> Attributes { Attributes::Custom(self.inner) } } pub fn attributes_from(operation: &Option) -> Option { match operation { None => None, - Some(operation) => operation.get_attributes(), + Some(operation) => Some(operation.get_attributes()), } } -pub fn compose_attributes( - op1: &Option, - op2: &Option, - keep_empty: bool, -) -> Option { - let a = attributes_from(op1); - let b = attributes_from(op2); - - if a.is_none() && b.is_none() { - return None; +pub fn compose_attributes(left: &Option, right: &Option) -> Attributes { + if left.is_none() && right.is_none() { + return Attributes::Empty; } + let attr_l = attributes_from(left); + let attr_r = attributes_from(right); + log::trace!("compose_attributes: a: {:?}, b: {:?}", attr_l, attr_r); - let mut attrs_a = a.unwrap_or(Attributes::default()); - let attrs_b = b.unwrap_or(Attributes::default()); + let mut attr = match (&attr_l, &attr_r) { + (_, Some(Attributes::Custom(_))) => match &attr_l { + None => attr_r.unwrap(), + Some(_) => attr_l.unwrap().merge(attr_r.clone()), + }, + (Some(Attributes::Custom(_)), _) => attr_l.unwrap().merge(attr_r), + _ => Attributes::Empty, + }; - log::trace!( - "before compose_attributes: a: {:?}, b: {:?}", - attrs_a, - attrs_b - ); - attrs_a.extend(attrs_b); - log::trace!("after compose_attributes: a: {:?}", attrs_a); - if !keep_empty { - attrs_a.remove_empty() - } + log::trace!("composed_attributes: a: {:?}", attr); - Some(attrs_a) + // remove the attribute if the value is PLAIN + attr.remove_plain(); + + attr } pub fn transform_attributes( - op1: &Option, - op2: &Option, + left: &Option, + right: &Option, priority: bool, -) -> Option { - let a = attributes_from(op1); - let b = attributes_from(op2); +) -> Attributes { + let attr_l = attributes_from(left); + let attr_r = attributes_from(right); - if a.is_none() { - return b; - } + if attr_l.is_none() { + if attr_r.is_none() { + return Attributes::Empty; + } - if b.is_none() { - return None; + return match attr_r.as_ref().unwrap() { + Attributes::Follow => Attributes::Empty, + Attributes::Custom(_) => attr_r.unwrap(), + Attributes::Empty => Attributes::Empty, + }; } if !priority { - return b; + return attr_r.unwrap(); } - let attrs_a = a.unwrap_or(Attributes::default()); - let attrs_b = b.unwrap_or(Attributes::default()); - - let result = attrs_b - .iter() - .fold(Attributes::new(), |mut attributes, (k, v)| { - if attrs_a.contains_key(k) == false { - attributes.insert(k.clone(), v.clone()); - } - attributes - }); - Some(result) + match (attr_l.unwrap(), attr_r.unwrap()) { + (Attributes::Custom(attr_data_l), Attributes::Custom(attr_data_r)) => { + let result = transform_attribute_data(attr_data_l, attr_data_r); + Attributes::Custom(result) + }, + _ => Attributes::Empty, + } } -pub fn invert_attributes(attr: Option, base: Option) -> Attributes { - let attr = attr.unwrap_or(Attributes::new()); - let base = base.unwrap_or(Attributes::new()); - - let base_inverted = base +fn transform_attribute_data(left: AttributesData, right: AttributesData) -> AttributesData { + let result = right .iter() - .fold(Attributes::new(), |mut attributes, (k, v)| { - if base.get(k) != attr.get(k) && attr.contains_key(k) { - attributes.insert(k.clone(), v.clone()); + .fold(AttributesData::new(), |mut new_attr_data, (k, v)| { + if !left.contains_key(k) { + new_attr_data.insert(k.clone(), v.clone()); } - attributes + new_attr_data }); - - 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; + 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/delta.rs b/rust-lib/flowy-ot/src/delta.rs index 44d5beca70..df23dd3356 100644 --- a/rust-lib/flowy-ot/src/delta.rs +++ b/rust-lib/flowy-ot/src/delta.rs @@ -1,6 +1,6 @@ use crate::{attributes::*, errors::OTError, operation::*}; use bytecount::num_chars; -use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator, str::FromStr}; +use std::{cmp::Ordering, iter::FromIterator, str::FromStr}; #[derive(Clone, Debug, PartialEq)] pub struct Delta { @@ -73,7 +73,7 @@ impl Delta { } } - pub fn insert(&mut self, s: &str, attrs: Option) { + pub fn insert(&mut self, s: &str, attrs: Attributes) { if s.is_empty() { return; } @@ -102,7 +102,7 @@ impl Delta { } } - pub fn retain(&mut self, n: u64, attrs: Option) { + pub fn retain(&mut self, n: u64, attrs: Attributes) { if n == 0 { return; } @@ -149,14 +149,17 @@ impl Delta { next_op1 = ops1.next(); }, (_, Some(Operation::Insert(o_insert))) => { - new_delta.insert(&o_insert.s, attributes_from(&next_op2)); + new_delta.insert( + &o_insert.s, + attributes_from(&next_op2).unwrap_or(Attributes::Empty), + ); next_op2 = ops2.next(); }, (None, _) | (_, None) => { return Err(OTError); }, (Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => { - let composed_attrs = compose_attributes(&next_op1, &next_op2, false); + let composed_attrs = compose_attributes(&next_op1, &next_op2); log::debug!( "[retain:{} - retain:{}]: {:?}", retain.num, @@ -186,6 +189,7 @@ impl Delta { Ordering::Less => { next_op2 = Some( OpBuilder::delete(*o_num - num_chars(insert.as_bytes()) as u64) + .attributes(insert.attributes.clone()) .build(), ); next_op1 = ops1.next(); @@ -206,7 +210,7 @@ impl Delta { } }, (Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => { - let composed_attrs = compose_attributes(&next_op1, &next_op2, false); + let composed_attrs = compose_attributes(&next_op1, &next_op2); log::debug!( "[insert:{} - retain:{}]: {:?}", insert.s, @@ -234,7 +238,11 @@ impl Delta { &chars.take(o_retain.num as usize).collect::(), composed_attrs, ); - next_op1 = Some(OpBuilder::insert(&chars.collect::()).build()); + next_op1 = Some( + OpBuilder::insert(&chars.collect::()) + .attributes(Attributes::Empty) + .build(), + ); next_op2 = ops2.next(); }, } @@ -297,7 +305,7 @@ impl Delta { (_, Some(Operation::Insert(o_insert))) => { let composed_attrs = transform_attributes(&next_op1, &next_op2, true); a_prime.retain(o_insert.num_chars(), composed_attrs.clone()); - b_prime.insert(&o_insert.s, composed_attrs.clone()); + b_prime.insert(&o_insert.s, composed_attrs); next_op2 = ops2.next(); }, (None, _) => { @@ -427,7 +435,7 @@ impl Delta { for op in &self.ops { match &op { Operation::Retain(retain) => { - inverted.retain(retain.num, None); + inverted.retain(retain.num, Attributes::Follow); for _ in 0..retain.num { chars.next(); } @@ -475,41 +483,47 @@ impl Delta { fn merge_insert_or_new_op( insert: &mut Insert, s: &str, - attributes: Option, + attributes: Attributes, ) -> Option { - if attributes.is_none() { - insert.s += s; - return None; - } - - if insert.attributes == attributes { - insert.s += s; - None - } else { - Some(OpBuilder::insert(s).attributes(attributes).build()) + 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()) + } + }, } } fn merge_retain_or_new_op( retain: &mut Retain, n: u64, - attributes: Option, + attributes: Attributes, ) -> Option { - log::trace!( + log::debug!( "merge_retain_or_new_op: {:?}, {:?}", retain.attributes, attributes ); - if attributes.is_none() { - retain.num += n; - return None; - } - - if retain.attributes == attributes { - retain.num += n; - None - } else { - Some(OpBuilder::retain(n).attributes(attributes).build()) + 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()) + } + }, } } diff --git a/rust-lib/flowy-ot/src/interval.rs b/rust-lib/flowy-ot/src/interval.rs index f63c90ede1..e5d9a6422b 100644 --- a/rust-lib/flowy-ot/src/interval.rs +++ b/rust-lib/flowy-ot/src/interval.rs @@ -1,7 +1,7 @@ use std::{ cmp::{max, min}, fmt, - ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, + ops::{Range, RangeInclusive, RangeTo, RangeToInclusive}, }; /// Representing a closed-open range; diff --git a/rust-lib/flowy-ot/src/operation.rs b/rust-lib/flowy-ot/src/operation.rs index 0bbf70c5b1..5f476ec280 100644 --- a/rust-lib/flowy-ot/src/operation.rs +++ b/rust-lib/flowy-ot/src/operation.rs @@ -1,8 +1,6 @@ use crate::attributes::Attributes; use bytecount::num_chars; use std::{ - cmp::Ordering, - collections::{hash_map::RandomState, HashMap}, ops::{Deref, DerefMut}, str::Chars, }; @@ -29,17 +27,19 @@ impl Operation { } } - pub fn get_attributes(&self) -> Option { + pub fn get_attributes(&self) -> Attributes { match self { - Operation::Delete(_) => None, + Operation::Delete(_) => Attributes::Empty, Operation::Retain(retain) => retain.attributes.clone(), Operation::Insert(insert) => insert.attributes.clone(), } } - pub fn set_attributes(&mut self, attributes: Option) { + pub fn set_attributes(&mut self, attributes: Attributes) { match self { - Operation::Delete(_) => {}, + Operation::Delete(_) => { + log::error!("Delete should not contains attributes"); + }, Operation::Retain(retain) => { retain.attributes = attributes; }, @@ -49,7 +49,13 @@ impl Operation { } } - pub fn is_plain(&self) -> bool { self.get_attributes().is_none() } + pub fn is_plain(&self) -> bool { + match self.get_attributes() { + Attributes::Follow => true, + Attributes::Custom(_) => false, + Attributes::Empty => true, + } + } pub fn length(&self) -> u64 { match self { @@ -63,11 +69,16 @@ impl Operation { pub struct OpBuilder { ty: Operation, - attrs: Option, + attrs: Attributes, } impl OpBuilder { - pub fn new(ty: Operation) -> OpBuilder { OpBuilder { ty, attrs: None } } + pub fn new(ty: Operation) -> OpBuilder { + OpBuilder { + ty, + attrs: Attributes::Empty, + } + } pub fn retain(n: u64) -> OpBuilder { OpBuilder::new(Operation::Retain(n.into())) } @@ -75,15 +86,8 @@ impl OpBuilder { pub fn insert(s: &str) -> OpBuilder { OpBuilder::new(Operation::Insert(s.into())) } - pub fn attributes(mut self, attrs: Option) -> OpBuilder { - match attrs { - None => self.attrs = attrs, - Some(attrs) => match attrs.is_empty() { - true => self.attrs = None, - false => self.attrs = Some(attrs), - }, - } - + pub fn attributes(mut self, attrs: Attributes) -> OpBuilder { + self.attrs = attrs; self } @@ -103,14 +107,14 @@ pub struct Retain { #[serde(rename(serialize = "retain", deserialize = "retain"))] pub num: u64, #[serde(skip_serializing_if = "is_empty")] - pub attributes: Option, + pub attributes: Attributes, } impl std::convert::From for Retain { fn from(n: u64) -> Self { Retain { num: n, - attributes: None, + attributes: Attributes::default(), } } } @@ -131,7 +135,7 @@ pub struct Insert { pub s: String, #[serde(skip_serializing_if = "is_empty")] - pub attributes: Option, + pub attributes: Attributes, } impl Insert { @@ -146,23 +150,22 @@ impl std::convert::From for Insert { fn from(s: String) -> Self { Insert { s, - attributes: None, + attributes: Attributes::default(), } } } impl std::convert::From<&str> for Insert { - fn from(s: &str) -> Self { - Insert { - s: s.to_owned(), - attributes: None, - } - } + fn from(s: &str) -> Self { Insert::from(s.to_owned()) } } -fn is_empty(attributes: &Option) -> bool { +fn is_empty(attributes: &Attributes) -> bool { match attributes { - None => true, - Some(attributes) => attributes.is_empty(), + Attributes::Follow => true, + Attributes::Custom(data) => { + let is_empty = data.is_plain(); + is_empty + }, + Attributes::Empty => true, } } diff --git a/rust-lib/flowy-ot/src/operation_serde.rs b/rust-lib/flowy-ot/src/operation_serde.rs index 6570d74c79..f54c37bba2 100644 --- a/rust-lib/flowy-ot/src/operation_serde.rs +++ b/rust-lib/flowy-ot/src/operation_serde.rs @@ -8,7 +8,7 @@ use serde::{ Serialize, Serializer, }; -use std::{collections::HashMap, fmt}; +use std::fmt; impl Serialize for Operation { fn serialize(&self, serializer: S) -> Result @@ -82,7 +82,7 @@ impl<'de> Deserialize<'de> for Operation { match operation { None => Err(de::Error::missing_field("operation")), Some(mut operation) => { - operation.set_attributes(attributes); + operation.set_attributes(attributes.unwrap_or(Attributes::Empty)); Ok(operation) }, } diff --git a/rust-lib/flowy-ot/tests/attribute_test.rs b/rust-lib/flowy-ot/tests/attribute_test.rs index efbc79f9ab..f32727381b 100644 --- a/rust-lib/flowy-ot/tests/attribute_test.rs +++ b/rust-lib/flowy-ot/tests/attribute_test.rs @@ -1,18 +1,13 @@ pub mod helper; -use crate::{ - helper::{MergeTestOp::*, *}, - MergeTestOp::*, -}; +use crate::helper::{MergeTestOp::*, *}; use flowy_ot::{ - attributes::{Attributes, AttrsBuilder}, - delta::Delta, interval::Interval, operation::{OpBuilder, Operation, Retain}, }; #[test] -fn delta_add_bold_attr1() { +fn delta_add_bold_and_invert_all() { let ops = vec![ Insert(0, "123"), Bold(0, Interval::new(0, 3), true), @@ -24,7 +19,7 @@ fn delta_add_bold_attr1() { } #[test] -fn delta_add_bold_attr2() { +fn delta_add_bold_and_invert_partial_suffix() { let ops = vec![ Insert(0, "1234"), Bold(0, Interval::new(0, 4), true), @@ -39,7 +34,24 @@ fn delta_add_bold_attr2() { } #[test] -fn delta_add_bold_attr3() { +fn delta_add_bold_and_invert_partial_suffix2() { + let ops = vec![ + Insert(0, "1234"), + Bold(0, Interval::new(0, 4), true), + AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#), + Bold(0, Interval::new(2, 4), false), + AssertOpsJson( + 0, + r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#, + ), + Bold(0, Interval::new(2, 4), true), + AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#), + ]; + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_add_bold_and_invert_partial_prefix() { let ops = vec![ Insert(0, "1234"), Bold(0, Interval::new(0, 4), true), @@ -53,6 +65,31 @@ fn delta_add_bold_attr3() { MergeTest::new().run_script(ops); } +#[test] +fn delta_add_bold_consecutive() { + let ops = vec![ + Insert(0, "1234"), + Bold(0, Interval::new(0, 1), true), + AssertOpsJson( + 0, + r#"[{"insert":"1","attributes":{"bold":"true"}},{"insert":"234"}]"#, + ), + Bold(0, Interval::new(1, 2), true), + AssertOpsJson( + 0, + r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#, + ), + ]; + MergeTest::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); +} + #[test] fn delta_add_bold_italic() { let ops = vec![ @@ -77,23 +114,6 @@ fn delta_add_bold_italic() { MergeTest::new().run_script(ops); } -#[test] -fn delta_add_bold_attr_and_invert() { - let ops = vec![ - Insert(0, "1234"), - Bold(0, Interval::new(0, 4), true), - AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#), - Bold(0, Interval::new(2, 4), false), - AssertOpsJson( - 0, - r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#, - ), - Bold(0, Interval::new(2, 4), true), - AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#), - ]; - MergeTest::new().run_script(ops); -} - #[test] fn delta_merge_inserted_text_with_same_attribute() { let ops = vec![ @@ -134,3 +154,53 @@ fn delta_compose_attr_delta_with_attr_delta_test() { MergeTest::new().run_script(ops); } + +#[test] +fn delta_delete_heading() { + let ops = vec![ + InsertBold(0, "123456", Interval::new(0, 6)), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + Delete(0, Interval::new(0, 2)), + AssertOpsJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#), + ]; + + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_delete_trailing() { + let ops = vec![ + InsertBold(0, "123456", Interval::new(0, 6)), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + Delete(0, Interval::new(5, 6)), + AssertOpsJson(0, r#"[{"insert":"12345","attributes":{"bold":"true"}}]"#), + ]; + + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_delete_middle() { + let ops = vec![ + InsertBold(0, "123456", Interval::new(0, 6)), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + Delete(0, Interval::new(0, 2)), + AssertOpsJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#), + Delete(0, Interval::new(2, 4)), + AssertOpsJson(0, r#"[{"insert":"34","attributes":{"bold":"true"}}]"#), + ]; + + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_delete_all() { + let ops = vec![ + InsertBold(0, "123456", Interval::new(0, 6)), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + Delete(0, Interval::new(0, 6)), + AssertOpsJson(0, r#"[]"#), + ]; + + MergeTest::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 948c7d0fd0..f87fde3168 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -1,5 +1,5 @@ use flowy_ot::{ - attributes::{Attributes, AttrsBuilder}, + attributes::{Attributes, AttributesData, AttrsBuilder}, delta::Delta, interval::Interval, operation::{OpBuilder, Operation}, @@ -7,50 +7,6 @@ use flowy_ot::{ use rand::{prelude::*, Rng as WrappedRng}; use std::sync::Once; -pub struct Rng(StdRng); - -impl Default for Rng { - fn default() -> Self { Rng(StdRng::from_rng(thread_rng()).unwrap()) } -} - -impl Rng { - pub fn from_seed(seed: [u8; 32]) -> Self { Rng(StdRng::from_seed(seed)) } - - pub fn gen_string(&mut self, len: usize) -> String { - (0..len).map(|_| self.0.gen::()).collect() - } - - pub fn gen_delta(&mut self, s: &str) -> Delta { - let mut delta = Delta::default(); - loop { - let left = s.chars().count() - delta.base_len(); - if left == 0 { - break; - } - let i = if left == 1 { - 1 - } else { - 1 + self.0.gen_range(0, std::cmp::min(left - 1, 20)) - }; - match self.0.gen_range(0.0, 1.0) { - f if f < 0.2 => { - delta.insert(&self.gen_string(i), None); - }, - f if f < 0.4 => { - delta.delete(i as u64); - }, - _ => { - delta.retain(i as u64, None); - }, - } - } - if self.0.gen_range(0.0, 1.0) < 0.3 { - delta.insert(&("1".to_owned() + &self.gen_string(10)), None); - } - delta - } -} - #[derive(Clone, Debug)] pub enum MergeTestOp { Insert(usize, &'static str), @@ -58,6 +14,7 @@ pub enum MergeTestOp { InsertBold(usize, &'static str, Interval), // delta_i, start, length, enable Bold(usize, Interval, bool), + Delete(usize, Interval), Italic(usize, Interval, bool), Transform(usize, usize), AssertStr(usize, &'static str), @@ -88,26 +45,31 @@ impl MergeTest { match op { MergeTestOp::Insert(delta_i, s) => { let delta = &mut self.deltas[*delta_i]; - delta.insert(s, None); + delta.insert(s, Attributes::Follow); }, - MergeTestOp::InsertBold(delta_i, s, interval) => { + MergeTestOp::Delete(delta_i, interval) => { + // + self.update_delta_with_delete(*delta_i, interval); + }, + MergeTestOp::InsertBold(delta_i, s, _interval) => { let attrs = AttrsBuilder::new().bold(true).build(); let delta = &mut self.deltas[*delta_i]; - delta.insert(s, Some(attrs)); + delta.insert(s, attrs); }, MergeTestOp::Bold(delta_i, interval, enable) => { let attrs = AttrsBuilder::new().bold(*enable).build(); - self.replace_delta(*delta_i, attrs, interval); + self.update_delta_with_attribute(*delta_i, attrs, interval); }, MergeTestOp::Italic(delta_i, interval, enable) => { let attrs = AttrsBuilder::new().italic(*enable).build(); - self.replace_delta(*delta_i, attrs, interval); + self.update_delta_with_attribute(*delta_i, attrs, interval); }, MergeTestOp::Transform(delta_a_i, delta_b_i) => { let delta_a = &self.deltas[*delta_a_i]; let delta_b = &self.deltas[*delta_b_i]; let (a_prime, b_prime) = delta_a.transform(delta_b).unwrap(); + log::trace!("a:{:?},b:{:?}", a_prime, b_prime); let new_delta_a = delta_a.compose(&b_prime).unwrap(); let new_delta_b = delta_b.compose(&a_prime).unwrap(); @@ -120,54 +82,62 @@ impl MergeTest { }, MergeTestOp::AssertOpsJson(delta_i, expected) => { - let expected_delta: Delta = serde_json::from_str(expected).unwrap(); - let delta_i_json = serde_json::to_string(&self.deltas[*delta_i]).unwrap(); - let delta: Delta = serde_json::from_str(&delta_i_json).unwrap(); - if expected_delta != delta { + let expected_delta: Delta = serde_json::from_str(expected).unwrap(); + let target_delta: Delta = serde_json::from_str(&delta_i_json).unwrap(); + + if expected_delta != target_delta { log::error!("✅ {}", expected); log::error!("❌ {}", delta_i_json); } - assert_eq!(delta, expected_delta); + assert_eq!(target_delta, expected_delta); }, } } pub fn run_script(&mut self, script: Vec) { - for (i, op) in script.iter().enumerate() { + for (_i, op) in script.iter().enumerate() { self.run_op(op); } } - pub fn replace_delta( + pub fn update_delta_with_attribute( &mut self, delta_index: usize, attributes: Attributes, interval: &Interval, ) { let old_delta = &self.deltas[delta_index]; - let new_delta = delta_with_attribute(old_delta, attributes, interval); + let retain = OpBuilder::retain(interval.size() as u64) + .attributes(attributes) + .build(); + let new_delta = make_delta_with_op(old_delta, retain, interval); + self.deltas[delta_index] = new_delta; + } + + pub fn update_delta_with_delete(&mut self, delta_index: usize, interval: &Interval) { + let old_delta = &self.deltas[delta_index]; + let delete = OpBuilder::delete(interval.size() as u64).build(); + let new_delta = make_delta_with_op(old_delta, delete, interval); self.deltas[delta_index] = new_delta; } } -pub fn delta_with_attribute(delta: &Delta, attributes: Attributes, interval: &Interval) -> Delta { - let delta_interval = Interval::new(0, delta.target_len); - +pub fn make_delta_with_op(delta: &Delta, op: Operation, interval: &Interval) -> Delta { let mut new_delta = Delta::default(); - let prefix = delta_interval.prefix(*interval); + let (prefix, suffix) = length_split_with_interval(delta.target_len, interval); + + // prefix if prefix.is_empty() == false && prefix != *interval { let size = prefix.size(); let attrs = attributes_in_interval(delta, &prefix); new_delta.retain(size as u64, attrs); } - let size = interval.size(); - log::debug!("Apply attribute {:?} to {}", attributes, interval); - new_delta.retain(size as u64, Some(attributes)); + new_delta.add(op); - let suffix = delta_interval.suffix(*interval); + // suffix if suffix.is_empty() == false { let size = suffix.size(); let attrs = attributes_in_interval(delta, &suffix); @@ -177,31 +147,93 @@ pub fn delta_with_attribute(delta: &Delta, attributes: Attributes, interval: &In delta.compose(&new_delta).unwrap() } +pub fn length_split_with_interval(length: usize, interval: &Interval) -> (Interval, Interval) { + let original_interval = Interval::new(0, length); + let prefix = original_interval.prefix(*interval); + let suffix = original_interval.suffix(*interval); + (prefix, suffix) +} + pub fn debug_print_delta(delta: &Delta) { log::debug!("😁 {}", serde_json::to_string(delta).unwrap()); } -pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Option { - let mut attributes = Attributes::new(); +pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Attributes { + let mut attributes_data = AttributesData::new(); let mut offset = 0; delta.ops.iter().for_each(|op| match op { - Operation::Delete(n) => {}, + Operation::Delete(_n) => {}, Operation::Retain(retain) => { - if retain.attributes.is_some() { - if interval.contains(retain.num as usize) { - attributes.extend(retain.attributes.as_ref().unwrap().clone()); + 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) => { - if insert.attributes.is_some() { + Operation::Insert(insert) => match &insert.attributes { + Attributes::Follow => {}, + Attributes::Custom(data) => { if interval.start >= offset && insert.num_chars() > (interval.end as u64 - 1) { - attributes.extend(insert.attributes.as_ref().unwrap().clone()); + attributes_data.extend(data.clone()); } offset += insert.num_chars() as usize; - } + }, + Attributes::Empty => {}, }, }); - Some(attributes) + + if attributes_data.is_plain() { + Attributes::Empty + } else { + Attributes::Custom(attributes_data) + } +} + +pub struct Rng(StdRng); + +impl Default for Rng { + fn default() -> Self { Rng(StdRng::from_rng(thread_rng()).unwrap()) } +} + +impl Rng { + pub fn from_seed(seed: [u8; 32]) -> Self { Rng(StdRng::from_seed(seed)) } + + pub fn gen_string(&mut self, len: usize) -> String { + (0..len).map(|_| self.0.gen::()).collect() + } + + pub fn gen_delta(&mut self, s: &str) -> Delta { + let mut delta = Delta::default(); + loop { + let left = s.chars().count() - delta.base_len(); + if left == 0 { + break; + } + let i = if left == 1 { + 1 + } else { + 1 + self.0.gen_range(0, std::cmp::min(left - 1, 20)) + }; + match self.0.gen_range(0.0, 1.0) { + f if f < 0.2 => { + delta.insert(&self.gen_string(i), Attributes::Empty); + }, + f if f < 0.4 => { + delta.delete(i as u64); + }, + _ => { + delta.retain(i as u64, Attributes::Empty); + }, + } + } + if self.0.gen_range(0.0, 1.0) < 0.3 { + delta.insert(&("1".to_owned() + &self.gen_string(10)), Attributes::Empty); + } + delta + } } diff --git a/rust-lib/flowy-ot/tests/op_test.rs b/rust-lib/flowy-ot/tests/op_test.rs index af3783994d..7c456739ab 100644 --- a/rust-lib/flowy-ot/tests/op_test.rs +++ b/rust-lib/flowy-ot/tests/op_test.rs @@ -2,26 +2,21 @@ pub mod helper; use crate::helper::MergeTestOp::*; use bytecount::num_chars; -use flowy_ot::{ - attributes::*, - delta::Delta, - operation::{OpBuilder, Operation}, -}; +use flowy_ot::{attributes::*, delta::Delta, operation::OpBuilder}; use helper::*; -use std::str::FromStr; #[test] fn lengths() { let mut delta = Delta::default(); assert_eq!(delta.base_len, 0); assert_eq!(delta.target_len, 0); - delta.retain(5, None); + delta.retain(5, Attributes::Empty); assert_eq!(delta.base_len, 5); assert_eq!(delta.target_len, 5); - delta.insert("abc", None); + delta.insert("abc", Attributes::Empty); assert_eq!(delta.base_len, 5); assert_eq!(delta.target_len, 8); - delta.retain(2, None); + delta.retain(2, Attributes::Empty); assert_eq!(delta.base_len, 7); assert_eq!(delta.target_len, 10); delta.delete(2); @@ -31,10 +26,10 @@ fn lengths() { #[test] fn sequence() { let mut delta = Delta::default(); - delta.retain(5, None); - delta.retain(0, None); - delta.insert("appflowy", None); - delta.insert("", None); + delta.retain(5, Attributes::Empty); + delta.retain(0, Attributes::Empty); + delta.insert("appflowy", Attributes::Empty); + delta.insert("", Attributes::Empty); delta.delete(3); delta.delete(0); assert_eq!(delta.ops.len(), 3); @@ -55,11 +50,11 @@ fn apply_1000() { fn apply() { let s = "hello world,".to_owned(); let mut delta_a = Delta::default(); - delta_a.insert(&s, None); + delta_a.insert(&s, Attributes::Empty); let mut delta_b = Delta::default(); - delta_b.retain(s.len() as u64, None); - delta_b.insert("appflowy", None); + delta_b.retain(s.len() as u64, Attributes::Empty); + delta_b.insert("appflowy", Attributes::Empty); let after_a = delta_a.apply("").unwrap(); let after_b = delta_b.apply(&after_a).unwrap(); @@ -69,15 +64,15 @@ fn apply() { #[test] fn base_len_test() { let mut delta_a = Delta::default(); - delta_a.insert("a", None); - delta_a.insert("b", None); - delta_a.insert("c", None); + delta_a.insert("a", Attributes::Empty); + delta_a.insert("b", Attributes::Empty); + delta_a.insert("c", Attributes::Empty); let s = "hello world,".to_owned(); delta_a.delete(s.len() as u64); let after_a = delta_a.apply(&s).unwrap(); - delta_a.insert("d", None); + delta_a.insert("d", Attributes::Empty); assert_eq!("abc", &after_a); } @@ -97,8 +92,8 @@ fn invert() { #[test] fn empty_ops() { let mut delta = Delta::default(); - delta.retain(0, None); - delta.insert("", None); + delta.retain(0, Attributes::Empty); + delta.insert("", Attributes::Empty); delta.delete(0); assert_eq!(delta.ops.len(), 0); } @@ -106,33 +101,33 @@ fn empty_ops() { fn eq() { let mut delta_a = Delta::default(); delta_a.delete(1); - delta_a.insert("lo", None); - delta_a.retain(2, None); - delta_a.retain(3, None); + delta_a.insert("lo", Attributes::Empty); + delta_a.retain(2, Attributes::Empty); + delta_a.retain(3, Attributes::Empty); let mut delta_b = Delta::default(); delta_b.delete(1); - delta_b.insert("l", None); - delta_b.insert("o", None); - delta_b.retain(5, None); + delta_b.insert("l", Attributes::Empty); + delta_b.insert("o", Attributes::Empty); + delta_b.retain(5, Attributes::Empty); assert_eq!(delta_a, delta_b); delta_a.delete(1); - delta_b.retain(1, None); + delta_b.retain(1, Attributes::Empty); assert_ne!(delta_a, delta_b); } #[test] fn ops_merging() { let mut delta = Delta::default(); assert_eq!(delta.ops.len(), 0); - delta.retain(2, None); + delta.retain(2, Attributes::Empty); assert_eq!(delta.ops.len(), 1); assert_eq!(delta.ops.last(), Some(&OpBuilder::retain(2).build())); - delta.retain(3, None); + delta.retain(3, Attributes::Empty); assert_eq!(delta.ops.len(), 1); assert_eq!(delta.ops.last(), Some(&OpBuilder::retain(5).build())); - delta.insert("abc", None); + delta.insert("abc", Attributes::Empty); assert_eq!(delta.ops.len(), 2); assert_eq!(delta.ops.last(), Some(&OpBuilder::insert("abc").build())); - delta.insert("xyz", None); + delta.insert("xyz", Attributes::Empty); assert_eq!(delta.ops.len(), 2); assert_eq!(delta.ops.last(), Some(&OpBuilder::insert("abcxyz").build())); delta.delete(1); @@ -146,11 +141,11 @@ fn ops_merging() { fn is_noop() { let mut delta = Delta::default(); assert!(delta.is_noop()); - delta.retain(5, None); + delta.retain(5, Attributes::Empty); assert!(delta.is_noop()); - delta.retain(3, None); + delta.retain(3, Attributes::Empty); assert!(delta.is_noop()); - delta.insert("lorem", None); + delta.insert("lorem", Attributes::Empty); assert!(!delta.is_noop()); } #[test] @@ -184,9 +179,9 @@ fn transform() { let ba_prime = b.compose(&a_prime).unwrap(); assert_eq!(ab_prime, ba_prime); - // let after_ab_prime = ab_prime.apply(&s).unwrap(); - // let after_ba_prime = ba_prime.apply(&s).unwrap(); - // assert_eq!(after_ab_prime, after_ba_prime); + let after_ab_prime = ab_prime.apply(&s).unwrap(); + let after_ba_prime = ba_prime.apply(&s).unwrap(); + assert_eq!(after_ab_prime, after_ba_prime); } } @@ -208,13 +203,13 @@ fn transform2() { fn delta_transform_test() { let mut a = Delta::default(); let mut a_s = String::new(); - a.insert("123", Some(AttrsBuilder::new().bold(true).build())); + a.insert("123", AttrsBuilder::new().bold(true).build()); a_s = a.apply(&a_s).unwrap(); assert_eq!(&a_s, "123"); let mut b = Delta::default(); let mut b_s = String::new(); - b.insert("456", None); + b.insert("456", Attributes::Empty); b_s = b.apply(&b_s).unwrap(); assert_eq!(&b_s, "456"); diff --git a/rust-lib/flowy-ot/tests/serde_test.rs b/rust-lib/flowy-ot/tests/serde_test.rs index a93b03517d..dba59e7875 100644 --- a/rust-lib/flowy-ot/tests/serde_test.rs +++ b/rust-lib/flowy-ot/tests/serde_test.rs @@ -1,5 +1,5 @@ use flowy_ot::{ - attributes::{Attributes, AttrsBuilder}, + attributes::AttrsBuilder, delta::Delta, operation::{OpBuilder, Operation, Retain}, }; @@ -7,9 +7,7 @@ use flowy_ot::{ #[test] fn operation_insert_serialize_test() { let attributes = AttrsBuilder::new().bold(true).italic(true).build(); - let operation = OpBuilder::insert("123") - .attributes(Some(attributes)) - .build(); + let operation = OpBuilder::insert("123").attributes(attributes).build(); let json = serde_json::to_string(&operation).unwrap(); eprintln!("{}", json); @@ -39,9 +37,7 @@ fn delta_serialize_test() { let mut delta = Delta::default(); let attributes = AttrsBuilder::new().bold(true).italic(true).build(); - let retain = OpBuilder::insert("123") - .attributes(Some(attributes)) - .build(); + let retain = OpBuilder::insert("123").attributes(attributes).build(); delta.add(retain); delta.add(Operation::Retain(5.into()));