config flowy ot attributes and add attribute test

This commit is contained in:
appflowy 2021-08-02 18:35:25 +08:00
parent eb4728e346
commit 0b82336b6c
12 changed files with 620 additions and 124 deletions

View File

@ -7,7 +7,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.6.1" version: "2.7.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -28,7 +28,7 @@ packages:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -141,7 +141,7 @@ packages:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.7.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -302,7 +302,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.0" version: "0.4.1"
tuple: tuple:
dependency: transitive dependency: transitive
description: description:

View File

@ -7,7 +7,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.6.1" version: "2.7.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -28,7 +28,7 @@ packages:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -113,7 +113,7 @@ packages:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.7.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -211,7 +211,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.0" version: "0.4.1"
tuple: tuple:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -9,7 +9,10 @@ edition = "2018"
bytecount = "0.6.0" bytecount = "0.6.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = {version = "1.0"} serde_json = {version = "1.0"}
log = "0.4"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"
rand = "0.7.3" rand = "0.7.3"
env_logger = "0.8.2"

View File

@ -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 extend(&mut self, other: Attributes) { self.inner.extend(other.inner); }
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
} }
impl std::convert::From<HashMap<String, String>> for Attributes { impl std::convert::From<HashMap<String, String>> for Attributes {
@ -36,11 +38,11 @@ impl std::ops::DerefMut for Attributes {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
} }
pub struct AttributesBuilder { pub struct AttrsBuilder {
inner: Attributes, inner: Attributes,
} }
impl AttributesBuilder { impl AttrsBuilder {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
inner: Attributes::default(), inner: Attributes::default(),
@ -52,6 +54,11 @@ impl AttributesBuilder {
self self
} }
pub fn un_bold(mut self) -> Self {
self.inner.insert("bold".to_owned(), "".to_owned());
self
}
pub fn italic(mut self) -> Self { pub fn italic(mut self) -> Self {
self.inner.insert("italic".to_owned(), "true".to_owned()); self.inner.insert("italic".to_owned(), "true".to_owned());
self self
@ -68,7 +75,7 @@ impl AttributesBuilder {
pub fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> { pub fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
match operation { match operation {
None => None, None => None,
Some(operation) => operation.attributes(), Some(operation) => operation.get_attributes(),
} }
} }
@ -79,22 +86,19 @@ pub fn compose_attributes(
) -> Option<Attributes> { ) -> Option<Attributes> {
let a = attributes_from(op1); let a = attributes_from(op1);
let b = attributes_from(op2); 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 mut attrs_a = a.unwrap_or(Attributes::default());
let attrs_b = b.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 { if !keep_empty {
attrs_a.remove_empty_value() attrs_a.remove_empty_value()
} }
// log::debug!("after compose_attributes: a: {:?}", attrs_a);
return if attrs_a.is_empty() { return if attrs_a.is_empty() {
None None

View File

@ -57,7 +57,7 @@ impl Delta {
match op { match op {
Operation::Delete(i) => self.delete(i), Operation::Delete(i) => self.delete(i),
Operation::Insert(i) => self.insert(&i.s, i.attributes), 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()); self.target_len += num_chars(s.as_bytes());
let new_last = match self.ops.as_mut_slice() { 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(_)] => { [.., op_last @ Operation::Delete(_)] => {
let new_last = op_last.clone(); let new_last = op_last.clone();
@ -109,8 +109,8 @@ impl Delta {
self.base_len += n as usize; self.base_len += n as usize;
self.target_len += n as usize; self.target_len += n as usize;
if let Some(Operation::Retain(i_last)) = self.ops.last_mut() { if let Some(Operation::Retain(retain)) = self.ops.last_mut() {
match merge_retain_or_new_op(i_last, n, attrs) { match merge_retain_or_new_op(retain, n, attrs) {
None => {}, None => {},
Some(new_op) => self.ops.push(new_op), Some(new_op) => self.ops.push(new_op),
} }
@ -148,38 +148,39 @@ impl Delta {
new_delta.delete(*i); new_delta.delete(*i);
next_op1 = ops1.next(); next_op1 = ops1.next();
}, },
(_, Some(Operation::Insert(insert))) => { (_, Some(Operation::Insert(o_insert))) => {
new_delta.insert(&insert.s, attributes_from(&next_op2)); new_delta.insert(&o_insert.s, attributes_from(&next_op2));
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
(None, _) | (_, None) => { (None, _) | (_, None) => {
return Err(OTError); return Err(OTError);
}, },
(Some(Operation::Retain(i)), Some(Operation::Retain(j))) => { (Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => {
let new_attrs = compose_attributes(&next_op1, &next_op2, true); let composed_attrs = compose_attributes(&next_op1, &next_op2, true);
match i.cmp(&j) { match retain.cmp(&o_retain) {
Ordering::Less => { Ordering::Less => {
new_delta.retain(i.n, new_attrs); new_delta.retain(retain.num, composed_attrs);
next_op2 = Some(OpBuilder::retain(j.n - i.n).build()); next_op2 = Some(OpBuilder::retain(o_retain.num - retain.num).build());
next_op1 = ops1.next(); next_op1 = ops1.next();
}, },
std::cmp::Ordering::Equal => { std::cmp::Ordering::Equal => {
new_delta.retain(i.n, new_attrs); new_delta.retain(retain.num, composed_attrs);
next_op1 = ops1.next(); next_op1 = ops1.next();
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
std::cmp::Ordering::Greater => { std::cmp::Ordering::Greater => {
new_delta.retain(j.n, new_attrs); new_delta.retain(o_retain.num, composed_attrs);
next_op1 = Some(OpBuilder::retain(i.n - j.n).build()); next_op1 = Some(OpBuilder::retain(retain.num - o_retain.num).build());
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
} }
}, },
(Some(Operation::Insert(insert)), Some(Operation::Delete(j))) => { (Some(Operation::Insert(insert)), Some(Operation::Delete(o_num))) => {
match (num_chars(insert.as_bytes()) as u64).cmp(j) { match (num_chars(insert.as_bytes()) as u64).cmp(o_num) {
Ordering::Less => { Ordering::Less => {
next_op2 = Some( 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(); next_op1 = ops1.next();
}, },
@ -190,7 +191,7 @@ impl Delta {
Ordering::Greater => { Ordering::Greater => {
next_op1 = Some( next_op1 = Some(
OpBuilder::insert( OpBuilder::insert(
&insert.chars().skip(*j as usize).collect::<String>(), &insert.chars().skip(*o_num as usize).collect::<String>(),
) )
.build(), .build(),
); );
@ -198,44 +199,52 @@ impl Delta {
}, },
} }
}, },
(Some(Operation::Insert(insert)), Some(Operation::Retain(j))) => { (Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => {
let new_attrs = compose_attributes(&next_op1, &next_op2, false); let composed_attrs = compose_attributes(&next_op1, &next_op2, false);
match (insert.num_chars()).cmp(j) { match (insert.num_chars()).cmp(o_retain) {
Ordering::Less => { Ordering::Less => {
new_delta.insert(&insert.s, new_attrs); new_delta.insert(&insert.s, composed_attrs.clone());
next_op2 = Some(OpBuilder::retain(j.n - insert.num_chars()).build()); next_op2 = Some(
OpBuilder::retain(o_retain.num - insert.num_chars())
.attributes(composed_attrs.clone())
.build(),
);
next_op1 = ops1.next(); next_op1 = ops1.next();
}, },
Ordering::Equal => { Ordering::Equal => {
new_delta.insert(&insert.s, new_attrs); new_delta.insert(&insert.s, composed_attrs);
next_op1 = ops1.next(); next_op1 = ops1.next();
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
Ordering::Greater => { Ordering::Greater => {
let chars = &mut insert.chars(); let chars = &mut insert.chars();
new_delta new_delta.insert(
.insert(&chars.take(j.n as usize).collect::<String>(), new_attrs); &chars.take(o_retain.num as usize).collect::<String>(),
composed_attrs,
);
next_op1 = Some(OpBuilder::insert(&chars.collect::<String>()).build()); next_op1 = Some(OpBuilder::insert(&chars.collect::<String>()).build());
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
} }
}, },
(Some(Operation::Retain(i)), Some(Operation::Delete(j))) => match i.cmp(&j) { (Some(Operation::Retain(retain)), Some(Operation::Delete(o_num))) => {
match retain.cmp(&o_num) {
Ordering::Less => { Ordering::Less => {
new_delta.delete(i.n); new_delta.delete(retain.num);
next_op2 = Some(OpBuilder::delete(*j - i.n).build()); next_op2 = Some(OpBuilder::delete(*o_num - retain.num).build());
next_op1 = ops1.next(); next_op1 = ops1.next();
}, },
Ordering::Equal => { Ordering::Equal => {
new_delta.delete(*j); new_delta.delete(*o_num);
next_op2 = ops2.next(); next_op2 = ops2.next();
next_op1 = ops1.next(); next_op1 = ops1.next();
}, },
Ordering::Greater => { Ordering::Greater => {
new_delta.delete(*j); new_delta.delete(*o_num);
next_op1 = Some(OpBuilder::retain(i.n - *j).build()); next_op1 = Some(OpBuilder::retain(retain.num - *o_num).build());
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
}
}, },
}; };
} }
@ -268,15 +277,15 @@ impl Delta {
match (&next_op1, &next_op2) { match (&next_op1, &next_op2) {
(None, None) => break, (None, None) => break,
(Some(Operation::Insert(insert)), _) => { (Some(Operation::Insert(insert)), _) => {
let new_attrs = compose_attributes(&next_op1, &next_op2, true); // let composed_attrs = transform_attributes(&next_op1, &next_op2, true);
a_prime.insert(&insert.s, new_attrs.clone()); a_prime.insert(&insert.s, insert.attributes.clone());
b_prime.retain(insert.num_chars(), new_attrs.clone()); b_prime.retain(insert.num_chars(), insert.attributes.clone());
next_op1 = ops1.next(); next_op1 = ops1.next();
}, },
(_, Some(Operation::Insert(insert))) => { (_, Some(Operation::Insert(o_insert))) => {
let new_attrs = compose_attributes(&next_op1, &next_op2, true); let composed_attrs = transform_attributes(&next_op1, &next_op2, true);
a_prime.retain(insert.num_chars(), new_attrs.clone()); a_prime.retain(o_insert.num_chars(), composed_attrs.clone());
b_prime.insert(&insert.s, new_attrs.clone()); b_prime.insert(&o_insert.s, composed_attrs.clone());
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
(None, _) => { (None, _) => {
@ -285,25 +294,25 @@ impl Delta {
(_, None) => { (_, None) => {
return Err(OTError); return Err(OTError);
}, },
(Some(Operation::Retain(i)), Some(Operation::Retain(j))) => { (Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => {
let new_attrs = compose_attributes(&next_op1, &next_op2, true); let composed_attrs = transform_attributes(&next_op1, &next_op2, true);
match i.cmp(&j) { match retain.cmp(&o_retain) {
Ordering::Less => { Ordering::Less => {
a_prime.retain(i.n, new_attrs.clone()); a_prime.retain(retain.num, composed_attrs.clone());
b_prime.retain(i.n, new_attrs.clone()); b_prime.retain(retain.num, composed_attrs.clone());
next_op2 = Some(OpBuilder::retain(j.n - i.n).build()); next_op2 = Some(OpBuilder::retain(o_retain.num - retain.num).build());
next_op1 = ops1.next(); next_op1 = ops1.next();
}, },
Ordering::Equal => { Ordering::Equal => {
a_prime.retain(i.n, new_attrs.clone()); a_prime.retain(retain.num, composed_attrs.clone());
b_prime.retain(i.n, new_attrs.clone()); b_prime.retain(retain.num, composed_attrs.clone());
next_op1 = ops1.next(); next_op1 = ops1.next();
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
Ordering::Greater => { Ordering::Greater => {
a_prime.retain(j.n, new_attrs.clone()); a_prime.retain(o_retain.num, composed_attrs.clone());
b_prime.retain(j.n, new_attrs.clone()); b_prime.retain(o_retain.num, composed_attrs.clone());
next_op1 = Some(OpBuilder::retain(i.n - j.n).build()); next_op1 = Some(OpBuilder::retain(retain.num - o_retain.num).build());
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
}; };
@ -322,11 +331,11 @@ impl Delta {
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
}, },
(Some(Operation::Delete(i)), Some(Operation::Retain(j))) => { (Some(Operation::Delete(i)), Some(Operation::Retain(o_retain))) => {
match i.cmp(&j) { match i.cmp(&o_retain) {
Ordering::Less => { Ordering::Less => {
a_prime.delete(*i); 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(); next_op1 = ops1.next();
}, },
Ordering::Equal => { Ordering::Equal => {
@ -335,27 +344,27 @@ impl Delta {
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
Ordering::Greater => { Ordering::Greater => {
a_prime.delete(j.n); a_prime.delete(o_retain.num);
next_op1 = Some(OpBuilder::delete(*i - j.n).build()); next_op1 = Some(OpBuilder::delete(*i - o_retain.num).build());
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
}; };
}, },
(Some(Operation::Retain(i)), Some(Operation::Delete(j))) => { (Some(Operation::Retain(retain)), Some(Operation::Delete(j))) => {
match i.cmp(&j) { match retain.cmp(&j) {
Ordering::Less => { Ordering::Less => {
b_prime.delete(i.n); b_prime.delete(retain.num);
next_op2 = Some(OpBuilder::delete(*j - i.n).build()); next_op2 = Some(OpBuilder::delete(*j - retain.num).build());
next_op1 = ops1.next(); next_op1 = ops1.next();
}, },
Ordering::Equal => { Ordering::Equal => {
b_prime.delete(i.n); b_prime.delete(retain.num);
next_op1 = ops1.next(); next_op1 = ops1.next();
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
Ordering::Greater => { Ordering::Greater => {
b_prime.delete(*j); 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(); next_op2 = ops2.next();
}, },
}; };
@ -381,7 +390,7 @@ impl Delta {
for op in &self.ops { for op in &self.ops {
match &op { match &op {
Operation::Retain(retain) => { 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); new_s.push(c);
} }
}, },
@ -406,8 +415,8 @@ impl Delta {
for op in &self.ops { for op in &self.ops {
match &op { match &op {
Operation::Retain(retain) => { Operation::Retain(retain) => {
inverted.retain(retain.n, None); inverted.retain(retain.num, None);
for _ in 0..retain.n { for _ in 0..retain.num {
chars.next(); chars.next();
} }
}, },
@ -417,7 +426,7 @@ impl Delta {
Operation::Delete(delete) => { Operation::Delete(delete) => {
inverted.insert( inverted.insert(
&chars.take(*delete as usize).collect::<String>(), &chars.take(*delete as usize).collect::<String>(),
op.attributes(), op.get_attributes(),
); );
}, },
} }
@ -470,12 +479,12 @@ fn merge_retain_or_new_op(
attributes: Option<Attributes>, attributes: Option<Attributes>,
) -> Option<Operation> { ) -> Option<Operation> {
if attributes.is_none() { if attributes.is_none() {
retain.n += n; retain.num += n;
return None; return None;
} }
// log::debug!("merge retain: {:?}, {:?}", retain.attributes, attributes);
if retain.attributes == attributes { if retain.attributes == attributes {
retain.n += n; retain.num += n;
None None
} else { } else {
Some(OpBuilder::retain(n).attributes(attributes).build()) Some(OpBuilder::retain(n).attributes(attributes).build())

View File

@ -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<Range<usize>> for Interval {
fn from(src: Range<usize>) -> Interval {
let Range { start, end } = src;
Interval { start, end }
}
}
impl From<RangeTo<usize>> for Interval {
fn from(src: RangeTo<usize>) -> Interval { Interval::new(0, src.end) }
}
impl From<RangeInclusive<usize>> for Interval {
fn from(src: RangeInclusive<usize>) -> Interval {
Interval::new(*src.start(), src.end().saturating_add(1))
}
}
impl From<RangeToInclusive<usize>> for Interval {
fn from(src: RangeToInclusive<usize>) -> 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());
}
}

View File

@ -1,5 +1,6 @@
pub mod attributes; pub mod attributes;
pub mod delta; pub mod delta;
pub mod errors; pub mod errors;
pub mod interval;
pub mod operation; pub mod operation;
mod operation_serde; mod operation_serde;

View File

@ -29,7 +29,7 @@ impl Operation {
} }
} }
pub fn attributes(&self) -> Option<Attributes> { pub fn get_attributes(&self) -> Option<Attributes> {
match self { match self {
Operation::Delete(_) => None, Operation::Delete(_) => None,
Operation::Retain(retain) => retain.attributes.clone(), 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 { pub fn length(&self) -> u64 {
match self { match self {
Operation::Delete(n) => *n, Operation::Delete(n) => *n,
Operation::Retain(r) => r.n, Operation::Retain(r) => r.num,
Operation::Insert(i) => i.num_chars(), Operation::Insert(i) => i.num_chars(),
} }
} }
@ -94,15 +94,15 @@ impl OpBuilder {
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Retain { pub struct Retain {
#[serde(rename(serialize = "retain", deserialize = "retain"))] #[serde(rename(serialize = "retain", deserialize = "retain"))]
pub n: u64, pub num: u64,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub(crate) attributes: Option<Attributes>, pub attributes: Option<Attributes>,
} }
impl std::convert::From<u64> for Retain { impl std::convert::From<u64> for Retain {
fn from(n: u64) -> Self { fn from(n: u64) -> Self {
Retain { Retain {
n, num: n,
attributes: None, attributes: None,
} }
} }
@ -111,11 +111,11 @@ impl std::convert::From<u64> for Retain {
impl Deref for Retain { impl Deref for Retain {
type Target = u64; type Target = u64;
fn deref(&self) -> &Self::Target { &self.n } fn deref(&self) -> &Self::Target { &self.num }
} }
impl DerefMut for Retain { 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)] #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]

View File

@ -1,16 +1,112 @@
pub mod helper;
use crate::{
helper::{MergeTestOp::*, *},
MergeTestOp::*,
};
use flowy_ot::{ use flowy_ot::{
attributes::{Attributes, AttributesBuilder}, attributes::{Attributes, AttrsBuilder},
delta::Delta, delta::Delta,
interval::Interval,
operation::{OpBuilder, Operation, Retain}, operation::{OpBuilder, Operation, Retain},
}; };
#[test] #[test]
fn attribute_insert_merge_test() { fn delta_add_bold_attr1() {
let mut delta = Delta::default(); let ops = vec![
delta.insert("123", Some(AttributesBuilder::new().bold().build())); Insert(0, "123"),
delta.insert("456", Some(AttributesBuilder::new().bold().build())); Bold(0, Interval::new(0, 3), true),
assert_eq!( AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#, Bold(0, Interval::new(0, 3), false),
serde_json::to_string(&delta).unwrap() 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);
} }

View File

@ -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 rand::{prelude::*, Rng as WrappedRng};
use std::sync::Once;
pub struct Rng(StdRng); pub struct Rng(StdRng);
@ -44,3 +50,140 @@ impl Rng {
delta 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<Delta>,
}
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<MergeTestOp>) {
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
}

View File

@ -1,5 +1,6 @@
pub mod helper; pub mod helper;
use crate::helper::MergeTestOp::*;
use bytecount::num_chars; use bytecount::num_chars;
use flowy_ot::{ use flowy_ot::{
attributes::*, attributes::*,
@ -7,6 +8,7 @@ use flowy_ot::{
operation::{OpBuilder, Operation}, operation::{OpBuilder, Operation},
}; };
use helper::*; use helper::*;
use std::str::FromStr;
#[test] #[test]
fn lengths() { fn lengths() {
@ -91,6 +93,7 @@ fn invert() {
assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s); assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);
} }
} }
#[test] #[test]
fn empty_ops() { fn empty_ops() {
let mut delta = Delta::default(); let mut delta = Delta::default();
@ -185,3 +188,40 @@ fn transform() {
assert_eq!(after_ab_prime, after_ba_prime); 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()
);
}

View File

@ -1,12 +1,12 @@
use flowy_ot::{ use flowy_ot::{
attributes::{Attributes, AttributesBuilder}, attributes::{Attributes, AttrsBuilder},
delta::Delta, delta::Delta,
operation::{OpBuilder, Operation, Retain}, operation::{OpBuilder, Operation, Retain},
}; };
#[test] #[test]
fn operation_insert_serialize_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") let operation = OpBuilder::insert("123")
.attributes(Some(attributes)) .attributes(Some(attributes))
.build(); .build();
@ -38,7 +38,7 @@ fn operation_delete_serialize_test() {
fn delta_serialize_test() { fn delta_serialize_test() {
let mut delta = Delta::default(); let mut delta = Delta::default();
let attributes = AttributesBuilder::new().bold().italic().build(); let attributes = AttrsBuilder::new().bold().italic().build();
let retain = OpBuilder::insert("123") let retain = OpBuilder::insert("123")
.attributes(Some(attributes)) .attributes(Some(attributes))
.build(); .build();