add attributes

This commit is contained in:
appflowy 2021-07-31 23:22:17 +08:00
parent b449707021
commit a3ed1b2874
7 changed files with 226 additions and 118 deletions

View File

@ -1,6 +1,6 @@
use flowy_test::builder::SingleUserTestBuilder;
use flowy_editor::{entities::doc::*, event::EditorEvent::*};
use flowy_document::{entities::doc::*, event::EditorEvent::*};
use flowy_infra::uuid;
pub fn create_doc(name: &str, desc: &str, text: &str) -> DocInfo {

View File

@ -33,39 +33,87 @@ impl std::ops::DerefMut for Attributes {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
}
pub fn compose_attributes(
mut a: Attributes,
b: Attributes,
keep_empty: bool,
) -> Option<Attributes> {
a.extend(b);
let mut result = a;
if !keep_empty {
result.remove_empty_value()
pub struct AttributesBuilder {
inner: Attributes,
}
return if result.is_empty() {
impl AttributesBuilder {
pub fn new() -> Self {
Self {
inner: Attributes::default(),
}
}
pub fn bold(mut self) -> Self {
self.inner.insert("bold".to_owned(), "true".to_owned());
self
}
pub fn italic(mut self) -> Self {
self.inner.insert("italic".to_owned(), "true".to_owned());
self
}
pub fn underline(mut self) -> Self {
self.inner.insert("underline".to_owned(), "true".to_owned());
self
}
pub fn build(self) -> Attributes { self.inner }
}
pub fn compose_attributes(
a: Option<Attributes>,
b: Option<Attributes>,
keep_empty: bool,
) -> Option<Attributes> {
if a.is_none() {
return b;
}
if b.is_none() {
return None;
}
let mut attrs_a = a.unwrap_or(Attributes::default());
let attrs_b = b.unwrap_or(Attributes::default());
attrs_a.extend(attrs_b);
if !keep_empty {
attrs_a.remove_empty_value()
}
return if attrs_a.is_empty() {
None
} else {
Some(result)
Some(attrs_a)
};
}
pub fn transform_attributes(a: Attributes, b: Attributes, priority: bool) -> Option<Attributes> {
if a.is_empty() {
return Some(b);
pub fn transform_attributes(
a: Option<Attributes>,
b: Option<Attributes>,
priority: bool,
) -> Option<Attributes> {
if a.is_none() {
return b;
}
if b.is_empty() {
if b.is_none() {
return None;
}
if !priority {
return Some(b);
return b;
}
let result = b.iter().fold(Attributes::new(), |mut attributes, (k, v)| {
if a.contains_key(k) == false {
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

View File

@ -1,4 +1,4 @@
use crate::{errors::OTError, operation::*};
use crate::{attributes::*, errors::OTError, operation::*};
use bytecount::num_chars;
use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator};
@ -19,15 +19,15 @@ impl Default for Delta {
}
}
impl FromIterator<OpType> for Delta {
fn from_iter<T: IntoIterator<Item = OpType>>(ops: T) -> Self {
let mut operations = Delta::default();
for op in ops {
operations.add(op);
}
operations
}
}
// impl FromIterator<OpType> for Delta {
// fn from_iter<T: IntoIterator<Item = OpType>>(ops: T) -> Self {
// let mut operations = Delta::default();
// for op in ops {
// operations.add(op);
// }
// operations
// }
// }
impl Delta {
#[inline]
@ -39,13 +39,13 @@ impl Delta {
}
}
fn add(&mut self, op: OpType) {
match op {
OpType::Delete(i) => self.delete(i),
OpType::Insert(s) => self.insert(&s),
OpType::Retain(i) => self.retain(i),
}
}
// fn add(&mut self, op: OpType) {
// match op {
// OpType::Delete(i) => self.delete(i),
// OpType::Insert(s) => self.insert(&s),
// OpType::Retain(i) => self.retain(i),
// }
// }
pub fn delete(&mut self, n: u64) {
if n == 0 {
@ -63,7 +63,7 @@ impl Delta {
self.ops.push(OperationBuilder::delete(n).build());
}
pub fn insert(&mut self, s: &str) {
pub fn insert(&mut self, s: &str, attrs: Option<Attributes>) {
if s.is_empty() {
return;
}
@ -90,10 +90,11 @@ impl Delta {
},
_ => OpType::Insert(s.to_owned()),
};
self.ops.push(OperationBuilder::new(new_last).build());
self.ops
.push(OperationBuilder::new(new_last).with_attrs(attrs).build());
}
pub fn retain(&mut self, n: u64) {
pub fn retain(&mut self, n: u64, attrs: Option<Attributes>) {
if n == 0 {
return;
}
@ -103,11 +104,13 @@ impl Delta {
if let Some(operation) = self.ops.last_mut() {
if operation.ty.is_retain() {
operation.retain(n);
operation.set_attrs(attrs);
return;
}
}
self.ops.push(OperationBuilder::retain(n).build());
self.ops
.push(OperationBuilder::retain(n).with_attrs(attrs).build());
}
/// Merges the operation with `other` into one operation while preserving
@ -142,28 +145,35 @@ impl Delta {
maybe_op1 = ops1.next();
},
(_, Some(OpType::Insert(s))) => {
new_delta.insert(s);
new_delta.insert(s, operation_attrs(&maybe_op2));
maybe_op2 = ops2.next();
},
(None, _) | (_, None) => {
return Err(OTError);
},
(Some(OpType::Retain(i)), Some(OpType::Retain(j))) => match i.cmp(&j) {
(Some(OpType::Retain(i)), Some(OpType::Retain(j))) => {
let new_attrs = compose_attributes(
operation_attrs(&maybe_op1),
operation_attrs(&maybe_op2),
true,
);
match i.cmp(&j) {
Ordering::Less => {
new_delta.retain(*i);
new_delta.retain(*i, new_attrs);
maybe_op2 = Some(OperationBuilder::retain(*j - *i).build());
maybe_op1 = ops1.next();
},
std::cmp::Ordering::Equal => {
new_delta.retain(*i);
new_delta.retain(*i, new_attrs);
maybe_op1 = ops1.next();
maybe_op2 = ops2.next();
},
std::cmp::Ordering::Greater => {
new_delta.retain(*j);
new_delta.retain(*j, new_attrs);
maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
maybe_op2 = ops2.next();
},
}
},
(Some(OpType::Insert(s)), Some(OpType::Delete(j))) => {
match (num_chars(s.as_bytes()) as u64).cmp(j) {
@ -188,9 +198,14 @@ impl Delta {
}
},
(Some(OpType::Insert(s)), Some(OpType::Retain(j))) => {
let new_attrs = compose_attributes(
operation_attrs(&maybe_op1),
operation_attrs(&maybe_op2),
false,
);
match (num_chars(s.as_bytes()) as u64).cmp(j) {
Ordering::Less => {
new_delta.insert(s);
new_delta.insert(s, new_attrs);
maybe_op2 = Some(
OperationBuilder::retain(*j - num_chars(s.as_bytes()) as u64)
.build(),
@ -198,13 +213,14 @@ impl Delta {
maybe_op1 = ops1.next();
},
Ordering::Equal => {
new_delta.insert(s);
new_delta.insert(s, new_attrs);
maybe_op1 = ops1.next();
maybe_op2 = ops2.next();
},
Ordering::Greater => {
let chars = &mut s.chars();
new_delta.insert(&chars.take(*j as usize).collect::<String>());
new_delta
.insert(&chars.take(*j as usize).collect::<String>(), new_attrs);
maybe_op1 = Some(OperationBuilder::insert(chars.collect()).build());
maybe_op2 = ops2.next();
},
@ -261,13 +277,23 @@ impl Delta {
) {
(None, None) => break,
(Some(OpType::Insert(s)), _) => {
a_prime.insert(s);
b_prime.retain(num_chars(s.as_bytes()) as _);
let new_attrs = compose_attributes(
operation_attrs(&maybe_op1),
operation_attrs(&maybe_op2),
true,
);
a_prime.insert(s, new_attrs.clone());
b_prime.retain(num_chars(s.as_bytes()) as _, new_attrs.clone());
maybe_op1 = ops1.next();
},
(_, Some(OpType::Insert(s))) => {
a_prime.retain(num_chars(s.as_bytes()) as _);
b_prime.insert(s);
let new_attrs = compose_attributes(
operation_attrs(&maybe_op1),
operation_attrs(&maybe_op2),
true,
);
a_prime.retain(num_chars(s.as_bytes()) as _, new_attrs.clone());
b_prime.insert(s, new_attrs.clone());
maybe_op2 = ops2.next();
},
(None, _) => {
@ -277,22 +303,27 @@ impl Delta {
return Err(OTError);
},
(Some(OpType::Retain(i)), Some(OpType::Retain(j))) => {
let new_attrs = compose_attributes(
operation_attrs(&maybe_op1),
operation_attrs(&maybe_op2),
true,
);
match i.cmp(&j) {
Ordering::Less => {
a_prime.retain(*i);
b_prime.retain(*i);
a_prime.retain(*i, new_attrs.clone());
b_prime.retain(*i, new_attrs.clone());
maybe_op2 = Some(OperationBuilder::retain(*j - *i).build());
maybe_op1 = ops1.next();
},
Ordering::Equal => {
a_prime.retain(*i);
b_prime.retain(*i);
a_prime.retain(*i, new_attrs.clone());
b_prime.retain(*i, new_attrs.clone());
maybe_op1 = ops1.next();
maybe_op2 = ops2.next();
},
Ordering::Greater => {
a_prime.retain(*j);
b_prime.retain(*j);
a_prime.retain(*j, new_attrs.clone());
b_prime.retain(*j, new_attrs.clone());
maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
maybe_op2 = ops2.next();
},
@ -396,7 +427,7 @@ impl Delta {
for op in &self.ops {
match &op.ty {
OpType::Retain(retain) => {
inverse.retain(*retain);
inverse.retain(*retain, op.attrs.clone());
for _ in 0..*retain {
chars.next();
}
@ -405,7 +436,10 @@ impl Delta {
inverse.delete(num_chars(insert.as_bytes()) as u64);
},
OpType::Delete(delete) => {
inverse.insert(&chars.take(*delete as usize).collect::<String>());
inverse.insert(
&chars.take(*delete as usize).collect::<String>(),
op.attrs.clone(),
);
},
}
}
@ -441,3 +475,10 @@ impl Delta {
#[inline]
pub fn ops(&self) -> &[Operation] { &self.ops }
}
pub fn operation_attrs(operation: &Option<Operation>) -> Option<Attributes> {
match operation {
None => None,
Some(operation) => operation.attrs.clone(),
}
}

View File

@ -1,4 +1,4 @@
mod attributes;
pub mod attributes;
pub mod delta;
pub mod errors;
pub mod operation;

View File

@ -8,7 +8,7 @@ use std::{
#[derive(Clone, Debug, PartialEq)]
pub struct Operation {
pub ty: OpType,
pub attrs: Attributes,
pub attrs: Option<Attributes>,
}
impl Operation {
@ -16,7 +16,14 @@ impl Operation {
pub fn retain(&mut self, n: u64) { self.ty.retain(n); }
pub fn is_plain(&self) -> bool { self.attrs.is_empty() }
pub fn set_attrs(&mut self, attrs: Option<Attributes>) { self.attrs = attrs; }
pub fn is_plain(&self) -> bool {
match self.attrs {
None => true,
Some(ref attrs) => attrs.is_empty(),
}
}
pub fn is_noop(&self) -> bool {
match self.ty {
@ -72,16 +79,11 @@ impl OpType {
pub struct OperationBuilder {
ty: OpType,
attrs: Attributes,
attrs: Option<Attributes>,
}
impl OperationBuilder {
pub fn new(ty: OpType) -> OperationBuilder {
OperationBuilder {
ty,
attrs: Attributes::default(),
}
}
pub fn new(ty: OpType) -> OperationBuilder { OperationBuilder { ty, attrs: None } }
pub fn retain(n: u64) -> OperationBuilder { OperationBuilder::new(OpType::Retain(n)) }
@ -89,7 +91,7 @@ impl OperationBuilder {
pub fn insert(s: String) -> OperationBuilder { OperationBuilder::new(OpType::Insert(s)) }
pub fn with_attrs(mut self, attrs: Attributes) -> OperationBuilder {
pub fn with_attrs(mut self, attrs: Option<Attributes>) -> OperationBuilder {
self.attrs = attrs;
self
}

View File

@ -15,9 +15,9 @@ impl Rng {
}
pub fn gen_delta(&mut self, s: &str) -> Delta {
let mut op = Delta::default();
let mut delta = Delta::default();
loop {
let left = s.chars().count() - op.base_len();
let left = s.chars().count() - delta.base_len();
if left == 0 {
break;
}
@ -28,19 +28,19 @@ impl Rng {
};
match self.0.gen_range(0.0, 1.0) {
f if f < 0.2 => {
op.insert(&self.gen_string(i));
delta.insert(&self.gen_string(i), None);
},
f if f < 0.4 => {
op.delete(i as u64);
delta.delete(i as u64);
},
_ => {
op.retain(i as u64);
delta.retain(i as u64, None);
},
}
}
if self.0.gen_range(0.0, 1.0) < 0.3 {
op.insert(&("1".to_owned() + &self.gen_string(10)));
delta.insert(&("1".to_owned() + &self.gen_string(10)), None);
}
op
delta
}
}

View File

@ -3,6 +3,7 @@ mod helper;
use crate::helper::Rng;
use bytecount::num_chars;
use flowy_ot::{
attributes::*,
delta::Delta,
operation::{OpType, OperationBuilder},
};
@ -12,13 +13,13 @@ fn lengths() {
let mut delta = Delta::default();
assert_eq!(delta.base_len, 0);
assert_eq!(delta.target_len, 0);
delta.retain(5);
delta.retain(5, None);
assert_eq!(delta.base_len, 5);
assert_eq!(delta.target_len, 5);
delta.insert("abc");
delta.insert("abc", None);
assert_eq!(delta.base_len, 5);
assert_eq!(delta.target_len, 8);
delta.retain(2);
delta.retain(2, None);
assert_eq!(delta.base_len, 7);
assert_eq!(delta.target_len, 10);
delta.delete(2);
@ -28,17 +29,17 @@ fn lengths() {
#[test]
fn sequence() {
let mut delta = Delta::default();
delta.retain(5);
delta.retain(0);
delta.insert("lorem");
delta.insert("");
delta.retain(5, None);
delta.retain(0, None);
delta.insert("lorem", None);
delta.insert("", None);
delta.delete(3);
delta.delete(0);
assert_eq!(delta.ops.len(), 3);
}
#[test]
fn apply() {
fn apply_1000() {
for _ in 0..1000 {
let mut rng = Rng::default();
let s = rng.gen_string(50);
@ -47,6 +48,22 @@ fn apply() {
assert_eq!(delta.apply(&s).unwrap().chars().count(), delta.target_len);
}
}
#[test]
fn apply() {
let s = "hello world,".to_owned();
let mut delta_a = Delta::default();
delta_a.insert(&s, None);
let mut delta_b = Delta::default();
delta_b.retain(s.len() as u64, None);
delta_b.insert("appflowy", None);
let after_a = delta_a.apply("").unwrap();
let after_b = delta_b.apply(&after_a).unwrap();
assert_eq!("hello world,appflowy", &after_b);
}
#[test]
fn invert() {
for _ in 0..1000 {
@ -62,8 +79,8 @@ fn invert() {
#[test]
fn empty_ops() {
let mut delta = Delta::default();
delta.retain(0);
delta.insert("");
delta.retain(0, None);
delta.insert("", None);
delta.delete(0);
assert_eq!(delta.ops.len(), 0);
}
@ -71,36 +88,36 @@ fn empty_ops() {
fn eq() {
let mut delta_a = Delta::default();
delta_a.delete(1);
delta_a.insert("lo");
delta_a.retain(2);
delta_a.retain(3);
delta_a.insert("lo", None);
delta_a.retain(2, None);
delta_a.retain(3, None);
let mut delta_b = Delta::default();
delta_b.delete(1);
delta_b.insert("l");
delta_b.insert("o");
delta_b.retain(5);
delta_b.insert("l", None);
delta_b.insert("o", None);
delta_b.retain(5, None);
assert_eq!(delta_a, delta_b);
delta_a.delete(1);
delta_b.retain(1);
delta_b.retain(1, None);
assert_ne!(delta_a, delta_b);
}
#[test]
fn ops_merging() {
let mut delta = Delta::default();
assert_eq!(delta.ops.len(), 0);
delta.retain(2);
delta.retain(2, None);
assert_eq!(delta.ops.len(), 1);
assert_eq!(delta.ops.last(), Some(&OperationBuilder::retain(2).build()));
delta.retain(3);
delta.retain(3, None);
assert_eq!(delta.ops.len(), 1);
assert_eq!(delta.ops.last(), Some(&OperationBuilder::retain(5).build()));
delta.insert("abc");
delta.insert("abc", None);
assert_eq!(delta.ops.len(), 2);
assert_eq!(
delta.ops.last(),
Some(&OperationBuilder::insert("abc".to_owned()).build())
);
delta.insert("xyz");
delta.insert("xyz", None);
assert_eq!(delta.ops.len(), 2);
assert_eq!(
delta.ops.last(),
@ -117,11 +134,11 @@ fn ops_merging() {
fn is_noop() {
let mut delta = Delta::default();
assert!(delta.is_noop());
delta.retain(5);
delta.retain(5, None);
assert!(delta.is_noop());
delta.retain(3);
delta.retain(3, None);
assert!(delta.is_noop());
delta.insert("lorem");
delta.insert("lorem", None);
assert!(!delta.is_noop());
}
#[test]