mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00: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
|
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:
|
||||||
|
@ -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:
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
||||||
|
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 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;
|
||||||
|
@ -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)]
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user