attribute: follow, empty, custom

This commit is contained in:
appflowy 2021-08-03 16:20:25 +08:00
parent d78c33e194
commit 3770c648c3
9 changed files with 459 additions and 300 deletions

View File

@ -1,58 +1,97 @@
use crate::operation::Operation;
use std::collections::{hash_map::RandomState, HashMap};
use std::collections::HashMap;
const PLAIN: &'static str = "";
fn is_plain(s: &str) -> bool { s == PLAIN }
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum Attributes {
#[serde(skip)]
Follow,
Custom(AttributesData),
#[serde(skip)]
Empty,
}
impl Attributes {
pub fn merge(&self, other: Option<Attributes>) -> Attributes {
let other = other.unwrap_or(Attributes::Empty);
match (self, &other) {
(Attributes::Custom(data), Attributes::Custom(o_data)) => {
let mut data = data.clone();
data.extend(o_data.clone());
Attributes::Custom(data)
},
_ => other,
}
}
// remove attribute if the value is PLAIN
pub fn remove_plain(&mut self) {
match self {
Attributes::Follow => {},
Attributes::Custom(data) => {
data.remove_plain();
},
Attributes::Empty => {},
}
}
}
impl std::default::Default for Attributes {
fn default() -> Self { Attributes::Empty }
}
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Attributes {
pub struct AttributesData {
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(flatten)]
inner: HashMap<String, String>,
}
impl Attributes {
impl AttributesData {
pub fn new() -> Self {
Attributes {
AttributesData {
inner: HashMap::new(),
}
}
pub fn remove_empty(&mut self) { self.inner.retain(|_, v| v.is_empty() == false); }
pub fn remove_plain(&mut self) { self.inner.retain(|_, v| !is_plain(v)); }
pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); }
pub fn extend(&mut self, other: AttributesData) { self.inner.extend(other.inner); }
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
pub fn is_plain(&self) -> bool { self.inner.values().filter(|v| !is_plain(v)).count() == 0 }
}
impl std::convert::From<HashMap<String, String>> for Attributes {
fn from(attributes: HashMap<String, String, RandomState>) -> Self {
Attributes { inner: attributes }
}
impl std::convert::From<HashMap<String, String>> for AttributesData {
fn from(attributes: HashMap<String, String>) -> Self { AttributesData { inner: attributes } }
}
impl std::ops::Deref for Attributes {
impl std::ops::Deref for AttributesData {
type Target = HashMap<String, String>;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl std::ops::DerefMut for Attributes {
impl std::ops::DerefMut for AttributesData {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
}
pub struct AttrsBuilder {
inner: Attributes,
inner: AttributesData,
}
impl AttrsBuilder {
pub fn new() -> Self {
Self {
inner: Attributes::default(),
inner: AttributesData::default(),
}
}
pub fn bold(mut self, bold: bool) -> Self {
let val = match bold {
true => "true",
false => "",
false => PLAIN,
};
self.inner.insert("bold".to_owned(), val.to_owned());
self
@ -61,7 +100,7 @@ impl AttrsBuilder {
pub fn italic(mut self, italic: bool) -> Self {
let val = match italic {
true => "true",
false => "",
false => PLAIN,
};
self.inner.insert("italic".to_owned(), val.to_owned());
self
@ -72,98 +111,108 @@ impl AttrsBuilder {
self
}
pub fn build(self) -> Attributes { self.inner }
pub fn build(self) -> Attributes { Attributes::Custom(self.inner) }
}
pub fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
match operation {
None => None,
Some(operation) => operation.get_attributes(),
Some(operation) => Some(operation.get_attributes()),
}
}
pub fn compose_attributes(
op1: &Option<Operation>,
op2: &Option<Operation>,
keep_empty: bool,
) -> Option<Attributes> {
let a = attributes_from(op1);
let b = attributes_from(op2);
if a.is_none() && b.is_none() {
return None;
pub fn compose_attributes(left: &Option<Operation>, right: &Option<Operation>) -> Attributes {
if left.is_none() && right.is_none() {
return Attributes::Empty;
}
let attr_l = attributes_from(left);
let attr_r = attributes_from(right);
log::trace!("compose_attributes: a: {:?}, b: {:?}", attr_l, attr_r);
let mut attrs_a = a.unwrap_or(Attributes::default());
let attrs_b = b.unwrap_or(Attributes::default());
let mut attr = match (&attr_l, &attr_r) {
(_, Some(Attributes::Custom(_))) => match &attr_l {
None => attr_r.unwrap(),
Some(_) => attr_l.unwrap().merge(attr_r.clone()),
},
(Some(Attributes::Custom(_)), _) => attr_l.unwrap().merge(attr_r),
_ => Attributes::Empty,
};
log::trace!(
"before compose_attributes: a: {:?}, b: {:?}",
attrs_a,
attrs_b
);
attrs_a.extend(attrs_b);
log::trace!("after compose_attributes: a: {:?}", attrs_a);
if !keep_empty {
attrs_a.remove_empty()
}
log::trace!("composed_attributes: a: {:?}", attr);
Some(attrs_a)
// remove the attribute if the value is PLAIN
attr.remove_plain();
attr
}
pub fn transform_attributes(
op1: &Option<Operation>,
op2: &Option<Operation>,
left: &Option<Operation>,
right: &Option<Operation>,
priority: bool,
) -> Option<Attributes> {
let a = attributes_from(op1);
let b = attributes_from(op2);
) -> Attributes {
let attr_l = attributes_from(left);
let attr_r = attributes_from(right);
if a.is_none() {
return b;
}
if attr_l.is_none() {
if attr_r.is_none() {
return Attributes::Empty;
}
if b.is_none() {
return None;
return match attr_r.as_ref().unwrap() {
Attributes::Follow => Attributes::Empty,
Attributes::Custom(_) => attr_r.unwrap(),
Attributes::Empty => Attributes::Empty,
};
}
if !priority {
return b;
return attr_r.unwrap();
}
let attrs_a = a.unwrap_or(Attributes::default());
let attrs_b = b.unwrap_or(Attributes::default());
let result = attrs_b
.iter()
.fold(Attributes::new(), |mut attributes, (k, v)| {
if attrs_a.contains_key(k) == false {
attributes.insert(k.clone(), v.clone());
}
attributes
});
Some(result)
match (attr_l.unwrap(), attr_r.unwrap()) {
(Attributes::Custom(attr_data_l), Attributes::Custom(attr_data_r)) => {
let result = transform_attribute_data(attr_data_l, attr_data_r);
Attributes::Custom(result)
},
_ => Attributes::Empty,
}
}
pub fn invert_attributes(attr: Option<Attributes>, base: Option<Attributes>) -> Attributes {
let attr = attr.unwrap_or(Attributes::new());
let base = base.unwrap_or(Attributes::new());
let base_inverted = base
fn transform_attribute_data(left: AttributesData, right: AttributesData) -> AttributesData {
let result = right
.iter()
.fold(Attributes::new(), |mut attributes, (k, v)| {
if base.get(k) != attr.get(k) && attr.contains_key(k) {
attributes.insert(k.clone(), v.clone());
.fold(AttributesData::new(), |mut new_attr_data, (k, v)| {
if !left.contains_key(k) {
new_attr_data.insert(k.clone(), v.clone());
}
attributes
new_attr_data
});
let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, _)| {
if base.get(k) != attr.get(k) && !base.contains_key(k) {
attributes.insert(k.clone(), "".to_owned());
}
attributes
});
return inverted;
result
}
// pub fn invert_attributes(
// attr: Option<AttributesData>,
// base: Option<AttributesData>,
// ) -> AttributesData {
// let attr = attr.unwrap_or(AttributesData::new());
// let base = base.unwrap_or(AttributesData::new());
//
// let base_inverted = base
// .iter()
// .fold(AttributesData::new(), |mut attributes, (k, v)| {
// if base.get(k) != attr.get(k) && attr.contains_key(k) {
// attributes.insert(k.clone(), v.clone());
// }
// attributes
// });
//
// let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, _)| {
// if base.get(k) != attr.get(k) && !base.contains_key(k) {
// attributes.insert(k.clone(), "".to_owned());
// }
// attributes
// });
//
// return inverted;
// }

View File

@ -1,6 +1,6 @@
use crate::{attributes::*, errors::OTError, operation::*};
use bytecount::num_chars;
use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator, str::FromStr};
use std::{cmp::Ordering, iter::FromIterator, str::FromStr};
#[derive(Clone, Debug, PartialEq)]
pub struct Delta {
@ -73,7 +73,7 @@ impl Delta {
}
}
pub fn insert(&mut self, s: &str, attrs: Option<Attributes>) {
pub fn insert(&mut self, s: &str, attrs: Attributes) {
if s.is_empty() {
return;
}
@ -102,7 +102,7 @@ impl Delta {
}
}
pub fn retain(&mut self, n: u64, attrs: Option<Attributes>) {
pub fn retain(&mut self, n: u64, attrs: Attributes) {
if n == 0 {
return;
}
@ -149,14 +149,17 @@ impl Delta {
next_op1 = ops1.next();
},
(_, Some(Operation::Insert(o_insert))) => {
new_delta.insert(&o_insert.s, attributes_from(&next_op2));
new_delta.insert(
&o_insert.s,
attributes_from(&next_op2).unwrap_or(Attributes::Empty),
);
next_op2 = ops2.next();
},
(None, _) | (_, None) => {
return Err(OTError);
},
(Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => {
let composed_attrs = compose_attributes(&next_op1, &next_op2, false);
let composed_attrs = compose_attributes(&next_op1, &next_op2);
log::debug!(
"[retain:{} - retain:{}]: {:?}",
retain.num,
@ -186,6 +189,7 @@ impl Delta {
Ordering::Less => {
next_op2 = Some(
OpBuilder::delete(*o_num - num_chars(insert.as_bytes()) as u64)
.attributes(insert.attributes.clone())
.build(),
);
next_op1 = ops1.next();
@ -206,7 +210,7 @@ impl Delta {
}
},
(Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => {
let composed_attrs = compose_attributes(&next_op1, &next_op2, false);
let composed_attrs = compose_attributes(&next_op1, &next_op2);
log::debug!(
"[insert:{} - retain:{}]: {:?}",
insert.s,
@ -234,7 +238,11 @@ impl Delta {
&chars.take(o_retain.num as usize).collect::<String>(),
composed_attrs,
);
next_op1 = Some(OpBuilder::insert(&chars.collect::<String>()).build());
next_op1 = Some(
OpBuilder::insert(&chars.collect::<String>())
.attributes(Attributes::Empty)
.build(),
);
next_op2 = ops2.next();
},
}
@ -297,7 +305,7 @@ impl Delta {
(_, Some(Operation::Insert(o_insert))) => {
let composed_attrs = transform_attributes(&next_op1, &next_op2, true);
a_prime.retain(o_insert.num_chars(), composed_attrs.clone());
b_prime.insert(&o_insert.s, composed_attrs.clone());
b_prime.insert(&o_insert.s, composed_attrs);
next_op2 = ops2.next();
},
(None, _) => {
@ -427,7 +435,7 @@ impl Delta {
for op in &self.ops {
match &op {
Operation::Retain(retain) => {
inverted.retain(retain.num, None);
inverted.retain(retain.num, Attributes::Follow);
for _ in 0..retain.num {
chars.next();
}
@ -475,41 +483,47 @@ impl Delta {
fn merge_insert_or_new_op(
insert: &mut Insert,
s: &str,
attributes: Option<Attributes>,
attributes: Attributes,
) -> Option<Operation> {
if attributes.is_none() {
insert.s += s;
return None;
}
if insert.attributes == attributes {
insert.s += s;
None
} else {
Some(OpBuilder::insert(s).attributes(attributes).build())
match &attributes {
Attributes::Follow => {
insert.s += s;
return None;
},
Attributes::Custom(_) | Attributes::Empty => {
if insert.attributes == attributes {
insert.s += s;
None
} else {
Some(OpBuilder::insert(s).attributes(attributes).build())
}
},
}
}
fn merge_retain_or_new_op(
retain: &mut Retain,
n: u64,
attributes: Option<Attributes>,
attributes: Attributes,
) -> Option<Operation> {
log::trace!(
log::debug!(
"merge_retain_or_new_op: {:?}, {:?}",
retain.attributes,
attributes
);
if attributes.is_none() {
retain.num += n;
return None;
}
if retain.attributes == attributes {
retain.num += n;
None
} else {
Some(OpBuilder::retain(n).attributes(attributes).build())
match &attributes {
Attributes::Follow => {
retain.num += n;
None
},
Attributes::Custom(_) | Attributes::Empty => {
if retain.attributes == attributes {
retain.num += n;
None
} else {
Some(OpBuilder::retain(n).attributes(attributes).build())
}
},
}
}

View File

@ -1,7 +1,7 @@
use std::{
cmp::{max, min},
fmt,
ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
ops::{Range, RangeInclusive, RangeTo, RangeToInclusive},
};
/// Representing a closed-open range;

View File

@ -1,8 +1,6 @@
use crate::attributes::Attributes;
use bytecount::num_chars;
use std::{
cmp::Ordering,
collections::{hash_map::RandomState, HashMap},
ops::{Deref, DerefMut},
str::Chars,
};
@ -29,17 +27,19 @@ impl Operation {
}
}
pub fn get_attributes(&self) -> Option<Attributes> {
pub fn get_attributes(&self) -> Attributes {
match self {
Operation::Delete(_) => None,
Operation::Delete(_) => Attributes::Empty,
Operation::Retain(retain) => retain.attributes.clone(),
Operation::Insert(insert) => insert.attributes.clone(),
}
}
pub fn set_attributes(&mut self, attributes: Option<Attributes>) {
pub fn set_attributes(&mut self, attributes: Attributes) {
match self {
Operation::Delete(_) => {},
Operation::Delete(_) => {
log::error!("Delete should not contains attributes");
},
Operation::Retain(retain) => {
retain.attributes = attributes;
},
@ -49,7 +49,13 @@ impl Operation {
}
}
pub fn is_plain(&self) -> bool { self.get_attributes().is_none() }
pub fn is_plain(&self) -> bool {
match self.get_attributes() {
Attributes::Follow => true,
Attributes::Custom(_) => false,
Attributes::Empty => true,
}
}
pub fn length(&self) -> u64 {
match self {
@ -63,11 +69,16 @@ impl Operation {
pub struct OpBuilder {
ty: Operation,
attrs: Option<Attributes>,
attrs: Attributes,
}
impl OpBuilder {
pub fn new(ty: Operation) -> OpBuilder { OpBuilder { ty, attrs: None } }
pub fn new(ty: Operation) -> OpBuilder {
OpBuilder {
ty,
attrs: Attributes::Empty,
}
}
pub fn retain(n: u64) -> OpBuilder { OpBuilder::new(Operation::Retain(n.into())) }
@ -75,15 +86,8 @@ impl OpBuilder {
pub fn insert(s: &str) -> OpBuilder { OpBuilder::new(Operation::Insert(s.into())) }
pub fn attributes(mut self, attrs: Option<Attributes>) -> OpBuilder {
match attrs {
None => self.attrs = attrs,
Some(attrs) => match attrs.is_empty() {
true => self.attrs = None,
false => self.attrs = Some(attrs),
},
}
pub fn attributes(mut self, attrs: Attributes) -> OpBuilder {
self.attrs = attrs;
self
}
@ -103,14 +107,14 @@ pub struct Retain {
#[serde(rename(serialize = "retain", deserialize = "retain"))]
pub num: u64,
#[serde(skip_serializing_if = "is_empty")]
pub attributes: Option<Attributes>,
pub attributes: Attributes,
}
impl std::convert::From<u64> for Retain {
fn from(n: u64) -> Self {
Retain {
num: n,
attributes: None,
attributes: Attributes::default(),
}
}
}
@ -131,7 +135,7 @@ pub struct Insert {
pub s: String,
#[serde(skip_serializing_if = "is_empty")]
pub attributes: Option<Attributes>,
pub attributes: Attributes,
}
impl Insert {
@ -146,23 +150,22 @@ impl std::convert::From<String> for Insert {
fn from(s: String) -> Self {
Insert {
s,
attributes: None,
attributes: Attributes::default(),
}
}
}
impl std::convert::From<&str> for Insert {
fn from(s: &str) -> Self {
Insert {
s: s.to_owned(),
attributes: None,
}
}
fn from(s: &str) -> Self { Insert::from(s.to_owned()) }
}
fn is_empty(attributes: &Option<Attributes>) -> bool {
fn is_empty(attributes: &Attributes) -> bool {
match attributes {
None => true,
Some(attributes) => attributes.is_empty(),
Attributes::Follow => true,
Attributes::Custom(data) => {
let is_empty = data.is_plain();
is_empty
},
Attributes::Empty => true,
}
}

View File

@ -8,7 +8,7 @@ use serde::{
Serialize,
Serializer,
};
use std::{collections::HashMap, fmt};
use std::fmt;
impl Serialize for Operation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@ -82,7 +82,7 @@ impl<'de> Deserialize<'de> for Operation {
match operation {
None => Err(de::Error::missing_field("operation")),
Some(mut operation) => {
operation.set_attributes(attributes);
operation.set_attributes(attributes.unwrap_or(Attributes::Empty));
Ok(operation)
},
}

View File

@ -1,18 +1,13 @@
pub mod helper;
use crate::{
helper::{MergeTestOp::*, *},
MergeTestOp::*,
};
use crate::helper::{MergeTestOp::*, *};
use flowy_ot::{
attributes::{Attributes, AttrsBuilder},
delta::Delta,
interval::Interval,
operation::{OpBuilder, Operation, Retain},
};
#[test]
fn delta_add_bold_attr1() {
fn delta_add_bold_and_invert_all() {
let ops = vec![
Insert(0, "123"),
Bold(0, Interval::new(0, 3), true),
@ -24,7 +19,7 @@ fn delta_add_bold_attr1() {
}
#[test]
fn delta_add_bold_attr2() {
fn delta_add_bold_and_invert_partial_suffix() {
let ops = vec![
Insert(0, "1234"),
Bold(0, Interval::new(0, 4), true),
@ -39,7 +34,24 @@ fn delta_add_bold_attr2() {
}
#[test]
fn delta_add_bold_attr3() {
fn delta_add_bold_and_invert_partial_suffix2() {
let ops = vec![
Insert(0, "1234"),
Bold(0, Interval::new(0, 4), true),
AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
Bold(0, Interval::new(2, 4), false),
AssertOpsJson(
0,
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#,
),
Bold(0, Interval::new(2, 4), true),
AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
];
MergeTest::new().run_script(ops);
}
#[test]
fn delta_add_bold_and_invert_partial_prefix() {
let ops = vec![
Insert(0, "1234"),
Bold(0, Interval::new(0, 4), true),
@ -53,6 +65,31 @@ fn delta_add_bold_attr3() {
MergeTest::new().run_script(ops);
}
#[test]
fn delta_add_bold_consecutive() {
let ops = vec![
Insert(0, "1234"),
Bold(0, Interval::new(0, 1), true),
AssertOpsJson(
0,
r#"[{"insert":"1","attributes":{"bold":"true"}},{"insert":"234"}]"#,
),
Bold(0, Interval::new(1, 2), true),
AssertOpsJson(
0,
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#,
),
];
MergeTest::new().run_script(ops);
}
#[test]
#[should_panic]
fn delta_add_bold_empty_str() {
let ops = vec![Bold(0, Interval::new(0, 4), true)];
MergeTest::new().run_script(ops);
}
#[test]
fn delta_add_bold_italic() {
let ops = vec![
@ -77,23 +114,6 @@ fn delta_add_bold_italic() {
MergeTest::new().run_script(ops);
}
#[test]
fn delta_add_bold_attr_and_invert() {
let ops = vec![
Insert(0, "1234"),
Bold(0, Interval::new(0, 4), true),
AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
Bold(0, Interval::new(2, 4), false),
AssertOpsJson(
0,
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#,
),
Bold(0, Interval::new(2, 4), true),
AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
];
MergeTest::new().run_script(ops);
}
#[test]
fn delta_merge_inserted_text_with_same_attribute() {
let ops = vec![
@ -134,3 +154,53 @@ fn delta_compose_attr_delta_with_attr_delta_test() {
MergeTest::new().run_script(ops);
}
#[test]
fn delta_delete_heading() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
Delete(0, Interval::new(0, 2)),
AssertOpsJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#),
];
MergeTest::new().run_script(ops);
}
#[test]
fn delta_delete_trailing() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
Delete(0, Interval::new(5, 6)),
AssertOpsJson(0, r#"[{"insert":"12345","attributes":{"bold":"true"}}]"#),
];
MergeTest::new().run_script(ops);
}
#[test]
fn delta_delete_middle() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
Delete(0, Interval::new(0, 2)),
AssertOpsJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#),
Delete(0, Interval::new(2, 4)),
AssertOpsJson(0, r#"[{"insert":"34","attributes":{"bold":"true"}}]"#),
];
MergeTest::new().run_script(ops);
}
#[test]
fn delta_delete_all() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
Delete(0, Interval::new(0, 6)),
AssertOpsJson(0, r#"[]"#),
];
MergeTest::new().run_script(ops);
}

View File

@ -1,5 +1,5 @@
use flowy_ot::{
attributes::{Attributes, AttrsBuilder},
attributes::{Attributes, AttributesData, AttrsBuilder},
delta::Delta,
interval::Interval,
operation::{OpBuilder, Operation},
@ -7,50 +7,6 @@ use flowy_ot::{
use rand::{prelude::*, Rng as WrappedRng};
use std::sync::Once;
pub struct Rng(StdRng);
impl Default for Rng {
fn default() -> Self { Rng(StdRng::from_rng(thread_rng()).unwrap()) }
}
impl Rng {
pub fn from_seed(seed: [u8; 32]) -> Self { Rng(StdRng::from_seed(seed)) }
pub fn gen_string(&mut self, len: usize) -> String {
(0..len).map(|_| self.0.gen::<char>()).collect()
}
pub fn gen_delta(&mut self, s: &str) -> Delta {
let mut delta = Delta::default();
loop {
let left = s.chars().count() - delta.base_len();
if left == 0 {
break;
}
let i = if left == 1 {
1
} else {
1 + self.0.gen_range(0, std::cmp::min(left - 1, 20))
};
match self.0.gen_range(0.0, 1.0) {
f if f < 0.2 => {
delta.insert(&self.gen_string(i), None);
},
f if f < 0.4 => {
delta.delete(i as u64);
},
_ => {
delta.retain(i as u64, None);
},
}
}
if self.0.gen_range(0.0, 1.0) < 0.3 {
delta.insert(&("1".to_owned() + &self.gen_string(10)), None);
}
delta
}
}
#[derive(Clone, Debug)]
pub enum MergeTestOp {
Insert(usize, &'static str),
@ -58,6 +14,7 @@ pub enum MergeTestOp {
InsertBold(usize, &'static str, Interval),
// delta_i, start, length, enable
Bold(usize, Interval, bool),
Delete(usize, Interval),
Italic(usize, Interval, bool),
Transform(usize, usize),
AssertStr(usize, &'static str),
@ -88,26 +45,31 @@ impl MergeTest {
match op {
MergeTestOp::Insert(delta_i, s) => {
let delta = &mut self.deltas[*delta_i];
delta.insert(s, None);
delta.insert(s, Attributes::Follow);
},
MergeTestOp::InsertBold(delta_i, s, interval) => {
MergeTestOp::Delete(delta_i, interval) => {
//
self.update_delta_with_delete(*delta_i, interval);
},
MergeTestOp::InsertBold(delta_i, s, _interval) => {
let attrs = AttrsBuilder::new().bold(true).build();
let delta = &mut self.deltas[*delta_i];
delta.insert(s, Some(attrs));
delta.insert(s, attrs);
},
MergeTestOp::Bold(delta_i, interval, enable) => {
let attrs = AttrsBuilder::new().bold(*enable).build();
self.replace_delta(*delta_i, attrs, interval);
self.update_delta_with_attribute(*delta_i, attrs, interval);
},
MergeTestOp::Italic(delta_i, interval, enable) => {
let attrs = AttrsBuilder::new().italic(*enable).build();
self.replace_delta(*delta_i, attrs, interval);
self.update_delta_with_attribute(*delta_i, attrs, interval);
},
MergeTestOp::Transform(delta_a_i, delta_b_i) => {
let delta_a = &self.deltas[*delta_a_i];
let delta_b = &self.deltas[*delta_b_i];
let (a_prime, b_prime) = delta_a.transform(delta_b).unwrap();
log::trace!("a:{:?},b:{:?}", a_prime, b_prime);
let new_delta_a = delta_a.compose(&b_prime).unwrap();
let new_delta_b = delta_b.compose(&a_prime).unwrap();
@ -120,54 +82,62 @@ impl MergeTest {
},
MergeTestOp::AssertOpsJson(delta_i, expected) => {
let expected_delta: Delta = serde_json::from_str(expected).unwrap();
let delta_i_json = serde_json::to_string(&self.deltas[*delta_i]).unwrap();
let delta: Delta = serde_json::from_str(&delta_i_json).unwrap();
if expected_delta != delta {
let expected_delta: Delta = serde_json::from_str(expected).unwrap();
let target_delta: Delta = serde_json::from_str(&delta_i_json).unwrap();
if expected_delta != target_delta {
log::error!("✅ {}", expected);
log::error!("❌ {}", delta_i_json);
}
assert_eq!(delta, expected_delta);
assert_eq!(target_delta, expected_delta);
},
}
}
pub fn run_script(&mut self, script: Vec<MergeTestOp>) {
for (i, op) in script.iter().enumerate() {
for (_i, op) in script.iter().enumerate() {
self.run_op(op);
}
}
pub fn replace_delta(
pub fn update_delta_with_attribute(
&mut self,
delta_index: usize,
attributes: Attributes,
interval: &Interval,
) {
let old_delta = &self.deltas[delta_index];
let new_delta = delta_with_attribute(old_delta, attributes, interval);
let retain = OpBuilder::retain(interval.size() as u64)
.attributes(attributes)
.build();
let new_delta = make_delta_with_op(old_delta, retain, interval);
self.deltas[delta_index] = new_delta;
}
pub fn update_delta_with_delete(&mut self, delta_index: usize, interval: &Interval) {
let old_delta = &self.deltas[delta_index];
let delete = OpBuilder::delete(interval.size() as u64).build();
let new_delta = make_delta_with_op(old_delta, delete, interval);
self.deltas[delta_index] = new_delta;
}
}
pub fn delta_with_attribute(delta: &Delta, attributes: Attributes, interval: &Interval) -> Delta {
let delta_interval = Interval::new(0, delta.target_len);
pub fn make_delta_with_op(delta: &Delta, op: Operation, interval: &Interval) -> Delta {
let mut new_delta = Delta::default();
let prefix = delta_interval.prefix(*interval);
let (prefix, suffix) = length_split_with_interval(delta.target_len, interval);
// prefix
if prefix.is_empty() == false && prefix != *interval {
let size = prefix.size();
let attrs = attributes_in_interval(delta, &prefix);
new_delta.retain(size as u64, attrs);
}
let size = interval.size();
log::debug!("Apply attribute {:?} to {}", attributes, interval);
new_delta.retain(size as u64, Some(attributes));
new_delta.add(op);
let suffix = delta_interval.suffix(*interval);
// suffix
if suffix.is_empty() == false {
let size = suffix.size();
let attrs = attributes_in_interval(delta, &suffix);
@ -177,31 +147,93 @@ pub fn delta_with_attribute(delta: &Delta, attributes: Attributes, interval: &In
delta.compose(&new_delta).unwrap()
}
pub fn length_split_with_interval(length: usize, interval: &Interval) -> (Interval, Interval) {
let original_interval = Interval::new(0, length);
let prefix = original_interval.prefix(*interval);
let suffix = original_interval.suffix(*interval);
(prefix, suffix)
}
pub fn debug_print_delta(delta: &Delta) {
log::debug!("😁 {}", serde_json::to_string(delta).unwrap());
}
pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Option<Attributes> {
let mut attributes = Attributes::new();
pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Attributes {
let mut attributes_data = AttributesData::new();
let mut offset = 0;
delta.ops.iter().for_each(|op| match op {
Operation::Delete(n) => {},
Operation::Delete(_n) => {},
Operation::Retain(retain) => {
if retain.attributes.is_some() {
if interval.contains(retain.num as usize) {
attributes.extend(retain.attributes.as_ref().unwrap().clone());
if interval.contains(retain.num as usize) {
match &retain.attributes {
Attributes::Follow => {},
Attributes::Custom(data) => {
attributes_data.extend(data.clone());
},
Attributes::Empty => {},
}
}
},
Operation::Insert(insert) => {
if insert.attributes.is_some() {
Operation::Insert(insert) => match &insert.attributes {
Attributes::Follow => {},
Attributes::Custom(data) => {
if interval.start >= offset && insert.num_chars() > (interval.end as u64 - 1) {
attributes.extend(insert.attributes.as_ref().unwrap().clone());
attributes_data.extend(data.clone());
}
offset += insert.num_chars() as usize;
}
},
Attributes::Empty => {},
},
});
Some(attributes)
if attributes_data.is_plain() {
Attributes::Empty
} else {
Attributes::Custom(attributes_data)
}
}
pub struct Rng(StdRng);
impl Default for Rng {
fn default() -> Self { Rng(StdRng::from_rng(thread_rng()).unwrap()) }
}
impl Rng {
pub fn from_seed(seed: [u8; 32]) -> Self { Rng(StdRng::from_seed(seed)) }
pub fn gen_string(&mut self, len: usize) -> String {
(0..len).map(|_| self.0.gen::<char>()).collect()
}
pub fn gen_delta(&mut self, s: &str) -> Delta {
let mut delta = Delta::default();
loop {
let left = s.chars().count() - delta.base_len();
if left == 0 {
break;
}
let i = if left == 1 {
1
} else {
1 + self.0.gen_range(0, std::cmp::min(left - 1, 20))
};
match self.0.gen_range(0.0, 1.0) {
f if f < 0.2 => {
delta.insert(&self.gen_string(i), Attributes::Empty);
},
f if f < 0.4 => {
delta.delete(i as u64);
},
_ => {
delta.retain(i as u64, Attributes::Empty);
},
}
}
if self.0.gen_range(0.0, 1.0) < 0.3 {
delta.insert(&("1".to_owned() + &self.gen_string(10)), Attributes::Empty);
}
delta
}
}

View File

@ -2,26 +2,21 @@ pub mod helper;
use crate::helper::MergeTestOp::*;
use bytecount::num_chars;
use flowy_ot::{
attributes::*,
delta::Delta,
operation::{OpBuilder, Operation},
};
use flowy_ot::{attributes::*, delta::Delta, operation::OpBuilder};
use helper::*;
use std::str::FromStr;
#[test]
fn lengths() {
let mut delta = Delta::default();
assert_eq!(delta.base_len, 0);
assert_eq!(delta.target_len, 0);
delta.retain(5, None);
delta.retain(5, Attributes::Empty);
assert_eq!(delta.base_len, 5);
assert_eq!(delta.target_len, 5);
delta.insert("abc", None);
delta.insert("abc", Attributes::Empty);
assert_eq!(delta.base_len, 5);
assert_eq!(delta.target_len, 8);
delta.retain(2, None);
delta.retain(2, Attributes::Empty);
assert_eq!(delta.base_len, 7);
assert_eq!(delta.target_len, 10);
delta.delete(2);
@ -31,10 +26,10 @@ fn lengths() {
#[test]
fn sequence() {
let mut delta = Delta::default();
delta.retain(5, None);
delta.retain(0, None);
delta.insert("appflowy", None);
delta.insert("", None);
delta.retain(5, Attributes::Empty);
delta.retain(0, Attributes::Empty);
delta.insert("appflowy", Attributes::Empty);
delta.insert("", Attributes::Empty);
delta.delete(3);
delta.delete(0);
assert_eq!(delta.ops.len(), 3);
@ -55,11 +50,11 @@ fn apply_1000() {
fn apply() {
let s = "hello world,".to_owned();
let mut delta_a = Delta::default();
delta_a.insert(&s, None);
delta_a.insert(&s, Attributes::Empty);
let mut delta_b = Delta::default();
delta_b.retain(s.len() as u64, None);
delta_b.insert("appflowy", None);
delta_b.retain(s.len() as u64, Attributes::Empty);
delta_b.insert("appflowy", Attributes::Empty);
let after_a = delta_a.apply("").unwrap();
let after_b = delta_b.apply(&after_a).unwrap();
@ -69,15 +64,15 @@ fn apply() {
#[test]
fn base_len_test() {
let mut delta_a = Delta::default();
delta_a.insert("a", None);
delta_a.insert("b", None);
delta_a.insert("c", None);
delta_a.insert("a", Attributes::Empty);
delta_a.insert("b", Attributes::Empty);
delta_a.insert("c", Attributes::Empty);
let s = "hello world,".to_owned();
delta_a.delete(s.len() as u64);
let after_a = delta_a.apply(&s).unwrap();
delta_a.insert("d", None);
delta_a.insert("d", Attributes::Empty);
assert_eq!("abc", &after_a);
}
@ -97,8 +92,8 @@ fn invert() {
#[test]
fn empty_ops() {
let mut delta = Delta::default();
delta.retain(0, None);
delta.insert("", None);
delta.retain(0, Attributes::Empty);
delta.insert("", Attributes::Empty);
delta.delete(0);
assert_eq!(delta.ops.len(), 0);
}
@ -106,33 +101,33 @@ fn empty_ops() {
fn eq() {
let mut delta_a = Delta::default();
delta_a.delete(1);
delta_a.insert("lo", None);
delta_a.retain(2, None);
delta_a.retain(3, None);
delta_a.insert("lo", Attributes::Empty);
delta_a.retain(2, Attributes::Empty);
delta_a.retain(3, Attributes::Empty);
let mut delta_b = Delta::default();
delta_b.delete(1);
delta_b.insert("l", None);
delta_b.insert("o", None);
delta_b.retain(5, None);
delta_b.insert("l", Attributes::Empty);
delta_b.insert("o", Attributes::Empty);
delta_b.retain(5, Attributes::Empty);
assert_eq!(delta_a, delta_b);
delta_a.delete(1);
delta_b.retain(1, None);
delta_b.retain(1, Attributes::Empty);
assert_ne!(delta_a, delta_b);
}
#[test]
fn ops_merging() {
let mut delta = Delta::default();
assert_eq!(delta.ops.len(), 0);
delta.retain(2, None);
delta.retain(2, Attributes::Empty);
assert_eq!(delta.ops.len(), 1);
assert_eq!(delta.ops.last(), Some(&OpBuilder::retain(2).build()));
delta.retain(3, None);
delta.retain(3, Attributes::Empty);
assert_eq!(delta.ops.len(), 1);
assert_eq!(delta.ops.last(), Some(&OpBuilder::retain(5).build()));
delta.insert("abc", None);
delta.insert("abc", Attributes::Empty);
assert_eq!(delta.ops.len(), 2);
assert_eq!(delta.ops.last(), Some(&OpBuilder::insert("abc").build()));
delta.insert("xyz", None);
delta.insert("xyz", Attributes::Empty);
assert_eq!(delta.ops.len(), 2);
assert_eq!(delta.ops.last(), Some(&OpBuilder::insert("abcxyz").build()));
delta.delete(1);
@ -146,11 +141,11 @@ fn ops_merging() {
fn is_noop() {
let mut delta = Delta::default();
assert!(delta.is_noop());
delta.retain(5, None);
delta.retain(5, Attributes::Empty);
assert!(delta.is_noop());
delta.retain(3, None);
delta.retain(3, Attributes::Empty);
assert!(delta.is_noop());
delta.insert("lorem", None);
delta.insert("lorem", Attributes::Empty);
assert!(!delta.is_noop());
}
#[test]
@ -184,9 +179,9 @@ fn transform() {
let ba_prime = b.compose(&a_prime).unwrap();
assert_eq!(ab_prime, ba_prime);
// let after_ab_prime = ab_prime.apply(&s).unwrap();
// let after_ba_prime = ba_prime.apply(&s).unwrap();
// assert_eq!(after_ab_prime, after_ba_prime);
let after_ab_prime = ab_prime.apply(&s).unwrap();
let after_ba_prime = ba_prime.apply(&s).unwrap();
assert_eq!(after_ab_prime, after_ba_prime);
}
}
@ -208,13 +203,13 @@ fn transform2() {
fn delta_transform_test() {
let mut a = Delta::default();
let mut a_s = String::new();
a.insert("123", Some(AttrsBuilder::new().bold(true).build()));
a.insert("123", AttrsBuilder::new().bold(true).build());
a_s = a.apply(&a_s).unwrap();
assert_eq!(&a_s, "123");
let mut b = Delta::default();
let mut b_s = String::new();
b.insert("456", None);
b.insert("456", Attributes::Empty);
b_s = b.apply(&b_s).unwrap();
assert_eq!(&b_s, "456");

View File

@ -1,5 +1,5 @@
use flowy_ot::{
attributes::{Attributes, AttrsBuilder},
attributes::AttrsBuilder,
delta::Delta,
operation::{OpBuilder, Operation, Retain},
};
@ -7,9 +7,7 @@ use flowy_ot::{
#[test]
fn operation_insert_serialize_test() {
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
let operation = OpBuilder::insert("123")
.attributes(Some(attributes))
.build();
let operation = OpBuilder::insert("123").attributes(attributes).build();
let json = serde_json::to_string(&operation).unwrap();
eprintln!("{}", json);
@ -39,9 +37,7 @@ fn delta_serialize_test() {
let mut delta = Delta::default();
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
let retain = OpBuilder::insert("123")
.attributes(Some(attributes))
.build();
let retain = OpBuilder::insert("123").attributes(attributes).build();
delta.add(retain);
delta.add(Operation::Retain(5.into()));