From 0b82336b6c7b3f2285eb0ec9f9dcfa5b4b76f951 Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 2 Aug 2021 18:35:25 +0800 Subject: [PATCH] config flowy ot attributes and add attribute test --- .../flowy_editor/example/pubspec.lock | 8 +- app_flowy/packages/flowy_editor/pubspec.lock | 8 +- rust-lib/flowy-ot/Cargo.toml | 3 + rust-lib/flowy-ot/src/attributes.rs | 32 +-- rust-lib/flowy-ot/src/delta.rs | 171 ++++++++------- rust-lib/flowy-ot/src/interval.rs | 200 ++++++++++++++++++ rust-lib/flowy-ot/src/lib.rs | 1 + rust-lib/flowy-ot/src/operation.rs | 16 +- rust-lib/flowy-ot/tests/attribute_test.rs | 114 +++++++++- rust-lib/flowy-ot/tests/helper/mod.rs | 145 ++++++++++++- rust-lib/flowy-ot/tests/op_test.rs | 40 ++++ rust-lib/flowy-ot/tests/serde_test.rs | 6 +- 12 files changed, 620 insertions(+), 124 deletions(-) create mode 100644 rust-lib/flowy-ot/src/interval.rs diff --git a/app_flowy/packages/flowy_editor/example/pubspec.lock b/app_flowy/packages/flowy_editor/example/pubspec.lock index e713df21de..e826de3f4d 100644 --- a/app_flowy/packages/flowy_editor/example/pubspec.lock +++ b/app_flowy/packages/flowy_editor/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.6.1" + version: "2.7.0" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" clock: dependency: transitive description: @@ -141,7 +141,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" nested: dependency: transitive description: @@ -302,7 +302,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.1" tuple: dependency: transitive description: diff --git a/app_flowy/packages/flowy_editor/pubspec.lock b/app_flowy/packages/flowy_editor/pubspec.lock index 554a00eb29..f36d3b7a53 100644 --- a/app_flowy/packages/flowy_editor/pubspec.lock +++ b/app_flowy/packages/flowy_editor/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.6.1" + version: "2.7.0" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" clock: dependency: transitive description: @@ -113,7 +113,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" path: dependency: transitive description: @@ -211,7 +211,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.1" tuple: dependency: "direct main" description: diff --git a/rust-lib/flowy-ot/Cargo.toml b/rust-lib/flowy-ot/Cargo.toml index a4ba029d72..b0e1124cb9 100644 --- a/rust-lib/flowy-ot/Cargo.toml +++ b/rust-lib/flowy-ot/Cargo.toml @@ -9,7 +9,10 @@ edition = "2018" bytecount = "0.6.0" serde = { version = "1.0", features = ["derive"] } serde_json = {version = "1.0"} +log = "0.4" [dev-dependencies] criterion = "0.3" rand = "0.7.3" +env_logger = "0.8.2" + diff --git a/rust-lib/flowy-ot/src/attributes.rs b/rust-lib/flowy-ot/src/attributes.rs index 283d835a5d..0c0ba650a8 100644 --- a/rust-lib/flowy-ot/src/attributes.rs +++ b/rust-lib/flowy-ot/src/attributes.rs @@ -15,9 +15,11 @@ impl Attributes { } } - pub fn remove_empty_value(&mut self) { self.inner.retain(|_, v| v.is_empty()); } + pub fn remove_empty_value(&mut self) { self.inner.retain(|_, v| v.is_empty() == false); } pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); } + + pub fn is_empty(&self) -> bool { self.inner.is_empty() } } impl std::convert::From> for Attributes { @@ -36,11 +38,11 @@ impl std::ops::DerefMut for Attributes { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } -pub struct AttributesBuilder { +pub struct AttrsBuilder { inner: Attributes, } -impl AttributesBuilder { +impl AttrsBuilder { pub fn new() -> Self { Self { inner: Attributes::default(), @@ -52,6 +54,11 @@ impl AttributesBuilder { self } + pub fn un_bold(mut self) -> Self { + self.inner.insert("bold".to_owned(), "".to_owned()); + self + } + pub fn italic(mut self) -> Self { self.inner.insert("italic".to_owned(), "true".to_owned()); self @@ -68,7 +75,7 @@ impl AttributesBuilder { pub fn attributes_from(operation: &Option) -> Option { match operation { None => None, - Some(operation) => operation.attributes(), + Some(operation) => operation.get_attributes(), } } @@ -79,22 +86,19 @@ pub fn compose_attributes( ) -> Option { let a = attributes_from(op1); let b = attributes_from(op2); - - if a.is_none() { - return b; - } - - if b.is_none() { - return None; - } - let mut attrs_a = a.unwrap_or(Attributes::default()); let attrs_b = b.unwrap_or(Attributes::default()); - attrs_a.extend(attrs_b); + // log::debug!( + // "before compose_attributes: a: {:?}, b: {:?}", + // attrs_a, + // attrs_b + // ); + attrs_a.extend(attrs_b); if !keep_empty { attrs_a.remove_empty_value() } + // log::debug!("after compose_attributes: a: {:?}", attrs_a); return if attrs_a.is_empty() { None diff --git a/rust-lib/flowy-ot/src/delta.rs b/rust-lib/flowy-ot/src/delta.rs index 537bc007c5..17376e6703 100644 --- a/rust-lib/flowy-ot/src/delta.rs +++ b/rust-lib/flowy-ot/src/delta.rs @@ -57,7 +57,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.n, r.attributes), + Operation::Retain(r) => self.retain(r.num, r.attributes), } } @@ -80,13 +80,13 @@ impl Delta { self.target_len += num_chars(s.as_bytes()); let new_last = match self.ops.as_mut_slice() { - [.., Operation::Insert(s_last)] => { + [.., Operation::Insert(insert)] => { // - merge_insert_or_new_op(s_last, s, attrs) + merge_insert_or_new_op(insert, s, attrs) }, - [.., Operation::Insert(s_pre_last), Operation::Delete(_)] => { + [.., Operation::Insert(pre_insert), Operation::Delete(_)] => { // - merge_insert_or_new_op(s_pre_last, s, attrs) + merge_insert_or_new_op(pre_insert, s, attrs) }, [.., op_last @ Operation::Delete(_)] => { let new_last = op_last.clone(); @@ -109,8 +109,8 @@ impl Delta { self.base_len += n as usize; self.target_len += n as usize; - if let Some(Operation::Retain(i_last)) = self.ops.last_mut() { - match merge_retain_or_new_op(i_last, n, attrs) { + 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), } @@ -148,38 +148,39 @@ impl Delta { new_delta.delete(*i); next_op1 = ops1.next(); }, - (_, Some(Operation::Insert(insert))) => { - new_delta.insert(&insert.s, attributes_from(&next_op2)); + (_, Some(Operation::Insert(o_insert))) => { + new_delta.insert(&o_insert.s, attributes_from(&next_op2)); next_op2 = ops2.next(); }, (None, _) | (_, None) => { return Err(OTError); }, - (Some(Operation::Retain(i)), Some(Operation::Retain(j))) => { - let new_attrs = compose_attributes(&next_op1, &next_op2, true); - match i.cmp(&j) { + (Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => { + let composed_attrs = compose_attributes(&next_op1, &next_op2, true); + match retain.cmp(&o_retain) { Ordering::Less => { - new_delta.retain(i.n, new_attrs); - next_op2 = Some(OpBuilder::retain(j.n - i.n).build()); + new_delta.retain(retain.num, composed_attrs); + next_op2 = Some(OpBuilder::retain(o_retain.num - retain.num).build()); next_op1 = ops1.next(); }, std::cmp::Ordering::Equal => { - new_delta.retain(i.n, new_attrs); + new_delta.retain(retain.num, composed_attrs); next_op1 = ops1.next(); next_op2 = ops2.next(); }, std::cmp::Ordering::Greater => { - new_delta.retain(j.n, new_attrs); - next_op1 = Some(OpBuilder::retain(i.n - j.n).build()); + new_delta.retain(o_retain.num, composed_attrs); + next_op1 = Some(OpBuilder::retain(retain.num - o_retain.num).build()); next_op2 = ops2.next(); }, } }, - (Some(Operation::Insert(insert)), Some(Operation::Delete(j))) => { - match (num_chars(insert.as_bytes()) as u64).cmp(j) { + (Some(Operation::Insert(insert)), Some(Operation::Delete(o_num))) => { + match (num_chars(insert.as_bytes()) as u64).cmp(o_num) { Ordering::Less => { next_op2 = Some( - OpBuilder::delete(*j - num_chars(insert.as_bytes()) as u64).build(), + OpBuilder::delete(*o_num - num_chars(insert.as_bytes()) as u64) + .build(), ); next_op1 = ops1.next(); }, @@ -190,7 +191,7 @@ impl Delta { Ordering::Greater => { next_op1 = Some( OpBuilder::insert( - &insert.chars().skip(*j as usize).collect::(), + &insert.chars().skip(*o_num as usize).collect::(), ) .build(), ); @@ -198,44 +199,52 @@ impl Delta { }, } }, - (Some(Operation::Insert(insert)), Some(Operation::Retain(j))) => { - let new_attrs = compose_attributes(&next_op1, &next_op2, false); - match (insert.num_chars()).cmp(j) { + (Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => { + let composed_attrs = compose_attributes(&next_op1, &next_op2, false); + match (insert.num_chars()).cmp(o_retain) { Ordering::Less => { - new_delta.insert(&insert.s, new_attrs); - next_op2 = Some(OpBuilder::retain(j.n - insert.num_chars()).build()); + new_delta.insert(&insert.s, composed_attrs.clone()); + next_op2 = Some( + OpBuilder::retain(o_retain.num - insert.num_chars()) + .attributes(composed_attrs.clone()) + .build(), + ); next_op1 = ops1.next(); }, Ordering::Equal => { - new_delta.insert(&insert.s, new_attrs); + new_delta.insert(&insert.s, composed_attrs); next_op1 = ops1.next(); next_op2 = ops2.next(); }, Ordering::Greater => { let chars = &mut insert.chars(); - new_delta - .insert(&chars.take(j.n as usize).collect::(), new_attrs); + new_delta.insert( + &chars.take(o_retain.num as usize).collect::(), + composed_attrs, + ); next_op1 = Some(OpBuilder::insert(&chars.collect::()).build()); next_op2 = ops2.next(); }, } }, - (Some(Operation::Retain(i)), Some(Operation::Delete(j))) => match i.cmp(&j) { - Ordering::Less => { - new_delta.delete(i.n); - next_op2 = Some(OpBuilder::delete(*j - i.n).build()); - next_op1 = ops1.next(); - }, - Ordering::Equal => { - new_delta.delete(*j); - next_op2 = ops2.next(); - next_op1 = ops1.next(); - }, - Ordering::Greater => { - new_delta.delete(*j); - next_op1 = Some(OpBuilder::retain(i.n - *j).build()); - next_op2 = ops2.next(); - }, + (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()); + next_op1 = ops1.next(); + }, + Ordering::Equal => { + new_delta.delete(*o_num); + next_op2 = ops2.next(); + next_op1 = ops1.next(); + }, + Ordering::Greater => { + new_delta.delete(*o_num); + next_op1 = Some(OpBuilder::retain(retain.num - *o_num).build()); + next_op2 = ops2.next(); + }, + } }, }; } @@ -268,15 +277,15 @@ impl Delta { match (&next_op1, &next_op2) { (None, None) => break, (Some(Operation::Insert(insert)), _) => { - let new_attrs = compose_attributes(&next_op1, &next_op2, true); - a_prime.insert(&insert.s, new_attrs.clone()); - b_prime.retain(insert.num_chars(), new_attrs.clone()); + // let composed_attrs = transform_attributes(&next_op1, &next_op2, true); + a_prime.insert(&insert.s, insert.attributes.clone()); + b_prime.retain(insert.num_chars(), insert.attributes.clone()); next_op1 = ops1.next(); }, - (_, Some(Operation::Insert(insert))) => { - let new_attrs = compose_attributes(&next_op1, &next_op2, true); - a_prime.retain(insert.num_chars(), new_attrs.clone()); - b_prime.insert(&insert.s, new_attrs.clone()); + (_, 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()); next_op2 = ops2.next(); }, (None, _) => { @@ -285,25 +294,25 @@ impl Delta { (_, None) => { return Err(OTError); }, - (Some(Operation::Retain(i)), Some(Operation::Retain(j))) => { - let new_attrs = compose_attributes(&next_op1, &next_op2, true); - match i.cmp(&j) { + (Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => { + let composed_attrs = transform_attributes(&next_op1, &next_op2, true); + match retain.cmp(&o_retain) { Ordering::Less => { - a_prime.retain(i.n, new_attrs.clone()); - b_prime.retain(i.n, new_attrs.clone()); - next_op2 = Some(OpBuilder::retain(j.n - i.n).build()); + 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()); next_op1 = ops1.next(); }, Ordering::Equal => { - a_prime.retain(i.n, new_attrs.clone()); - b_prime.retain(i.n, new_attrs.clone()); + a_prime.retain(retain.num, composed_attrs.clone()); + b_prime.retain(retain.num, composed_attrs.clone()); next_op1 = ops1.next(); next_op2 = ops2.next(); }, Ordering::Greater => { - a_prime.retain(j.n, new_attrs.clone()); - b_prime.retain(j.n, new_attrs.clone()); - next_op1 = Some(OpBuilder::retain(i.n - j.n).build()); + 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()); next_op2 = ops2.next(); }, }; @@ -322,11 +331,11 @@ impl Delta { next_op2 = ops2.next(); }, }, - (Some(Operation::Delete(i)), Some(Operation::Retain(j))) => { - match i.cmp(&j) { + (Some(Operation::Delete(i)), Some(Operation::Retain(o_retain))) => { + match i.cmp(&o_retain) { Ordering::Less => { a_prime.delete(*i); - next_op2 = Some(OpBuilder::retain(j.n - *i).build()); + next_op2 = Some(OpBuilder::retain(o_retain.num - *i).build()); next_op1 = ops1.next(); }, Ordering::Equal => { @@ -335,27 +344,27 @@ impl Delta { next_op2 = ops2.next(); }, Ordering::Greater => { - a_prime.delete(j.n); - next_op1 = Some(OpBuilder::delete(*i - j.n).build()); + a_prime.delete(o_retain.num); + next_op1 = Some(OpBuilder::delete(*i - o_retain.num).build()); next_op2 = ops2.next(); }, }; }, - (Some(Operation::Retain(i)), Some(Operation::Delete(j))) => { - match i.cmp(&j) { + (Some(Operation::Retain(retain)), Some(Operation::Delete(j))) => { + match retain.cmp(&j) { Ordering::Less => { - b_prime.delete(i.n); - next_op2 = Some(OpBuilder::delete(*j - i.n).build()); + b_prime.delete(retain.num); + next_op2 = Some(OpBuilder::delete(*j - retain.num).build()); next_op1 = ops1.next(); }, Ordering::Equal => { - b_prime.delete(i.n); + b_prime.delete(retain.num); next_op1 = ops1.next(); next_op2 = ops2.next(); }, Ordering::Greater => { b_prime.delete(*j); - next_op1 = Some(OpBuilder::retain(i.n - *j).build()); + next_op1 = Some(OpBuilder::retain(retain.num - *j).build()); next_op2 = ops2.next(); }, }; @@ -381,7 +390,7 @@ impl Delta { for op in &self.ops { match &op { Operation::Retain(retain) => { - for c in chars.take(retain.n as usize) { + for c in chars.take(retain.num as usize) { new_s.push(c); } }, @@ -406,8 +415,8 @@ impl Delta { for op in &self.ops { match &op { Operation::Retain(retain) => { - inverted.retain(retain.n, None); - for _ in 0..retain.n { + inverted.retain(retain.num, None); + for _ in 0..retain.num { chars.next(); } }, @@ -417,7 +426,7 @@ impl Delta { Operation::Delete(delete) => { inverted.insert( &chars.take(*delete as usize).collect::(), - op.attributes(), + op.get_attributes(), ); }, } @@ -470,12 +479,12 @@ fn merge_retain_or_new_op( attributes: Option, ) -> Option { if attributes.is_none() { - retain.n += n; + retain.num += n; return None; } - + // log::debug!("merge retain: {:?}, {:?}", retain.attributes, attributes); if retain.attributes == attributes { - retain.n += n; + 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 new file mode 100644 index 0000000000..f63c90ede1 --- /dev/null +++ b/rust-lib/flowy-ot/src/interval.rs @@ -0,0 +1,200 @@ +use std::{ + cmp::{max, min}, + fmt, + ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, +}; + +/// Representing a closed-open range; +/// the interval [5, 7) is the set {5, 6}. +/// +/// It is an invariant that `start <= end`. An interval where `end < start` is +/// considered empty. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Interval { + pub start: usize, + pub end: usize, +} + +impl Interval { + /// Construct a new `Interval` representing the range [start..end). + /// It is an invariant that `start <= end`. + pub fn new(start: usize, end: usize) -> Interval { + debug_assert!(start <= end); + Interval { start, end } + } + + pub fn start(&self) -> usize { self.start } + + pub fn end(&self) -> usize { self.end } + + pub fn start_end(&self) -> (usize, usize) { (self.start, self.end) } + + pub fn is_before(&self, val: usize) -> bool { self.end <= val } + + pub fn contains(&self, val: usize) -> bool { self.start <= val && val < self.end } + + pub fn is_after(&self, val: usize) -> bool { self.start > val } + + pub fn is_empty(&self) -> bool { self.end <= self.start } + + pub fn intersect(&self, other: Interval) -> Interval { + let start = max(self.start, other.start); + let end = min(self.end, other.end); + Interval { + start, + end: max(start, end), + } + } + + // the first half of self - other + pub fn prefix(&self, other: Interval) -> Interval { + Interval { + start: min(self.start, other.start), + end: min(self.end, other.start), + } + } + + // the second half of self - other + pub fn suffix(&self, other: Interval) -> Interval { + Interval { + start: max(self.start, other.end), + end: max(self.end, other.end), + } + } + + pub fn translate(&self, amount: usize) -> Interval { + Interval { + start: self.start + amount, + end: self.end + amount, + } + } + + pub fn translate_neg(&self, amount: usize) -> Interval { + debug_assert!(self.start >= amount); + Interval { + start: self.start - amount, + end: self.end - amount, + } + } + + pub fn size(&self) -> usize { self.end - self.start } +} + +impl fmt::Display for Interval { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{}, {})", self.start(), self.end()) + } +} + +impl fmt::Debug for Interval { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } +} + +impl From> for Interval { + fn from(src: Range) -> Interval { + let Range { start, end } = src; + Interval { start, end } + } +} + +impl From> for Interval { + fn from(src: RangeTo) -> Interval { Interval::new(0, src.end) } +} + +impl From> for Interval { + fn from(src: RangeInclusive) -> Interval { + Interval::new(*src.start(), src.end().saturating_add(1)) + } +} + +impl From> for Interval { + fn from(src: RangeToInclusive) -> Interval { + Interval::new(0, src.end.saturating_add(1)) + } +} + +#[cfg(test)] +mod tests { + use crate::interval::Interval; + + #[test] + fn contains() { + let i = Interval::new(2, 42); + assert!(!i.contains(1)); + assert!(i.contains(2)); + assert!(i.contains(3)); + assert!(i.contains(41)); + assert!(!i.contains(42)); + assert!(!i.contains(43)); + } + + #[test] + fn before() { + let i = Interval::new(2, 42); + assert!(!i.is_before(1)); + assert!(!i.is_before(2)); + assert!(!i.is_before(3)); + assert!(!i.is_before(41)); + assert!(i.is_before(42)); + assert!(i.is_before(43)); + } + + #[test] + fn after() { + let i = Interval::new(2, 42); + assert!(i.is_after(1)); + assert!(!i.is_after(2)); + assert!(!i.is_after(3)); + assert!(!i.is_after(41)); + assert!(!i.is_after(42)); + assert!(!i.is_after(43)); + } + + #[test] + fn translate() { + let i = Interval::new(2, 42); + assert_eq!(Interval::new(5, 45), i.translate(3)); + assert_eq!(Interval::new(1, 41), i.translate_neg(1)); + } + + #[test] + fn empty() { + assert!(Interval::new(0, 0).is_empty()); + assert!(Interval::new(1, 1).is_empty()); + assert!(!Interval::new(1, 2).is_empty()); + } + + #[test] + fn intersect() { + assert_eq!( + Interval::new(2, 3), + Interval::new(1, 3).intersect(Interval::new(2, 4)) + ); + assert!(Interval::new(1, 2) + .intersect(Interval::new(2, 43)) + .is_empty()); + } + + #[test] + fn prefix() { + assert_eq!( + Interval::new(1, 2), + Interval::new(1, 4).prefix(Interval::new(2, 3)) + ); + } + + #[test] + fn suffix() { + assert_eq!( + Interval::new(3, 4), + Interval::new(1, 4).suffix(Interval::new(2, 3)) + ); + } + + #[test] + fn size() { + assert_eq!(40, Interval::new(2, 42).size()); + assert_eq!(0, Interval::new(1, 1).size()); + assert_eq!(1, Interval::new(1, 2).size()); + } +} diff --git a/rust-lib/flowy-ot/src/lib.rs b/rust-lib/flowy-ot/src/lib.rs index 375cc3c628..e0bf6884a2 100644 --- a/rust-lib/flowy-ot/src/lib.rs +++ b/rust-lib/flowy-ot/src/lib.rs @@ -1,5 +1,6 @@ pub mod attributes; pub mod delta; pub mod errors; +pub mod interval; pub mod operation; mod operation_serde; diff --git a/rust-lib/flowy-ot/src/operation.rs b/rust-lib/flowy-ot/src/operation.rs index 722e07e863..7404bf3a3b 100644 --- a/rust-lib/flowy-ot/src/operation.rs +++ b/rust-lib/flowy-ot/src/operation.rs @@ -29,7 +29,7 @@ impl Operation { } } - pub fn attributes(&self) -> Option { + pub fn get_attributes(&self) -> Option { match self { Operation::Delete(_) => None, Operation::Retain(retain) => retain.attributes.clone(), @@ -49,12 +49,12 @@ impl Operation { } } - pub fn is_plain(&self) -> bool { self.attributes().is_none() } + pub fn is_plain(&self) -> bool { self.get_attributes().is_none() } pub fn length(&self) -> u64 { match self { Operation::Delete(n) => *n, - Operation::Retain(r) => r.n, + Operation::Retain(r) => r.num, Operation::Insert(i) => i.num_chars(), } } @@ -94,15 +94,15 @@ impl OpBuilder { #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Retain { #[serde(rename(serialize = "retain", deserialize = "retain"))] - pub n: u64, + pub num: u64, #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) attributes: Option, + pub attributes: Option, } impl std::convert::From for Retain { fn from(n: u64) -> Self { Retain { - n, + num: n, attributes: None, } } @@ -111,11 +111,11 @@ impl std::convert::From for Retain { impl Deref for Retain { type Target = u64; - fn deref(&self) -> &Self::Target { &self.n } + fn deref(&self) -> &Self::Target { &self.num } } impl DerefMut for Retain { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.n } + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.num } } #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] diff --git a/rust-lib/flowy-ot/tests/attribute_test.rs b/rust-lib/flowy-ot/tests/attribute_test.rs index 46b4f81294..1770cc6b15 100644 --- a/rust-lib/flowy-ot/tests/attribute_test.rs +++ b/rust-lib/flowy-ot/tests/attribute_test.rs @@ -1,16 +1,112 @@ +pub mod helper; + +use crate::{ + helper::{MergeTestOp::*, *}, + MergeTestOp::*, +}; use flowy_ot::{ - attributes::{Attributes, AttributesBuilder}, + attributes::{Attributes, AttrsBuilder}, delta::Delta, + interval::Interval, operation::{OpBuilder, Operation, Retain}, }; #[test] -fn attribute_insert_merge_test() { - let mut delta = Delta::default(); - delta.insert("123", Some(AttributesBuilder::new().bold().build())); - delta.insert("456", Some(AttributesBuilder::new().bold().build())); - assert_eq!( - r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#, - serde_json::to_string(&delta).unwrap() - ) +fn delta_add_bold_attr1() { + let ops = vec![ + Insert(0, "123"), + Bold(0, Interval::new(0, 3), true), + AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#), + Bold(0, Interval::new(0, 3), false), + AssertOpsJson(0, r#"[{"insert":"123"}]"#), + ]; + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_add_bold_attr2() { + 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"}]"#, + ), + ]; + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_add_bold_attr3() { + 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(0, 2), false), + AssertOpsJson( + 0, + r#"[{"insert":"12"},{"insert":"34","attributes":{"bold":"true"}}]"#, + ), + ]; + 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![ + InsertBold(0, "123", Interval::new(0, 3)), + AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#), + InsertBold(0, "456", Interval::new(3, 6)), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + ]; + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_compose_attr_delta_with_no_attr_delta_test() { + let expected = r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"7"}]"#; + let ops = vec![ + InsertBold(0, "123456", Interval::new(0, 6)), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + Insert(1, "7"), + AssertOpsJson(1, r#"[{"insert":"7"}]"#), + Transform(0, 1), + AssertOpsJson(0, expected), + AssertOpsJson(1, expected), + ]; + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_compose_attr_delta_with_attr_delta_test() { + let ops = vec![ + InsertBold(0, "123456", Interval::new(0, 6)), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + InsertBold(1, "7", Interval::new(0, 1)), + AssertOpsJson(1, r#"[{"insert":"7","attributes":{"bold":"true"}}]"#), + Transform(0, 1), + AssertOpsJson(0, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#), + AssertOpsJson(1, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#), + ]; + + 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 450583dc6a..5147e0afeb 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -1,5 +1,11 @@ -use flowy_ot::delta::Delta; +use flowy_ot::{ + attributes::{Attributes, AttrsBuilder}, + delta::Delta, + interval::Interval, + operation::{OpBuilder, Operation}, +}; use rand::{prelude::*, Rng as WrappedRng}; +use std::sync::Once; pub struct Rng(StdRng); @@ -44,3 +50,140 @@ impl Rng { delta } } + +#[derive(Clone, Debug)] +pub enum MergeTestOp { + Insert(usize, &'static str), + // delta_i, s, start, length, + InsertBold(usize, &'static str, Interval), + // delta_i, start, length, enable + Bold(usize, Interval, bool), + Transform(usize, usize), + AssertStr(usize, &'static str), + AssertOpsJson(usize, &'static str), +} + +pub struct MergeTest { + deltas: Vec, +} + +impl MergeTest { + pub fn new() -> Self { + static INIT: Once = Once::new(); + INIT.call_once(|| { + std::env::set_var("RUST_LOG", "debug"); + env_logger::init(); + }); + + let mut deltas = Vec::with_capacity(2); + for _ in 0..2 { + let delta = Delta::default(); + deltas.push(delta); + } + Self { deltas } + } + + pub fn run_op(&mut self, op: &MergeTestOp) { + match op { + MergeTestOp::Insert(delta_i, s) => { + let delta = &mut self.deltas[*delta_i]; + delta.insert(s, None); + }, + MergeTestOp::InsertBold(delta_i, s, interval) => { + let attrs = AttrsBuilder::new().bold().build(); + let delta = &mut self.deltas[*delta_i]; + delta.insert(s, Some(attrs)); + }, + MergeTestOp::Bold(delta_i, interval, enable) => { + let attrs = if *enable { + AttrsBuilder::new().bold().build() + } else { + AttrsBuilder::new().un_bold().build() + }; + let delta = &mut self.deltas[*delta_i]; + let delta_interval = Interval::new(0, delta.target_len); + + let mut new_delta = Delta::default(); + let prefix = delta_interval.prefix(*interval); + if prefix.is_empty() == false && prefix != *interval { + let size = prefix.size(); + // get attr in prefix interval + let attrs = attributes_in_interval(delta, &prefix); + new_delta.retain(size as u64, Some(attrs)); + } + + let size = interval.size(); + new_delta.retain(size as u64, Some(attrs)); + + let suffix = delta_interval.suffix(*interval); + if suffix.is_empty() == false { + let size = suffix.size(); + let attrs = attributes_in_interval(delta, &suffix); + new_delta.retain(size as u64, Some(attrs)); + } + + let a = delta.compose(&new_delta).unwrap(); + self.deltas[*delta_i] = a; + }, + 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(); + let new_delta_a = delta_a.compose(&b_prime).unwrap(); + let new_delta_b = delta_b.compose(&a_prime).unwrap(); + + self.deltas[*delta_a_i] = new_delta_a; + self.deltas[*delta_b_i] = new_delta_b; + }, + MergeTestOp::AssertStr(delta_i, expected) => { + let s = self.deltas[*delta_i].apply("").unwrap(); + assert_eq!(&s, expected); + }, + + MergeTestOp::AssertOpsJson(delta_i, expected) => { + let s = serde_json::to_string(&self.deltas[*delta_i]).unwrap(); + if &s != expected { + log::error!("{}", s); + } + + assert_eq!(&s, expected); + }, + } + } + + pub fn run_script(&mut self, script: Vec) { + for (i, op) in script.iter().enumerate() { + self.run_op(op); + } + } +} + +pub fn debug_print_delta(delta: &Delta) { + log::debug!("😁 {}", serde_json::to_string(delta).unwrap()); +} + +pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Attributes { + let mut attributes = Attributes::new(); + let mut offset = 0; + + delta.ops.iter().for_each(|op| match op { + 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()); + } + } + }, + Operation::Insert(insert) => { + if insert.attributes.is_some() { + if interval.start >= offset || insert.num_chars() > interval.end as u64 { + attributes.extend(insert.attributes.as_ref().unwrap().clone()); + } + offset += insert.num_chars() as usize; + } + }, + }); + attributes +} diff --git a/rust-lib/flowy-ot/tests/op_test.rs b/rust-lib/flowy-ot/tests/op_test.rs index e681433332..5a3abf233e 100644 --- a/rust-lib/flowy-ot/tests/op_test.rs +++ b/rust-lib/flowy-ot/tests/op_test.rs @@ -1,5 +1,6 @@ pub mod helper; +use crate::helper::MergeTestOp::*; use bytecount::num_chars; use flowy_ot::{ attributes::*, @@ -7,6 +8,7 @@ use flowy_ot::{ operation::{OpBuilder, Operation}, }; use helper::*; +use std::str::FromStr; #[test] fn lengths() { @@ -91,6 +93,7 @@ fn invert() { assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s); } } + #[test] fn empty_ops() { let mut delta = Delta::default(); @@ -185,3 +188,40 @@ fn transform() { assert_eq!(after_ab_prime, after_ba_prime); } } + +#[test] +fn transform2() { + let ops = vec![ + Insert(0, "123"), + Insert(1, "456"), + Transform(0, 1), + AssertStr(0, "123456"), + AssertStr(1, "123456"), + AssertOpsJson(0, r#"[{"insert":"123456"}]"#), + AssertOpsJson(1, r#"[{"insert":"123456"}]"#), + ]; + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_transform_test() { + let mut a = Delta::default(); + let mut a_s = String::new(); + a.insert("123", Some(AttrsBuilder::new().bold().build())); + a_s = a.apply(&a_s).unwrap(); + + let mut b = Delta::default(); + let mut b_s = String::new(); + b.insert("456", None); + b_s = a.apply(&b_s).unwrap(); + + let (a_prime, b_prime) = a.transform(&b).unwrap(); + assert_eq!( + r#"[{"insert":"123","attributes":{"bold":"true"}},{"retain":3}]"#, + serde_json::to_string(&a_prime).unwrap() + ); + assert_eq!( + r#"[{"retain":3,"attributes":{"bold":"true"}},{"insert":"456"}]"#, + serde_json::to_string(&b_prime).unwrap() + ); +} diff --git a/rust-lib/flowy-ot/tests/serde_test.rs b/rust-lib/flowy-ot/tests/serde_test.rs index 18238b0241..e93c226765 100644 --- a/rust-lib/flowy-ot/tests/serde_test.rs +++ b/rust-lib/flowy-ot/tests/serde_test.rs @@ -1,12 +1,12 @@ use flowy_ot::{ - attributes::{Attributes, AttributesBuilder}, + attributes::{Attributes, AttrsBuilder}, delta::Delta, operation::{OpBuilder, Operation, Retain}, }; #[test] fn operation_insert_serialize_test() { - let attributes = AttributesBuilder::new().bold().italic().build(); + let attributes = AttrsBuilder::new().bold().italic().build(); let operation = OpBuilder::insert("123") .attributes(Some(attributes)) .build(); @@ -38,7 +38,7 @@ fn operation_delete_serialize_test() { fn delta_serialize_test() { let mut delta = Delta::default(); - let attributes = AttributesBuilder::new().bold().italic().build(); + let attributes = AttrsBuilder::new().bold().italic().build(); let retain = OpBuilder::insert("123") .attributes(Some(attributes)) .build();