瀏覽代碼

chore: update lib ot tests

appflowy 2 年之前
父節點
當前提交
798e16d3aa

+ 1 - 1
frontend/rust-lib/flowy-folder/src/services/web_socket.rs

@@ -10,7 +10,7 @@ use flowy_sync::{
     },
 };
 use lib_infra::future::{BoxResultFuture, FutureResult};
-use lib_ot::core::{OperationTransformable, PhantomAttributes, PlainTextDelta};
+use lib_ot::core::{OperationTransform, PhantomAttributes, PlainTextDelta};
 use parking_lot::RwLock;
 use std::{sync::Arc, time::Duration};
 

+ 1 - 1
frontend/rust-lib/flowy-text-block/src/queue.rs

@@ -12,7 +12,7 @@ use flowy_sync::{
 };
 use futures::stream::StreamExt;
 use lib_ot::{
-    core::{Interval, OperationTransformable},
+    core::{Interval, OperationTransform},
     rich_text::{RichTextAttribute, RichTextAttributes, RichTextDelta},
 };
 use std::sync::Arc;

+ 2 - 2
frontend/rust-lib/flowy-text-block/tests/editor/attribute_test.rs

@@ -1,7 +1,7 @@
 #![cfg_attr(rustfmt, rustfmt::skip)]
 use crate::editor::{TestBuilder, TestOp::*};
 use flowy_sync::client_document::{NewlineDoc, PlainDoc};
-use lib_ot::core::{Interval, OperationTransformable, NEW_LINE, WHITESPACE, FlowyStr};
+use lib_ot::core::{Interval, OperationTransform, NEW_LINE, WHITESPACE, FlowyStr};
 use unicode_segmentation::UnicodeSegmentation;
 use lib_ot::rich_text::RichTextDelta;
 
@@ -724,7 +724,7 @@ fn attributes_preserve_header_format_on_merge() {
 fn attributes_format_emoji() {
     let emoji_s = "👋 ";
     let s: FlowyStr = emoji_s.into();
-    let len = s.utf16_size();
+    let len = s.utf16_len();
     assert_eq!(3, len);
     assert_eq!(2, s.graphemes(true).count());
     

+ 1 - 1
frontend/rust-lib/flowy-text-block/tests/editor/mod.rs

@@ -302,7 +302,7 @@ impl Rng {
         let mut delta = RichTextDelta::default();
         let s = FlowyStr::from(s);
         loop {
-            let left = s.utf16_size() - delta.utf16_base_len;
+            let left = s.utf16_len() - delta.utf16_base_len;
             if left == 0 {
                 break;
             }

+ 6 - 9
frontend/rust-lib/flowy-text-block/tests/editor/op_test.rs

@@ -1,6 +1,7 @@
 #![allow(clippy::all)]
 use crate::editor::{Rng, TestBuilder, TestOp::*};
 use flowy_sync::client_document::{NewlineDoc, PlainDoc};
+use lib_ot::rich_text::RichTextDeltaBuilder;
 use lib_ot::{
     core::Interval,
     core::*,
@@ -39,12 +40,8 @@ fn attributes_insert_text_at_middle() {
 
 #[test]
 fn delta_get_ops_in_interval_1() {
-    let mut delta = RichTextDelta::default();
-    let insert_a = OperationBuilder::insert("123").build();
-    let insert_b = OperationBuilder::insert("4").build();
-
-    delta.add(insert_a.clone());
-    delta.add(insert_b.clone());
+    let operations = OperationBuilder::new().insert("123", None).insert("4", None).build();
+    let delta = RichTextDeltaBuilder::from_operations(operations);
 
     let mut iterator = DeltaIterator::from_interval(&delta, Interval::new(0, 4));
     assert_eq!(iterator.ops(), delta.ops);
@@ -365,7 +362,7 @@ fn apply_1000() {
         let mut rng = Rng::default();
         let s: FlowyStr = rng.gen_string(50).into();
         let delta = rng.gen_delta(&s);
-        assert_eq!(s.utf16_size(), delta.utf16_base_len);
+        assert_eq!(s.utf16_len(), delta.utf16_base_len);
     }
 }
 
@@ -489,11 +486,11 @@ fn compose() {
         let s = rng.gen_string(20);
         let a = rng.gen_delta(&s);
         let after_a: FlowyStr = a.apply(&s).unwrap().into();
-        assert_eq!(a.utf16_target_len, after_a.utf16_size());
+        assert_eq!(a.utf16_target_len, after_a.utf16_len());
 
         let b = rng.gen_delta(&after_a);
         let after_b: FlowyStr = b.apply(&after_a).unwrap().into();
-        assert_eq!(b.utf16_target_len, after_b.utf16_size());
+        assert_eq!(b.utf16_target_len, after_b.utf16_len());
 
         let ab = a.compose(&b).unwrap();
         assert_eq!(ab.utf16_target_len, b.utf16_target_len);

+ 1 - 1
shared-lib/flowy-sync/src/client_folder/folder_pad.rs

@@ -434,7 +434,7 @@ mod tests {
     use chrono::Utc;
 
     use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
-    use lib_ot::core::{OperationTransformable, PlainTextDelta, PlainTextDeltaBuilder};
+    use lib_ot::core::{OperationTransform, PlainTextDelta, PlainTextDeltaBuilder};
 
     #[test]
     fn folder_add_workspace() {

+ 1 - 1
shared-lib/flowy-sync/src/client_grid/grid_block_revsion_pad.rs

@@ -4,7 +4,7 @@ use crate::util::{cal_diff, make_delta_from_revisions};
 use flowy_grid_data_model::revision::{
     gen_block_id, gen_row_id, CellRevision, GridBlockRevision, RowMetaChangeset, RowRevision,
 };
-use lib_ot::core::{OperationTransformable, PhantomAttributes, PlainTextDelta, PlainTextDeltaBuilder};
+use lib_ot::core::{OperationTransform, PhantomAttributes, PlainTextDelta, PlainTextDeltaBuilder};
 use std::borrow::Cow;
 use std::collections::HashMap;
 use std::sync::Arc;

+ 1 - 1
shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs

@@ -9,7 +9,7 @@ use flowy_grid_data_model::revision::{
     GridLayoutRevision, GridRevision, GridSettingRevision, GridSortRevision,
 };
 use lib_infra::util::move_vec_element;
-use lib_ot::core::{OperationTransformable, PhantomAttributes, PlainTextDelta, PlainTextDeltaBuilder};
+use lib_ot::core::{OperationTransform, PhantomAttributes, PlainTextDelta, PlainTextDeltaBuilder};
 use std::collections::HashMap;
 use std::sync::Arc;
 

+ 1 - 1
shared-lib/flowy-sync/src/server_folder/folder_pad.rs

@@ -1,5 +1,5 @@
 use crate::{entities::folder::FolderDelta, errors::CollaborateError, synchronizer::RevisionSyncObject};
-use lib_ot::core::{OperationTransformable, PhantomAttributes, PlainTextDelta};
+use lib_ot::core::{OperationTransform, PhantomAttributes, PlainTextDelta};
 
 pub struct ServerFolder {
     folder_id: String,

+ 3 - 3
shared-lib/flowy-sync/src/util.rs

@@ -9,7 +9,7 @@ use crate::{
 use dissimilar::Chunk;
 use lib_ot::core::{DeltaBuilder, FlowyStr};
 use lib_ot::{
-    core::{Attributes, Delta, OperationTransformable, NEW_LINE, WHITESPACE},
+    core::{Attributes, Delta, OperationTransform, NEW_LINE, WHITESPACE},
     rich_text::RichTextDelta,
 };
 use serde::de::DeserializeOwned;
@@ -208,10 +208,10 @@ pub fn cal_diff<T: Attributes>(old: String, new: String) -> Option<Delta<T>> {
     for chunk in &chunks {
         match chunk {
             Chunk::Equal(s) => {
-                delta_builder = delta_builder.retain(FlowyStr::from(*s).utf16_size());
+                delta_builder = delta_builder.retain(FlowyStr::from(*s).utf16_len());
             }
             Chunk::Delete(s) => {
-                delta_builder = delta_builder.delete(FlowyStr::from(*s).utf16_size());
+                delta_builder = delta_builder.delete(FlowyStr::from(*s).utf16_len());
             }
             Chunk::Insert(s) => {
                 delta_builder = delta_builder.insert(*s);

+ 66 - 0
shared-lib/lib-ot/src/core/delta/builder.rs

@@ -1,5 +1,6 @@
 use crate::core::delta::{trim, Delta};
 use crate::core::operation::{Attributes, PhantomAttributes};
+use crate::core::Operation;
 
 pub type PlainTextDeltaBuilder = DeltaBuilder<PhantomAttributes>;
 
@@ -7,6 +8,16 @@ pub type PlainTextDeltaBuilder = DeltaBuilder<PhantomAttributes>;
 ///
 /// Note that all edit operations must be sorted; the start point of each
 /// interval must be no less than the end point of the previous one.
+///
+/// # Examples
+///
+/// ```
+/// use lib_ot::core::PlainTextDeltaBuilder;
+/// let delta = PlainTextDeltaBuilder::new()
+///         .insert("AppFlowy")
+///         .build();
+/// assert_eq!(delta.content_str().unwrap(), "AppFlowy");
+/// ```
 pub struct DeltaBuilder<T: Attributes> {
     delta: Delta<T>,
 }
@@ -28,8 +39,26 @@ where
         DeltaBuilder::default()
     }
 
+    pub fn from_operations(operations: Vec<Operation<T>>) -> Delta<T> {
+        let mut delta = DeltaBuilder::default().build();
+        operations.into_iter().for_each(|operation| {
+            delta.add(operation);
+        });
+        delta
+    }
+
     /// Retain the 'n' characters with the attributes. Use 'retain' instead if you don't
     /// need any attributes.
+    /// # Examples
+    ///
+    /// ```
+    /// use lib_ot::rich_text::{RichTextAttribute, RichTextDelta, RichTextDeltaBuilder};
+    ///
+    /// let mut attribute = RichTextAttribute::Bold(true);
+    /// let delta = RichTextDeltaBuilder::new().retain_with_attributes(7, attribute.into()).build();
+    ///
+    /// assert_eq!(delta.to_json_str(), r#"[{"retain":7,"attributes":{"bold":true}}]"#);
+    /// ```
     pub fn retain_with_attributes(mut self, n: usize, attrs: T) -> Self {
         self.delta.retain(n, attrs);
         self
@@ -41,6 +70,24 @@ where
     }
 
     /// Deletes the given interval. Panics if interval is not properly sorted.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use lib_ot::core::{OperationTransform, PlainTextDeltaBuilder};
+    ///
+    /// let delta = PlainTextDeltaBuilder::new()
+    ///         .insert("AppFlowy...")
+    ///         .build();
+    ///
+    /// let changeset = PlainTextDeltaBuilder::new()
+    ///         .retain(8)
+    ///         .delete(3)
+    ///         .build();
+    ///
+    /// let new_delta = delta.compose(&changeset).unwrap();
+    /// assert_eq!(new_delta.content_str().unwrap(), "AppFlowy");
+    /// ```
     pub fn delete(mut self, n: usize) -> Self {
         self.delta.delete(n);
         self
@@ -58,6 +105,25 @@ where
         self
     }
 
+    /// Removes trailing retain operation with empty attributes
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use lib_ot::core::{OperationTransform, PlainTextDeltaBuilder};
+    /// use lib_ot::rich_text::{RichTextAttribute, RichTextDeltaBuilder};
+    /// let delta = PlainTextDeltaBuilder::new()
+    ///         .retain(3)
+    ///         .trim()
+    ///         .build();
+    /// assert_eq!(delta.ops.len(), 0);
+    ///
+    /// let delta = RichTextDeltaBuilder::new()
+    ///         .retain_with_attributes(3, RichTextAttribute::Bold(true).into())
+    ///         .trim()
+    ///         .build();
+    /// assert_eq!(delta.ops.len(), 1);
+    /// ```
     pub fn trim(mut self) -> Self {
         trim(&mut self.delta);
         self

+ 16 - 5
shared-lib/lib-ot/src/core/delta/delta.rs

@@ -3,7 +3,7 @@ use crate::errors::{ErrorBuilder, OTError, OTErrorCode};
 use crate::core::delta::{DeltaIterator, MAX_IV_LEN};
 use crate::core::flowy_str::FlowyStr;
 use crate::core::interval::Interval;
-use crate::core::operation::{Attributes, Operation, OperationBuilder, OperationTransformable, PhantomAttributes};
+use crate::core::operation::{Attributes, Operation, OperationBuilder, OperationTransform, PhantomAttributes};
 use bytes::Bytes;
 use serde::de::DeserializeOwned;
 use std::{
@@ -123,7 +123,7 @@ where
             return;
         }
 
-        self.utf16_target_len += s.utf16_size();
+        self.utf16_target_len += s.utf16_len();
         let new_last = match self.ops.as_mut_slice() {
             [.., Operation::<T>::Insert(insert)] => {
                 //
@@ -191,12 +191,12 @@ where
     /// ```
     pub fn apply(&self, applied_str: &str) -> Result<String, OTError> {
         let applied_str: FlowyStr = applied_str.into();
-        if applied_str.utf16_size() != self.utf16_base_len {
+        if applied_str.utf16_len() != self.utf16_base_len {
             return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength)
                 .msg(format!(
                     "Expected: {}, but received: {}",
                     self.utf16_base_len,
-                    applied_str.utf16_size()
+                    applied_str.utf16_len()
                 ))
                 .build());
         }
@@ -289,7 +289,7 @@ where
     }
 }
 
-impl<T> OperationTransformable for Delta<T>
+impl<T> OperationTransform for Delta<T>
 where
     T: Attributes,
 {
@@ -568,6 +568,17 @@ impl<T> Delta<T>
 where
     T: Attributes + DeserializeOwned,
 {
+    /// # Examples
+    ///
+    /// ```
+    /// use lib_ot::core::DeltaBuilder;
+    /// use lib_ot::rich_text::{RichTextDelta};
+    /// let json = r#"[
+    ///     {"retain":7,"attributes":{"bold":null}}
+    ///  ]"#;
+    /// let delta = RichTextDelta::from_json_str(json).unwrap();
+    /// assert_eq!(delta.to_json_str(), r#"[{"retain":7,"attributes":{"bold":""}}]"#);
+    /// ```
     pub fn from_json_str(json: &str) -> Result<Self, OTError> {
         let delta = serde_json::from_str(json).map_err(|e| {
             tracing::trace!("Deserialize failed: {:?}", e);

+ 12 - 0
shared-lib/lib-ot/src/core/delta/iterator.rs

@@ -7,6 +7,18 @@ use std::ops::{Deref, DerefMut};
 
 pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize;
 
+/// Retain the 'n' characters with the attributes. Use 'retain' instead if you don't
+/// need any attributes.
+/// # Examples
+///
+/// ```
+/// use lib_ot::rich_text::{RichTextAttribute, RichTextDelta, RichTextDeltaBuilder};
+///
+/// let mut attribute = RichTextAttribute::Bold(true);
+/// let delta = RichTextDeltaBuilder::new().retain_with_attributes(7, attribute.into()).build();
+///
+/// assert_eq!(delta.to_json_str(), r#"[{"retain":7,"attributes":{"bold":true}}]"#);
+/// ```
 pub struct DeltaIterator<'a, T: Attributes> {
     cursor: DeltaCursor<'a, T>,
 }

+ 22 - 6
shared-lib/lib-ot/src/core/flowy_str.rs

@@ -5,8 +5,24 @@ use std::{fmt, fmt::Formatter};
 pub struct FlowyStr(pub String);
 
 impl FlowyStr {
-    // https://stackoverflow.com/questions/2241348/what-is-unicode-utf-8-utf-16
-    pub fn utf16_size(&self) -> usize {
+    ///
+    /// # Arguments
+    ///
+    /// * `delta`: The delta you want to iterate over.
+    /// * `interval`: The range for the cursor movement.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use lib_ot::core::FlowyStr;
+    /// let utf16_len = FlowyStr::from("👋").utf16_len();
+    /// assert_eq!(utf16_len, 2);
+    /// let bytes_len = String::from("👋").len();
+    /// assert_eq!(bytes_len, 4);
+    ///         
+    /// ```
+    /// https://stackoverflow.com/questions/2241348/what-is-unicode-utf-8-utf-16
+    pub fn utf16_len(&self) -> usize {
         count_utf16_code_units(&self.0)
     }
 
@@ -231,7 +247,7 @@ mod tests {
 
     #[test]
     fn flowy_str_code_unit() {
-        let size = FlowyStr::from("👋").utf16_size();
+        let size = FlowyStr::from("👋").utf16_len();
         assert_eq!(size, 2);
 
         let s: FlowyStr = "👋 \n👋".into();
@@ -251,7 +267,7 @@ mod tests {
     #[test]
     fn flowy_str_sub_str_in_chinese() {
         let s: FlowyStr = "你好\n😁".into();
-        let size = s.utf16_size();
+        let size = s.utf16_len();
         assert_eq!(size, 5);
 
         let output1 = s.sub_str(Interval::new(0, 2)).unwrap();
@@ -265,7 +281,7 @@ mod tests {
     #[test]
     fn flowy_str_sub_str_in_chinese2() {
         let s: FlowyStr = "😁 \n".into();
-        let size = s.utf16_size();
+        let size = s.utf16_len();
         assert_eq!(size, 4);
 
         let output1 = s.sub_str(Interval::new(0, 3)).unwrap();
@@ -277,7 +293,7 @@ mod tests {
     #[test]
     fn flowy_str_sub_str_in_english() {
         let s: FlowyStr = "ab".into();
-        let size = s.utf16_size();
+        let size = s.utf16_len();
         assert_eq!(size, 2);
 
         let output = s.sub_str(Interval::new(0, 2)).unwrap();

+ 27 - 21
shared-lib/lib-ot/src/core/operation/builder.rs

@@ -5,31 +5,43 @@ pub type RichTextOpBuilder = OperationBuilder<RichTextAttributes>;
 pub type PlainTextOpBuilder = OperationBuilder<PhantomAttributes>;
 
 pub struct OperationBuilder<T: Attributes> {
-    ty: Operation<T>,
-    attrs: T,
+    operations: Vec<Operation<T>>,
 }
 
 impl<T> OperationBuilder<T>
 where
     T: Attributes,
 {
-    pub fn new(ty: Operation<T>) -> OperationBuilder<T> {
-        OperationBuilder {
-            ty,
-            attrs: T::default(),
-        }
+    pub fn new() -> OperationBuilder<T> {
+        OperationBuilder { operations: vec![] }
     }
 
-    pub fn retain(n: usize) -> OperationBuilder<T> {
-        OperationBuilder::new(Operation::Retain(n.into()))
+    pub fn retain(mut self, n: usize) -> OperationBuilder<T> {
+        let mut retain = Operation::Retain(n.into());
+
+        if let Some(attributes) = attributes {
+            if let Operation::Retain(r) = &mut retain {
+                r.attributes = attributes;
+            }
+        }
+        self.operations.push(retain);
+        self
     }
 
-    pub fn delete(n: usize) -> OperationBuilder<T> {
-        OperationBuilder::new(Operation::Delete(n))
+    pub fn delete(mut self, n: usize) -> OperationBuilder<T> {
+        self.operations.push(Operation::Delete(n));
+        self
     }
 
-    pub fn insert(s: &str) -> OperationBuilder<T> {
-        OperationBuilder::new(Operation::Insert(s.into()))
+    pub fn insert(mut self, s: &str, attributes: Option<T>) -> OperationBuilder<T> {
+        let mut insert = Operation::Insert(s.into());
+        if let Some(attributes) = attributes {
+            if let Operation::Retain(i) = &mut insert {
+                i.attributes = attributes;
+            }
+        }
+        self.operations.push(insert);
+        self
     }
 
     pub fn attributes(mut self, attrs: T) -> OperationBuilder<T> {
@@ -37,13 +49,7 @@ where
         self
     }
 
-    pub fn build(self) -> Operation<T> {
-        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
+    pub fn build(self) -> Vec<Operation<T>> {
+        self.operations
     }
 }

+ 6 - 6
shared-lib/lib-ot/src/core/operation/operation.rs

@@ -11,7 +11,7 @@ use std::{
     ops::{Deref, DerefMut},
 };
 
-pub trait OperationTransformable {
+pub trait OperationTransform {
     /// Merges the operation with `other` into one operation while preserving
     /// the changes of both.    
     ///
@@ -22,7 +22,7 @@ pub trait OperationTransformable {
     /// # Examples
     ///
     /// ```
-    ///  use lib_ot::core::{OperationTransformable, PlainTextDeltaBuilder};
+    ///  use lib_ot::core::{OperationTransform, PlainTextDeltaBuilder};
     ///  let document = PlainTextDeltaBuilder::new().build();
     ///  let delta = PlainTextDeltaBuilder::new().insert("abc").build();
     ///  let new_document = document.compose(&delta).unwrap();
@@ -51,7 +51,7 @@ pub trait OperationTransformable {
     /// # Examples
     ///
     /// ```
-    /// use lib_ot::core::{OperationTransformable, PlainTextDeltaBuilder};
+    /// use lib_ot::core::{OperationTransform, PlainTextDeltaBuilder};
     /// let original_document = PlainTextDeltaBuilder::new().build();
     /// let delta = PlainTextDeltaBuilder::new().insert("abc").build();
     ///
@@ -71,7 +71,7 @@ pub trait OperationTransformable {
 /// Because [Operation] is generic over the T, so you must specify the T. For example, the [PlainTextDelta]. It use
 /// use [PhantomAttributes] as the T. [PhantomAttributes] does nothing, just a phantom.
 ///
-pub trait Attributes: Default + Display + Eq + PartialEq + Clone + Debug + OperationTransformable {
+pub trait Attributes: Default + Display + Eq + PartialEq + Clone + Debug + OperationTransform {
     fn is_empty(&self) -> bool {
         true
     }
@@ -360,7 +360,7 @@ where
     T: Attributes,
 {
     pub fn utf16_size(&self) -> usize {
-        self.s.utf16_size()
+        self.s.utf16_len()
     }
 
     pub fn merge_or_new_op(&mut self, s: &str, attributes: T) -> Option<Operation<T>> {
@@ -420,7 +420,7 @@ impl fmt::Display for PhantomAttributes {
 
 impl Attributes for PhantomAttributes {}
 
-impl OperationTransformable for PhantomAttributes {
+impl OperationTransform for PhantomAttributes {
     fn compose(&self, _other: &Self) -> Result<Self, OTError> {
         Ok(self.clone())
     }

+ 2 - 2
shared-lib/lib-ot/src/rich_text/attributes.rs

@@ -1,5 +1,5 @@
 #![allow(non_snake_case)]
-use crate::core::{Attributes, Operation, OperationTransformable};
+use crate::core::{Attributes, Operation, OperationTransform};
 use crate::{block_attribute, errors::OTError, ignore_attribute, inline_attribute, list_attribute};
 use lazy_static::lazy_static;
 use std::{
@@ -122,7 +122,7 @@ impl Attributes for RichTextAttributes {
     }
 }
 
-impl OperationTransformable for RichTextAttributes {
+impl OperationTransform for RichTextAttributes {
     fn compose(&self, other: &Self) -> Result<Self, OTError>
     where
         Self: Sized,