From e2b8738b652726589181855db855f6c1ccb7b0ee Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 5 Aug 2021 20:05:40 +0800 Subject: [PATCH] add flowy-ot document --- rust-lib/flowy-ot/src/client/document.rs | 177 ++++++++++++ rust-lib/flowy-ot/src/client/history.rs | 50 ++++ .../src/core/attributes/attributes.rs | 254 ++++++++++++++++++ .../flowy-ot/src/core/attributes/builder.rs | 52 ++++ rust-lib/flowy-ot/src/core/attributes/mod.rs | 5 + .../flowy-ot/src/core/operation/builder.rs | 36 +++ rust-lib/flowy-ot/src/core/operation/mod.rs | 7 + .../flowy-ot/src/core/operation/operation.rs | 197 ++++++++++++++ .../src/core/operation/operation_serde.rs | 137 ++++++++++ rust-lib/flowy-ot/tests/helper/mod.rs | 178 ++---------- rust-lib/flowy-ot/tests/op_test.rs | 2 - 11 files changed, 944 insertions(+), 151 deletions(-) create mode 100644 rust-lib/flowy-ot/src/client/document.rs create mode 100644 rust-lib/flowy-ot/src/client/history.rs create mode 100644 rust-lib/flowy-ot/src/core/attributes/attributes.rs create mode 100644 rust-lib/flowy-ot/src/core/attributes/builder.rs create mode 100644 rust-lib/flowy-ot/src/core/attributes/mod.rs create mode 100644 rust-lib/flowy-ot/src/core/operation/builder.rs create mode 100644 rust-lib/flowy-ot/src/core/operation/mod.rs create mode 100644 rust-lib/flowy-ot/src/core/operation/operation.rs create mode 100644 rust-lib/flowy-ot/src/core/operation/operation_serde.rs diff --git a/rust-lib/flowy-ot/src/client/document.rs b/rust-lib/flowy-ot/src/client/document.rs new file mode 100644 index 0000000000..fbb6655856 --- /dev/null +++ b/rust-lib/flowy-ot/src/client/document.rs @@ -0,0 +1,177 @@ +use crate::{ + client::{History, UndoResult}, + core::{ + Attribute, + Attributes, + AttributesDataRule, + AttrsBuilder, + Delta, + Interval, + OpBuilder, + Operation, + }, +}; + +pub struct Document { + data: Delta, + history: History, +} + +impl Document { + pub fn new() -> Self { + Document { + data: Delta::new(), + history: History::new(), + } + } + + pub fn edit(&mut self, index: usize, text: &str) { + if self.data.target_len < index { + log::error!( + "{} out of bounds. should 0..{}", + index, + self.data.target_len + ); + } + let probe = Interval::new(index, index + 1); + let mut attributes = self.data.get_attributes(probe); + if attributes == Attributes::Empty { + attributes = Attributes::Follow; + } + let insert = OpBuilder::insert(text).attributes(attributes).build(); + let interval = Interval::new(index, index); + + self.update_with_op(insert, interval); + } + + pub fn format(&mut self, interval: Interval, attribute: Attribute, enable: bool) { + let attributes = match enable { + true => AttrsBuilder::new().add_attribute(attribute).build(), + false => AttrsBuilder::new().remove_attribute(attribute).build(), + }; + + self.update_with_attribute(attributes, interval); + } + + pub fn undo(&mut self) -> UndoResult { unimplemented!() } + + pub fn redo(&mut self) -> UndoResult { unimplemented!() } + + pub fn delete(&mut self, interval: Interval) { + let delete = OpBuilder::delete(interval.size() as u64).build(); + self.update_with_op(delete, interval); + } + + pub fn to_json(&self) -> String { self.data.to_json() } + + pub fn data(&self) -> &Delta { &self.data } + + pub fn set_data(&mut self, data: Delta) { self.data = data; } + + fn update_with_op(&mut self, op: Operation, interval: Interval) { + let mut new_delta = Delta::default(); + let (prefix, interval, suffix) = split_length_with_interval(self.data.target_len, interval); + + // prefix + if prefix.is_empty() == false && prefix != interval { + let intervals = split_interval_with_delta(&self.data, &prefix); + intervals.into_iter().for_each(|i| { + let attributes = self.data.get_attributes(i); + log::debug!("prefix attribute: {:?}, interval: {:?}", attributes, i); + new_delta.retain(i.size() as u64, attributes); + }); + } + + log::debug!("add new op: {:?}", op); + new_delta.add(op); + + // suffix + if suffix.is_empty() == false { + let intervals = split_interval_with_delta(&self.data, &suffix); + intervals.into_iter().for_each(|i| { + let attributes = self.data.get_attributes(i); + log::debug!("suffix attribute: {:?}, interval: {:?}", attributes, i); + new_delta.retain(i.size() as u64, attributes); + }); + } + + let new_data = self.data.compose(&new_delta).unwrap(); + self.data = new_data; + } + + pub fn update_with_attribute(&mut self, mut attributes: Attributes, interval: Interval) { + let old_attributes = self.data.get_attributes(interval); + log::debug!( + "merge attributes: {:?}, with old: {:?}", + attributes, + old_attributes + ); + let new_attributes = match &mut attributes { + Attributes::Follow => old_attributes, + Attributes::Custom(attr_data) => { + attr_data.merge(old_attributes.data()); + attr_data.clone().into_attributes() + }, + Attributes::Empty => Attributes::Empty, + }; + + log::debug!("new attributes: {:?}", new_attributes); + let retain = OpBuilder::retain(interval.size() as u64) + .attributes(new_attributes) + .build(); + + log::debug!( + "Update delta with new attributes: {:?} at: {:?}", + retain, + interval + ); + + self.update_with_op(retain, interval); + } +} + +pub fn transform(left: &Document, right: &Document) -> (Document, Document) { + let (a_prime, b_prime) = left.data.transform(&right.data).unwrap(); + log::trace!("a:{:?},b:{:?}", a_prime, b_prime); + + let data_left = left.data.compose(&b_prime).unwrap(); + let data_right = right.data.compose(&a_prime).unwrap(); + ( + Document { + data: data_left, + history: left.history.clone(), + }, + Document { + data: data_right, + history: right.history.clone(), + }, + ) +} + +fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) { + let original_interval = Interval::new(0, length); + let prefix = original_interval.prefix(interval); + let suffix = original_interval.suffix(interval); + (prefix, interval, suffix) +} + +fn split_interval_with_delta(delta: &Delta, interval: &Interval) -> Vec { + let mut start = 0; + let mut new_intervals = vec![]; + delta.ops.iter().for_each(|op| match op { + Operation::Delete(_) => {}, + Operation::Retain(_) => {}, + Operation::Insert(insert) => { + let len = insert.num_chars() as usize; + let end = start + len; + let insert_interval = Interval::new(start, end); + let new_interval = interval.intersect(insert_interval); + + if !new_interval.is_empty() { + new_intervals.push(new_interval) + } + start += len; + }, + }); + new_intervals +} diff --git a/rust-lib/flowy-ot/src/client/history.rs b/rust-lib/flowy-ot/src/client/history.rs new file mode 100644 index 0000000000..caaf9b1a6d --- /dev/null +++ b/rust-lib/flowy-ot/src/client/history.rs @@ -0,0 +1,50 @@ +use crate::core::{Delta, Interval, OpBuilder, Operation}; + +const MAX_UNDOS: usize = 20; + +#[derive(Debug, Clone)] +pub struct Revision { + rev_id: u64, + delta: Delta, +} + +#[derive(Debug, Clone)] +pub struct UndoResult { + success: bool, + len: u64, +} + +#[derive(Debug, Clone)] +pub struct History { + cur_undo: usize, + undos: Vec, + redos: Vec, +} + +impl History { + pub fn new() -> Self { + History { + cur_undo: 1, + undos: Vec::new(), + redos: Vec::new(), + } + } + + pub fn can_undo(&self) -> bool { !self.undos.is_empty() } + + pub fn can_redo(&self) -> bool { !self.redos.is_empty() } + + pub fn record(&mut self, _change: Delta) {} + + pub fn undo(&mut self) -> Option { + if !self.can_undo() { + return None; + } + + let revision = self.undos.pop().unwrap(); + + Some(revision) + } + + pub fn redo(&mut self) -> Option { None } +} diff --git a/rust-lib/flowy-ot/src/core/attributes/attributes.rs b/rust-lib/flowy-ot/src/core/attributes/attributes.rs new file mode 100644 index 0000000000..32984ab11e --- /dev/null +++ b/rust-lib/flowy-ot/src/core/attributes/attributes.rs @@ -0,0 +1,254 @@ +use crate::core::{should_remove, Operation}; +use std::{collections::HashMap, fmt}; + +#[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 data(&self) -> Option { + match self { + Attributes::Follow => None, + Attributes::Custom(data) => Some(data.clone()), + Attributes::Empty => None, + } + } + + pub fn is_empty(&self) -> bool { + match self { + Attributes::Follow => true, + Attributes::Custom(data) => data.is_empty(), + Attributes::Empty => true, + } + } +} + +impl std::default::Default for Attributes { + fn default() -> Self { Attributes::Empty } +} + +impl fmt::Display for Attributes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Attributes::Follow => { + f.write_str("")?; + }, + Attributes::Custom(data) => { + f.write_fmt(format_args!("{:?}", data.inner))?; + }, + Attributes::Empty => { + f.write_str("")?; + }, + } + Ok(()) + } +} + +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct AttributesData { + #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(flatten)] + inner: HashMap, +} + +impl AttributesData { + pub fn new() -> Self { + AttributesData { + inner: HashMap::new(), + } + } + pub fn is_empty(&self) -> bool { + self.inner.values().filter(|v| !should_remove(v)).count() == 0 + } + + fn remove_empty(&mut self) { self.inner.retain(|_, v| !should_remove(v)); } + + pub fn extend(&mut self, other: AttributesData) { self.inner.extend(other.inner); } + + pub fn merge(&mut self, other: Option) { + if other.is_none() { + return; + } + + let mut new_attributes = other.unwrap().inner; + self.inner.iter().for_each(|(k, v)| { + if should_remove(v) { + new_attributes.remove(k); + } else { + new_attributes.insert(k.clone(), v.clone()); + } + }); + self.inner = new_attributes; + } +} + +pub trait AttributesDataRule { + fn apply_rule(&mut self); + + fn into_attributes(self) -> Attributes; +} +impl AttributesDataRule for AttributesData { + fn apply_rule(&mut self) { self.remove_empty(); } + + fn into_attributes(mut self) -> Attributes { + self.apply_rule(); + + if self.is_empty() { + Attributes::Empty + } else { + Attributes::Custom(self) + } + } +} + +pub trait AttributesRule { + fn apply_rule(self) -> Attributes; +} + +impl AttributesRule for Attributes { + fn apply_rule(self) -> Attributes { + match self { + Attributes::Follow => self, + Attributes::Custom(data) => data.into_attributes(), + Attributes::Empty => self, + } + } +} + +impl std::convert::From> for AttributesData { + fn from(attributes: HashMap) -> Self { AttributesData { inner: attributes } } +} + +impl std::ops::Deref for AttributesData { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl std::ops::DerefMut for AttributesData { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } +} + +pub(crate) fn attributes_from(operation: &Option) -> Option { + match operation { + None => None, + Some(operation) => Some(operation.get_attributes()), + } +} + +pub fn compose_operation(left: &Option, right: &Option) -> Attributes { + if left.is_none() && right.is_none() { + return Attributes::Empty; + } + let attr_l = attributes_from(left); + let attr_r = attributes_from(right); + + if attr_l.is_none() { + return attr_r.unwrap(); + } + + if attr_r.is_none() { + return attr_l.unwrap(); + } + + compose_attributes(attr_l.unwrap(), attr_r.unwrap()) +} + +pub fn transform_operation(left: &Option, right: &Option) -> Attributes { + let attr_l = attributes_from(left); + let attr_r = attributes_from(right); + + if attr_l.is_none() { + if attr_r.is_none() { + return Attributes::Empty; + } + + return match attr_r.as_ref().unwrap() { + Attributes::Follow => Attributes::Follow, + Attributes::Custom(_) => attr_r.unwrap(), + Attributes::Empty => Attributes::Empty, + }; + } + + transform_attributes(attr_l.unwrap(), attr_r.unwrap()) +} + +pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes { + let attr = attr.data(); + let base = base.data(); + + if attr.is_none() && base.is_none() { + return Attributes::Empty; + } + + 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.remove(k); + } + attributes + }); + + return Attributes::Custom(inverted); +} + +pub fn merge_attributes(attributes: Attributes, other: Attributes) -> Attributes { + match (&attributes, &other) { + (Attributes::Custom(data), Attributes::Custom(o_data)) => { + let mut data = data.clone(); + data.extend(o_data.clone()); + Attributes::Custom(data) + }, + (Attributes::Custom(data), _) => Attributes::Custom(data.clone()), + _ => other, + } +} + +fn compose_attributes(left: Attributes, right: Attributes) -> Attributes { + log::trace!("compose_attributes: a: {:?}, b: {:?}", left, right); + + let attr = match (&left, &right) { + (_, Attributes::Empty) => Attributes::Empty, + (_, Attributes::Custom(_)) => merge_attributes(left, right), + (Attributes::Custom(_), _) => merge_attributes(left, right), + _ => Attributes::Follow, + }; + + log::trace!("composed_attributes: a: {:?}", attr); + attr.apply_rule() +} + +fn transform_attributes(left: Attributes, right: Attributes) -> Attributes { + match (left, right) { + (Attributes::Custom(data_l), Attributes::Custom(data_r)) => { + let result = data_r + .iter() + .fold(AttributesData::new(), |mut new_attr_data, (k, v)| { + if !data_l.contains_key(k) { + new_attr_data.insert(k.clone(), v.clone()); + } + new_attr_data + }); + + Attributes::Custom(result) + }, + _ => Attributes::Empty, + } +} diff --git a/rust-lib/flowy-ot/src/core/attributes/builder.rs b/rust-lib/flowy-ot/src/core/attributes/builder.rs new file mode 100644 index 0000000000..9d774fd70c --- /dev/null +++ b/rust-lib/flowy-ot/src/core/attributes/builder.rs @@ -0,0 +1,52 @@ +use crate::core::{Attributes, AttributesData}; +use derive_more::Display; +const REMOVE_FLAG: &'static str = ""; +pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG } + +#[derive(Clone, Display)] +pub enum Attribute { + #[display(fmt = "bold")] + Bold, + #[display(fmt = "italic")] + Italic, +} + +pub struct AttrsBuilder { + inner: AttributesData, +} + +impl AttrsBuilder { + pub fn new() -> Self { + Self { + inner: AttributesData::default(), + } + } + + pub fn add_attribute(mut self, attribute: Attribute) -> Self { + self.inner + .insert(format!("{}", attribute), "true".to_owned()); + self + } + + pub fn remove_attribute(mut self, attribute: Attribute) -> Self { + self.inner + .insert(format!("{}", attribute), REMOVE_FLAG.to_owned()); + self + } + + pub fn bold(self, bold: bool) -> Self { + match bold { + true => self.add_attribute(Attribute::Bold), + false => self.remove_attribute(Attribute::Bold), + } + } + + pub fn italic(self, italic: bool) -> Self { + match italic { + true => self.add_attribute(Attribute::Italic), + false => self.remove_attribute(Attribute::Italic), + } + } + + pub fn build(self) -> Attributes { Attributes::Custom(self.inner) } +} diff --git a/rust-lib/flowy-ot/src/core/attributes/mod.rs b/rust-lib/flowy-ot/src/core/attributes/mod.rs new file mode 100644 index 0000000000..a87e8d00d4 --- /dev/null +++ b/rust-lib/flowy-ot/src/core/attributes/mod.rs @@ -0,0 +1,5 @@ +mod attributes; +mod builder; + +pub use attributes::*; +pub use builder::*; diff --git a/rust-lib/flowy-ot/src/core/operation/builder.rs b/rust-lib/flowy-ot/src/core/operation/builder.rs new file mode 100644 index 0000000000..0b79547c5e --- /dev/null +++ b/rust-lib/flowy-ot/src/core/operation/builder.rs @@ -0,0 +1,36 @@ +use crate::core::{Attributes, Operation}; + +pub struct OpBuilder { + ty: Operation, + attrs: Attributes, +} + +impl OpBuilder { + pub fn new(ty: Operation) -> OpBuilder { + OpBuilder { + ty, + attrs: Attributes::Empty, + } + } + + pub fn retain(n: u64) -> OpBuilder { OpBuilder::new(Operation::Retain(n.into())) } + + pub fn delete(n: u64) -> OpBuilder { OpBuilder::new(Operation::Delete(n)) } + + pub fn insert(s: &str) -> OpBuilder { OpBuilder::new(Operation::Insert(s.into())) } + + pub fn attributes(mut self, attrs: Attributes) -> OpBuilder { + self.attrs = attrs; + self + } + + pub fn build(self) -> Operation { + let mut operation = self.ty; + match &mut operation { + Operation::Delete(_) => {}, + Operation::Retain(retain) => retain.attributes = self.attrs, + Operation::Insert(insert) => insert.attributes = self.attrs, + } + operation + } +} diff --git a/rust-lib/flowy-ot/src/core/operation/mod.rs b/rust-lib/flowy-ot/src/core/operation/mod.rs new file mode 100644 index 0000000000..595a0e76c5 --- /dev/null +++ b/rust-lib/flowy-ot/src/core/operation/mod.rs @@ -0,0 +1,7 @@ +mod builder; +mod operation; +mod operation_serde; + +pub use builder::*; +pub use operation::*; +pub use operation_serde::*; diff --git a/rust-lib/flowy-ot/src/core/operation/operation.rs b/rust-lib/flowy-ot/src/core/operation/operation.rs new file mode 100644 index 0000000000..b37150bc04 --- /dev/null +++ b/rust-lib/flowy-ot/src/core/operation/operation.rs @@ -0,0 +1,197 @@ +use crate::core::{Attributes, OpBuilder}; +use bytecount::num_chars; +use std::{ + fmt, + ops::{Deref, DerefMut}, + str::Chars, +}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Operation { + Delete(u64), + Retain(Retain), + Insert(Insert), +} + +impl Operation { + pub fn is_delete(&self) -> bool { + match self { + Operation::Delete(_) => true, + _ => false, + } + } + + pub fn is_noop(&self) -> bool { + match self { + Operation::Retain(_) => true, + _ => false, + } + } + + pub fn get_attributes(&self) -> Attributes { + match self { + Operation::Delete(_) => Attributes::Empty, + Operation::Retain(retain) => retain.attributes.clone(), + Operation::Insert(insert) => insert.attributes.clone(), + } + } + + pub fn set_attributes(&mut self, attributes: Attributes) { + match self { + Operation::Delete(_) => { + log::error!("Delete should not contains attributes"); + }, + Operation::Retain(retain) => { + retain.attributes = attributes; + }, + Operation::Insert(insert) => { + insert.attributes = attributes; + }, + } + } + + pub fn has_attribute(&self) -> bool { + match self.get_attributes() { + Attributes::Follow => true, + Attributes::Custom(_) => false, + Attributes::Empty => true, + } + } + + pub fn length(&self) -> u64 { + match self { + Operation::Delete(n) => *n, + Operation::Retain(r) => r.n, + Operation::Insert(i) => i.num_chars(), + } + } + + pub fn is_empty(&self) -> bool { self.length() == 0 } +} + +impl fmt::Display for Operation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Operation::Delete(n) => { + f.write_fmt(format_args!("delete: {}", n))?; + }, + Operation::Retain(r) => { + f.write_fmt(format_args!( + "retain: {}, attributes: {}", + r.n, r.attributes + ))?; + }, + Operation::Insert(i) => { + f.write_fmt(format_args!( + "insert: {}, attributes: {}", + i.s, i.attributes + ))?; + }, + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Retain { + #[serde(rename(serialize = "retain", deserialize = "retain"))] + pub n: u64, + #[serde(skip_serializing_if = "is_empty")] + pub attributes: Attributes, +} + +impl Retain { + pub fn merge_or_new_op(&mut self, n: u64, attributes: Attributes) -> Option { + log::debug!( + "merge_retain_or_new_op: {:?}, {:?}", + self.attributes, + attributes + ); + + match &attributes { + Attributes::Follow => { + log::debug!("Follow attribute: {:?}", self.attributes); + self.n += n; + None + }, + Attributes::Custom(_) | Attributes::Empty => { + if self.attributes == attributes { + log::debug!("Attribute equal"); + self.n += n; + None + } else { + log::debug!("New retain op"); + Some(OpBuilder::retain(n).attributes(attributes).build()) + } + }, + } + } +} + +impl std::convert::From for Retain { + fn from(n: u64) -> Self { + Retain { + n, + attributes: Attributes::default(), + } + } +} + +impl Deref for Retain { + type Target = u64; + + fn deref(&self) -> &Self::Target { &self.n } +} + +impl DerefMut for Retain { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.n } +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Insert { + #[serde(rename(serialize = "insert", deserialize = "insert"))] + pub s: String, + + #[serde(skip_serializing_if = "is_empty")] + pub attributes: Attributes, +} + +impl Insert { + pub fn as_bytes(&self) -> &[u8] { self.s.as_bytes() } + + pub fn chars(&self) -> Chars<'_> { self.s.chars() } + + pub fn num_chars(&self) -> u64 { num_chars(self.s.as_bytes()) as _ } + + pub fn merge_or_new_op(&mut self, s: &str, attributes: Attributes) -> Option { + match &attributes { + Attributes::Follow => { + self.s += s; + return None; + }, + Attributes::Custom(_) | Attributes::Empty => { + if self.attributes == attributes { + self.s += s; + None + } else { + Some(OpBuilder::insert(s).attributes(attributes).build()) + } + }, + } + } +} + +impl std::convert::From for Insert { + fn from(s: String) -> Self { + Insert { + s, + attributes: Attributes::default(), + } + } +} + +impl std::convert::From<&str> for Insert { + fn from(s: &str) -> Self { Insert::from(s.to_owned()) } +} + +fn is_empty(attributes: &Attributes) -> bool { attributes.is_empty() } diff --git a/rust-lib/flowy-ot/src/core/operation/operation_serde.rs b/rust-lib/flowy-ot/src/core/operation/operation_serde.rs new file mode 100644 index 0000000000..4d3ab82688 --- /dev/null +++ b/rust-lib/flowy-ot/src/core/operation/operation_serde.rs @@ -0,0 +1,137 @@ +use crate::core::{Attributes, Delta, Operation}; +use serde::{ + de, + de::{MapAccess, SeqAccess, Visitor}, + ser::{SerializeMap, SerializeSeq}, + Deserialize, + Deserializer, + Serialize, + Serializer, +}; +use std::fmt; + +impl Serialize for Operation { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Operation::Retain(retain) => retain.serialize(serializer), + Operation::Delete(i) => { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("delete", i)?; + map.end() + }, + Operation::Insert(insert) => insert.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for Operation { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct OperationVisitor; + + impl<'de> Visitor<'de> for OperationVisitor { + type Value = Operation; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an integer between -2^64 and 2^63 or a string") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut operation = None; + let mut attributes = None; + while let Some(key) = map.next_key()? { + match key { + "delete" => { + if operation.is_some() { + return Err(de::Error::duplicate_field("operation")); + } + operation = Some(Operation::Delete(map.next_value()?)); + }, + "retain" => { + if operation.is_some() { + return Err(de::Error::duplicate_field("operation")); + } + let i: u64 = map.next_value()?; + operation = Some(Operation::Retain(i.into())); + }, + "insert" => { + if operation.is_some() { + return Err(de::Error::duplicate_field("operation")); + } + let i: String = map.next_value()?; + operation = Some(Operation::Insert(i.into())); + }, + "attributes" => { + if attributes.is_some() { + return Err(de::Error::duplicate_field("attributes")); + } + let map: Attributes = map.next_value()?; + attributes = Some(map); + }, + _ => panic!(), + } + } + match operation { + None => Err(de::Error::missing_field("operation")), + Some(mut operation) => { + operation.set_attributes(attributes.unwrap_or(Attributes::Empty)); + Ok(operation) + }, + } + } + } + + deserializer.deserialize_any(OperationVisitor) + } +} + +impl Serialize for Delta { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.ops.len()))?; + for op in self.ops.iter() { + seq.serialize_element(op)?; + } + seq.end() + } +} + +impl<'de> Deserialize<'de> for Delta { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct OperationSeqVisitor; + + impl<'de> Visitor<'de> for OperationSeqVisitor { + type Value = Delta; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut o = Delta::default(); + while let Some(op) = seq.next_element()? { + o.add(op); + } + Ok(o) + } + } + + deserializer.deserialize_seq(OperationSeqVisitor) + } +} diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index e0634d2e6c..7608b07995 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -1,5 +1,8 @@ use derive_more::Display; -use flowy_ot::core::*; +use flowy_ot::{ + client::{transform, Document}, + core::*, +}; use rand::{prelude::*, Rng as WrappedRng}; use std::sync::Once; @@ -29,15 +32,12 @@ pub enum TestOp { #[display(fmt = "Undo")] Undo(usize, usize), - #[display(fmt = "AssertStr")] - AssertStr(usize, &'static str), - #[display(fmt = "AssertOpsJson")] AssertOpsJson(usize, &'static str), } pub struct OpTester { - deltas: Vec, + documents: Vec, } impl OpTester { @@ -48,52 +48,47 @@ impl OpTester { env_logger::init(); }); - let mut deltas = Vec::with_capacity(2); + let mut documents = Vec::with_capacity(2); for _ in 0..2 { - let delta = Delta::default(); - deltas.push(delta); + documents.push(Document::new()); } - Self { deltas } + Self { documents } } pub fn run_op(&mut self, op: &TestOp) { log::debug!("***************** 😈{} *******************", &op); match op { TestOp::Insert(delta_i, s, index) => { - self.update_delta_with_insert(*delta_i, s, *index); + let document = &mut self.documents[*delta_i]; + document.edit(*index, s); }, TestOp::Delete(delta_i, interval) => { - // - self.update_delta_with_delete(*delta_i, interval); + let document = &mut self.documents[*delta_i]; + document.delete(*interval); }, - TestOp::InsertBold(delta_i, s, _interval) => { - let attrs = AttrsBuilder::new().bold(true).build(); - let delta = &mut self.deltas[*delta_i]; - delta.insert(s, attrs); + TestOp::InsertBold(delta_i, s, interval) => { + let document = &mut self.documents[*delta_i]; + document.edit(interval.start, s); + document.format(*interval, Attribute::Bold, true); }, TestOp::Bold(delta_i, interval, enable) => { - let attrs = AttrsBuilder::new().bold(*enable).build(); - self.update_delta_with_attribute(*delta_i, attrs, interval); + let document = &mut self.documents[*delta_i]; + document.format(*interval, Attribute::Bold, *enable); }, TestOp::Italic(delta_i, interval, enable) => { - let attrs = AttrsBuilder::new().italic(*enable).build(); - self.update_delta_with_attribute(*delta_i, attrs, interval); + let document = &mut self.documents[*delta_i]; + document.format(*interval, Attribute::Italic, *enable); }, TestOp::Transform(delta_a_i, delta_b_i) => { - let delta_a = &self.deltas[*delta_a_i]; - let delta_b = &self.deltas[*delta_b_i]; + let (document_a, document_b) = + transform(&self.documents[*delta_a_i], &self.documents[*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(); - - self.deltas[*delta_a_i] = new_delta_a; - self.deltas[*delta_b_i] = new_delta_b; + self.documents[*delta_a_i] = document_a; + self.documents[*delta_b_i] = document_b; }, TestOp::Undo(delta_a_i, delta_b_i) => { - let delta_a = &self.deltas[*delta_a_i]; - let delta_b = &self.deltas[*delta_b_i]; + let delta_a = &self.documents[*delta_a_i].data(); + let delta_b = &self.documents[*delta_b_i].data(); log::debug!("Invert: "); log::debug!("a: {}", delta_a.to_json()); log::debug!("b: {}", delta_b.to_json()); @@ -109,18 +104,13 @@ impl OpTester { log::debug!("inverted delta a: {}", new_delta_after_undo.to_string()); - assert_eq!(delta_a, &new_delta_after_undo); + assert_eq!(delta_a, &&new_delta_after_undo); - self.deltas[*delta_a_i] = new_delta_after_undo; - }, - TestOp::AssertStr(delta_i, expected) => { - let s = self.deltas[*delta_i].apply("").unwrap(); - assert_eq!(&s, expected); + self.documents[*delta_a_i].set_data(new_delta_after_undo); }, TestOp::AssertOpsJson(delta_i, expected) => { - log::debug!("AssertOpsJson: {:?}", self.deltas[*delta_i]); - let delta_i_json = serde_json::to_string(&self.deltas[*delta_i]).unwrap(); + let delta_i_json = self.documents[*delta_i].to_json(); let expected_delta: Delta = serde_json::from_str(expected).unwrap(); let target_delta: Delta = serde_json::from_str(&delta_i_json).unwrap(); @@ -139,116 +129,6 @@ impl OpTester { self.run_op(op); } } - - pub fn get_delta(&mut self, index: usize) -> &mut Delta { &mut self.deltas[index] } - - pub fn update_delta_with_insert(&mut self, delta_index: usize, s: &str, index: usize) { - let old_delta = &mut self.deltas[delta_index]; - let target_interval = Interval::new(0, old_delta.target_len); - if old_delta.target_len < index { - log::error!("{} out of bounds {}", index, target_interval); - } - - let mut attributes = old_delta.get_attributes(Interval::new(index, index + 1)); - if attributes == Attributes::Empty { - attributes = Attributes::Follow; - } - let insert = OpBuilder::insert(s).attributes(attributes).build(); - let new_delta = new_delta_with_op(old_delta, insert, Interval::new(index, index)); - self.deltas[delta_index] = new_delta; - } - - pub fn update_delta_with_attribute( - &mut self, - delta_index: usize, - mut attributes: Attributes, - interval: &Interval, - ) { - let old_delta = &self.deltas[delta_index]; - let old_attributes = old_delta.get_attributes(*interval); - log::debug!( - "merge attributes: {:?}, with old: {:?}", - attributes, - old_attributes - ); - let new_attributes = match &mut attributes { - Attributes::Follow => old_attributes, - Attributes::Custom(attr_data) => { - attr_data.merge(old_attributes.data()); - attr_data.clone().into_attributes() - }, - Attributes::Empty => Attributes::Empty, - }; - - log::debug!("new attributes: {:?}", new_attributes); - let retain = OpBuilder::retain(interval.size() as u64) - .attributes(new_attributes) - .build(); - - log::debug!( - "Update delta with new attributes: {:?} at: {:?}", - retain, - interval - ); - - let new_delta = new_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 = new_delta_with_op(old_delta, delete, *interval); - self.deltas[delta_index] = new_delta; - } -} - -fn new_delta_with_op(delta: &Delta, op: Operation, interval: Interval) -> Delta { - let mut new_delta = Delta::default(); - let (prefix, interval, suffix) = target_length_split_with_interval(delta.target_len, interval); - - // prefix - if prefix.is_empty() == false && prefix != interval { - let intervals = split_interval_with_delta(delta, &prefix); - intervals.into_iter().for_each(|p_interval| { - let attributes = delta.get_attributes(p_interval); - log::debug!( - "prefix attribute: {:?}, interval: {:?}", - attributes, - p_interval - ); - new_delta.retain(p_interval.size() as u64, attributes); - }); - } - - log::debug!("add new op: {:?}", op); - new_delta.add(op); - - // suffix - if suffix.is_empty() == false { - let intervals = split_interval_with_delta(delta, &suffix); - intervals.into_iter().for_each(|s_interval| { - let attributes = delta.get_attributes(s_interval); - log::debug!( - "suffix attribute: {:?}, interval: {:?}", - attributes, - s_interval - ); - new_delta.retain(s_interval.size() as u64, attributes); - }); - } - - delta.compose(&new_delta).unwrap() -} - -pub fn target_length_split_with_interval( - length: usize, - interval: Interval, -) -> (Interval, Interval, Interval) { - let original_interval = Interval::new(0, length); - let prefix = original_interval.prefix(interval); - let suffix = original_interval.suffix(interval); - (prefix, interval, suffix) } pub fn debug_print_delta(delta: &Delta) { diff --git a/rust-lib/flowy-ot/tests/op_test.rs b/rust-lib/flowy-ot/tests/op_test.rs index 327580c13b..27ec097656 100644 --- a/rust-lib/flowy-ot/tests/op_test.rs +++ b/rust-lib/flowy-ot/tests/op_test.rs @@ -191,8 +191,6 @@ fn transform2() { Insert(0, "123", 0), Insert(1, "456", 0), Transform(0, 1), - AssertStr(0, "123456"), - AssertStr(1, "123456"), AssertOpsJson(0, r#"[{"insert":"123456"}]"#), AssertOpsJson(1, r#"[{"insert":"123456"}]"#), ];