mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
attribute: follow, empty, custom
This commit is contained in:
parent
d78c33e194
commit
3770c648c3
@ -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;
|
||||
// }
|
||||
|
@ -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())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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()));
|
||||
|
Loading…
Reference in New Issue
Block a user