mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-11-13 09:06:30 +03:00
config flowy ot attributes and add attribute test
This commit is contained in:
parent
eb4728e346
commit
0b82336b6c
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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<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 }
|
||||
}
|
||||
|
||||
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<Operation>) -> Option<Attributes> {
|
||||
match operation {
|
||||
None => None,
|
||||
Some(operation) => operation.attributes(),
|
||||
Some(operation) => operation.get_attributes(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,22 +86,19 @@ pub fn compose_attributes(
|
||||
) -> Option<Attributes> {
|
||||
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
|
||||
|
@ -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::<String>(),
|
||||
&insert.chars().skip(*o_num as usize).collect::<String>(),
|
||||
)
|
||||
.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::<String>(), new_attrs);
|
||||
new_delta.insert(
|
||||
&chars.take(o_retain.num as usize).collect::<String>(),
|
||||
composed_attrs,
|
||||
);
|
||||
next_op1 = Some(OpBuilder::insert(&chars.collect::<String>()).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::<String>(),
|
||||
op.attributes(),
|
||||
op.get_attributes(),
|
||||
);
|
||||
},
|
||||
}
|
||||
@ -470,12 +479,12 @@ fn merge_retain_or_new_op(
|
||||
attributes: Option<Attributes>,
|
||||
) -> Option<Operation> {
|
||||
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())
|
||||
|
200
rust-lib/flowy-ot/src/interval.rs
Normal file
200
rust-lib/flowy-ot/src/interval.rs
Normal 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());
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub mod attributes;
|
||||
pub mod delta;
|
||||
pub mod errors;
|
||||
pub mod interval;
|
||||
pub mod operation;
|
||||
mod operation_serde;
|
||||
|
@ -29,7 +29,7 @@ impl Operation {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attributes(&self) -> Option<Attributes> {
|
||||
pub fn get_attributes(&self) -> Option<Attributes> {
|
||||
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<Attributes>,
|
||||
pub attributes: Option<Attributes>,
|
||||
}
|
||||
|
||||
impl std::convert::From<u64> for Retain {
|
||||
fn from(n: u64) -> Self {
|
||||
Retain {
|
||||
n,
|
||||
num: n,
|
||||
attributes: None,
|
||||
}
|
||||
}
|
||||
@ -111,11 +111,11 @@ impl std::convert::From<u64> 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)]
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<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
|
||||
}
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user