Browse Source

remove attributes type follow

appflowy 3 years ago
parent
commit
e4a64fd06a

+ 21 - 68
rust-lib/flowy-ot/src/client/document.rs

@@ -34,40 +34,22 @@ impl Document {
                 self.delta.target_len
             );
         }
-        let probe = Interval::new(index, index + 1);
-        let mut attributes = self.delta.get_attributes(probe);
-        if attributes.is_empty() {
-            attributes = Attributes::Follow;
-        }
-
-        // let delta = self.view.handle_insert(&self.delta, s, interval);
 
-        let mut delta = Delta::new();
-        let insert = Builder::insert(text).attributes(attributes).build();
+        let delta = self.view.handle_insert(&self.delta, text, index);
         let interval = Interval::new(index, index);
-        delta.add(insert);
-
         self.update_with_op(&delta, interval)
     }
 
-    pub fn format(
-        &mut self,
-        interval: Interval,
-        attribute: Attribute,
-        enable: bool,
-    ) -> Result<(), OTError> {
-        let attributes = match enable {
-            true => AttrsBuilder::new().add(attribute).build(),
-            false => AttrsBuilder::new().remove(&attribute).build(),
-        };
-        log::debug!("format with {} at {}", attributes, interval);
-        self.update_with_attribute(attributes, interval)
+    pub fn format(&mut self, interval: Interval, attribute: Attribute) -> Result<(), OTError> {
+        log::debug!("format with {} at {}", attribute, interval);
+
+        self.update_with_attribute(attribute, interval)
     }
 
     pub fn replace(&mut self, interval: Interval, s: &str) -> Result<(), OTError> {
         let mut delta = Delta::default();
         if !s.is_empty() {
-            let insert = Builder::insert(s).attributes(Attributes::Follow).build();
+            let insert = Builder::insert(s).build();
             delta.add(insert);
         }
 
@@ -128,12 +110,10 @@ impl Document {
 
         // prefix
         if prefix.is_empty() == false && prefix != interval {
-            DeltaAttributesIter::from_interval(&self.delta, prefix).for_each(
-                |(length, attributes)| {
-                    log::debug!("prefix attribute: {:?}, len: {}", attributes, length);
-                    new_delta.retain(length, attributes);
-                },
-            );
+            AttributesIter::from_interval(&self.delta, prefix).for_each(|(length, attributes)| {
+                log::debug!("prefix attribute: {:?}, len: {}", attributes, length);
+                new_delta.retain(length, attributes);
+            });
         }
 
         delta.ops.iter().for_each(|op| {
@@ -142,12 +122,10 @@ impl Document {
 
         // suffix
         if suffix.is_empty() == false {
-            DeltaAttributesIter::from_interval(&self.delta, suffix).for_each(
-                |(length, attributes)| {
-                    log::debug!("suffix attribute: {:?}, len: {}", attributes, length);
-                    new_delta.retain(length, attributes);
-                },
-            );
+            AttributesIter::from_interval(&self.delta, suffix).for_each(|(length, attributes)| {
+                log::debug!("suffix attribute: {:?}, len: {}", attributes, length);
+                new_delta.retain(length, attributes);
+            });
         }
 
         self.delta = self.record_change(&new_delta)?;
@@ -156,22 +134,18 @@ impl Document {
 
     pub fn update_with_attribute(
         &mut self,
-        mut attributes: Attributes,
+        attribute: Attribute,
         interval: Interval,
     ) -> Result<(), OTError> {
-        log::debug!("Update document with attributes: {:?}", attributes,);
+        log::debug!("Update document with attribute: {}", attribute);
+        let mut attributes = AttrsBuilder::new().add(attribute).build();
         let old_attributes = self.delta.get_attributes(interval);
-        log::debug!("combine with old: {:?}", old_attributes);
-        let new_attributes = match &mut attributes {
-            Attributes::Follow => old_attributes,
-            Attributes::Custom(attr_data) => {
-                attr_data.merge(old_attributes.data());
-                log::debug!("combine with old result : {:?}", attr_data);
-                attr_data.clone().into()
-            },
-        };
 
+        log::debug!("combine with old: {:?}", old_attributes);
+        attributes.merge(Some(old_attributes));
+        let new_attributes = attributes;
         log::debug!("combine result: {:?}", new_attributes);
+
         let retain = Builder::retain(interval.size())
             .attributes(new_attributes)
             .build();
@@ -229,27 +203,6 @@ fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, I
     (prefix, interval, suffix)
 }
 
-fn split_interval_by_delta(delta: &Delta, interval: &Interval) -> Vec<Interval> {
-    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
-}
-
 pub fn trim(delta: &mut Delta) {
     let remove_last = match delta.ops.last() {
         None => false,

+ 2 - 3
rust-lib/flowy-ot/src/client/view/extension.rs

@@ -1,11 +1,10 @@
 use crate::{
-    client::{view::insert_ext::*, Document},
+    client::Document,
     core::{Attributes, Delta, Interval},
 };
-use lazy_static::lazy_static;
 
 pub trait InsertExt {
-    fn apply(&self, delta: &Delta, s: &str, interval: Interval) -> Delta;
+    fn apply(&self, delta: &Delta, s: &str, index: usize) -> Delta;
 }
 
 pub trait FormatExt {

+ 9 - 10
rust-lib/flowy-ot/src/client/view/insert_ext.rs

@@ -1,6 +1,6 @@
 use crate::{
-    client::{view::InsertExt, Document},
-    core::{Builder, Delta, Interval},
+    client::view::InsertExt,
+    core::{attributes_at_index, Attributes, AttributesIter, Builder, Delta, Interval},
 };
 
 pub struct PreserveInlineStyleExt {}
@@ -10,13 +10,12 @@ impl PreserveInlineStyleExt {
 }
 
 impl InsertExt for PreserveInlineStyleExt {
-    fn apply(&self, delta: &Delta, s: &str, interval: Interval) -> Delta {
-        // let mut delta = Delta::default();
-        // let insert = Builder::insert(text).attributes(attributes).build();
-        // let interval = Interval::new(index, index);
-        // delta.add(insert);
-        //
-        // delta
-        unimplemented!()
+    fn apply(&self, delta: &Delta, text: &str, index: usize) -> Delta {
+        let attributes = attributes_at_index(delta, index);
+        let mut delta = Delta::new();
+        let insert = Builder::insert(text).attributes(attributes).build();
+        delta.add(insert);
+
+        delta
     }
 }

+ 3 - 3
rust-lib/flowy-ot/src/client/view/view.rs

@@ -1,6 +1,6 @@
 use crate::{
     client::view::{InsertExt, PreserveInlineStyleExt},
-    core::{Delta, Interval},
+    core::Delta,
 };
 
 type InsertExtension = Box<dyn InsertExt>;
@@ -15,10 +15,10 @@ impl View {
         Self { insert_exts }
     }
 
-    pub(crate) fn handle_insert(&self, delta: &Delta, s: &str, interval: Interval) -> Delta {
+    pub(crate) fn handle_insert(&self, delta: &Delta, s: &str, index: usize) -> Delta {
         let mut new_delta = Delta::new();
         self.insert_exts.iter().for_each(|ext| {
-            new_delta = ext.apply(delta, s, interval);
+            new_delta = ext.apply(delta, s, index);
         });
         new_delta
     }

+ 78 - 84
rust-lib/flowy-ot/src/core/attributes/attributes.rs

@@ -1,58 +1,72 @@
-use crate::core::{AttributesData, Operation};
-use std::{fmt, fmt::Formatter};
+use crate::core::{Attribute, AttributeKey, Operation};
+use std::{collections::HashMap, fmt, fmt::Formatter};
 
-pub trait AttributesRule {
-    // Remove the empty attribute that its value is empty. e.g. {bold: ""}.
-    fn remove_empty(self) -> Attributes;
-}
-
-impl AttributesRule for Attributes {
-    fn remove_empty(self) -> Attributes {
-        match self {
-            Attributes::Follow => self,
-            Attributes::Custom(mut data) => {
-                data.remove_empty_value();
-                data.into()
-            },
-        }
-    }
-}
+pub const REMOVE_FLAG: &'static str = "";
+pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG }
 
-#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
-#[serde(untagged)]
-pub enum Attributes {
-    #[serde(skip)]
-    Follow,
-    Custom(AttributesData),
+#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
+pub struct Attributes {
+    #[serde(skip_serializing_if = "HashMap::is_empty")]
+    #[serde(flatten)]
+    pub(crate) inner: HashMap<AttributeKey, String>,
 }
 
 impl fmt::Display for Attributes {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        match self {
-            Attributes::Follow => f.write_str("follow"),
-            Attributes::Custom(data) => f.write_fmt(format_args!("{}", data)),
-        }
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_fmt(format_args!("{:?}", self.inner))
     }
 }
 
 impl Attributes {
-    pub fn data(&self) -> Option<AttributesData> {
-        match self {
-            Attributes::Follow => None,
-            Attributes::Custom(data) => Some(data.clone()),
+    pub fn new() -> Self {
+        Attributes {
+            inner: HashMap::new(),
         }
     }
 
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Attributes::Follow => false,
-            Attributes::Custom(data) => data.is_empty(),
+    pub fn is_empty(&self) -> bool { self.inner.is_empty() }
+
+    pub fn add(&mut self, attribute: Attribute) {
+        let Attribute {
+            key,
+            value,
+            scope: _,
+        } = attribute;
+        self.inner.insert(key, value);
+    }
+
+    pub fn remove(&mut self, key: &AttributeKey) {
+        self.inner.insert(key.clone(), REMOVE_FLAG.to_owned());
+    }
+
+    // Remove the key if its value is empty. e.g. { bold: "" }
+    pub fn remove_empty_value(&mut self) { self.inner.retain(|_, v| !should_remove(v)); }
+
+    pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); }
+
+    // Update self attributes by constructing new attributes from the other if it's
+    // not None and replace the key/value with self key/value.
+    pub fn merge(&mut self, other: Option<Attributes>) {
+        if other.is_none() {
+            return;
         }
+
+        let mut new_attributes = other.unwrap().inner;
+        self.inner.iter().for_each(|(k, v)| {
+            new_attributes.insert(k.clone(), v.clone());
+        });
+        self.inner = new_attributes;
     }
 }
 
-impl std::default::Default for Attributes {
-    fn default() -> Self { Attributes::Custom(AttributesData::new()) }
+impl std::ops::Deref for Attributes {
+    type Target = HashMap<AttributeKey, String>;
+
+    fn deref(&self) -> &Self::Target { &self.inner }
+}
+
+impl std::ops::DerefMut for Attributes {
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
 }
 
 pub(crate) fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
@@ -80,11 +94,7 @@ pub fn compose_operation(left: &Option<Operation>, right: &Option<Operation>) ->
     let left = attr_left.unwrap();
     let right = attr_right.unwrap();
     log::trace!("compose attributes: a: {:?}, b: {:?}", left, right);
-    let attr = match (&left, &right) {
-        (_, Attributes::Custom(_)) => merge_attributes(left, right),
-        (Attributes::Custom(_), _) => merge_attributes(left, right),
-        _ => Attributes::Follow,
-    };
+    let attr = merge_attributes(left, right);
     log::trace!("compose attributes result: {:?}", attr);
     attr
 }
@@ -98,45 +108,24 @@ pub fn transform_operation(left: &Option<Operation>, right: &Option<Operation>)
             return Attributes::default();
         }
 
-        return match attr_r.as_ref().unwrap() {
-            Attributes::Follow => Attributes::Follow,
-            Attributes::Custom(_) => attr_r.unwrap(),
-        };
+        return attr_r.unwrap();
     }
 
     let left = attr_l.unwrap();
     let right = attr_r.unwrap();
-    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::default(),
-    }
+    left.iter()
+        .fold(Attributes::new(), |mut new_attributes, (k, v)| {
+            if !right.contains_key(k) {
+                new_attributes.insert(k.clone(), v.clone());
+            }
+            new_attributes
+        })
 }
 
 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::default();
-    }
-
-    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)| {
+        .fold(Attributes::new(), |mut attributes, (k, v)| {
             if base.get(k) != attr.get(k) && attr.contains_key(k) {
                 attributes.insert(k.clone(), v.clone());
             }
@@ -150,17 +139,22 @@ pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes {
         attributes
     });
 
-    return Attributes::Custom(inverted);
+    return inverted;
+}
+
+pub fn merge_attributes(mut attributes: Attributes, other: Attributes) -> Attributes {
+    attributes.extend(other);
+    attributes
 }
 
-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(Some(o_data.clone()));
-            Attributes::Custom(data)
-        },
-        (Attributes::Custom(data), _) => Attributes::Custom(data.clone()),
-        _ => other,
+pub trait AttributesRule {
+    // Remove the empty attribute that its value is empty. e.g. {bold: ""}.
+    fn remove_empty(self) -> Attributes;
+}
+
+impl AttributesRule for Attributes {
+    fn remove_empty(mut self) -> Attributes {
+        self.remove_empty_value();
+        self.into()
     }
 }

+ 161 - 17
rust-lib/flowy-ot/src/core/attributes/builder.rs

@@ -1,23 +1,118 @@
-use crate::core::{Attributes, AttributesData};
+use crate::core::{Attributes, REMOVE_FLAG};
 use derive_more::Display;
+use std::{fmt, fmt::Formatter};
 
 #[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
 #[serde(rename_all = "camelCase")]
-pub enum Attribute {
+pub enum AttributeKey {
     #[display(fmt = "bold")]
     Bold,
     #[display(fmt = "italic")]
     Italic,
+    #[display(fmt = "underline")]
+    Underline,
+    #[display(fmt = "strike_through")]
+    StrikeThrough,
+    #[display(fmt = "font")]
+    Font,
+    #[display(fmt = "size")]
+    Size,
+    #[display(fmt = "link")]
+    Link,
+    #[display(fmt = "color")]
+    Color,
+    #[display(fmt = "background")]
+    Background,
+    #[display(fmt = "header")]
+    Header,
+    #[display(fmt = "ident")]
+    Ident,
+    #[display(fmt = "align")]
+    Align,
+    #[display(fmt = "code_block")]
+    CodeBlock,
+    #[display(fmt = "list")]
+    List,
+    #[display(fmt = "quote_block")]
+    QuoteBlock,
+    #[display(fmt = "width")]
+    Width,
+    #[display(fmt = "height")]
+    Height,
+    #[display(fmt = "style")]
+    Style,
+    #[display(fmt = "h1")]
+    H1,
+    #[display(fmt = "h2")]
+    H2,
+    #[display(fmt = "h3")]
+    H3,
+    #[display(fmt = "h4")]
+    H4,
+    #[display(fmt = "h5")]
+    H5,
+    #[display(fmt = "h6")]
+    H6,
+    #[display(fmt = "left")]
+    LeftAlignment,
+    #[display(fmt = "center")]
+    CenterAlignment,
+    #[display(fmt = "right")]
+    RightAlignment,
+    #[display(fmt = "justify")]
+    JustifyAlignment,
+    #[display(fmt = "bullet")]
+    Bullet,
+    #[display(fmt = "ordered")]
+    Ordered,
+    #[display(fmt = "checked")]
+    Checked,
+    #[display(fmt = "unchecked")]
+    UnChecked,
+}
+
+#[derive(Debug)]
+pub enum AttributeScope {
+    Inline,
+    Block,
+    Embeds,
+    Ignore,
+}
+
+#[derive(Debug)]
+pub struct Attribute {
+    pub key: AttributeKey,
+    pub value: String,
+    pub scope: AttributeScope,
+}
+
+impl fmt::Display for Attribute {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let s = format!("{:?}:{} {:?}", self.key, self.value, self.scope);
+        f.write_str(&s)
+    }
 }
 
 pub struct AttrsBuilder {
-    inner: AttributesData,
+    inner: Attributes,
+}
+
+macro_rules! impl_bool_attribute {
+    ($name: ident,$key: expr) => {
+        pub fn $name(self, value: bool) -> Self {
+            let value = match value {
+                true => "true",
+                false => REMOVE_FLAG,
+            };
+            self.insert($key, value)
+        }
+    };
 }
 
 impl AttrsBuilder {
     pub fn new() -> Self {
         Self {
-            inner: AttributesData::default(),
+            inner: Attributes::default(),
         }
     }
 
@@ -26,24 +121,73 @@ impl AttrsBuilder {
         self
     }
 
-    pub fn remove(mut self, attribute: &Attribute) -> Self {
-        self.inner.remove(attribute);
+    pub fn insert<T: Into<String>>(mut self, key: AttributeKey, value: T) -> Self {
+        self.inner.add(key.with_value(value));
         self
     }
 
-    pub fn bold(self, bold: bool) -> Self {
-        match bold {
-            true => self.add(Attribute::Bold),
-            false => self.remove(&Attribute::Bold),
-        }
+    pub fn remove<T: Into<String>>(mut self, key: AttributeKey) -> Self {
+        self.inner.add(key.with_value(REMOVE_FLAG));
+        self
     }
 
-    pub fn italic(self, italic: bool) -> Self {
-        match italic {
-            true => self.add(Attribute::Italic),
-            false => self.remove(&Attribute::Italic),
+    impl_bool_attribute!(bold, AttributeKey::Bold);
+    impl_bool_attribute!(italic, AttributeKey::Italic);
+    impl_bool_attribute!(underline, AttributeKey::Underline);
+    impl_bool_attribute!(strike_through, AttributeKey::StrikeThrough);
+
+    pub fn build(self) -> Attributes { self.inner }
+}
+
+impl AttributeKey {
+    pub fn with_value<T: Into<String>>(&self, value: T) -> Attribute {
+        let key = self.clone();
+        let value: String = value.into();
+        match self {
+            AttributeKey::Bold
+            | AttributeKey::Italic
+            | AttributeKey::Underline
+            | AttributeKey::StrikeThrough
+            | AttributeKey::Link
+            | AttributeKey::Color
+            | AttributeKey::Background
+            | AttributeKey::Font
+            | AttributeKey::Size => Attribute {
+                key,
+                value,
+                scope: AttributeScope::Inline,
+            },
+
+            AttributeKey::Header
+            | AttributeKey::H1
+            | AttributeKey::H2
+            | AttributeKey::H3
+            | AttributeKey::H4
+            | AttributeKey::H5
+            | AttributeKey::H6
+            | AttributeKey::LeftAlignment
+            | AttributeKey::CenterAlignment
+            | AttributeKey::RightAlignment
+            | AttributeKey::JustifyAlignment
+            | AttributeKey::Ident
+            | AttributeKey::Align
+            | AttributeKey::CodeBlock
+            | AttributeKey::List
+            | AttributeKey::Bullet
+            | AttributeKey::Ordered
+            | AttributeKey::Checked
+            | AttributeKey::UnChecked
+            | AttributeKey::QuoteBlock => Attribute {
+                key,
+                value,
+                scope: AttributeScope::Block,
+            },
+
+            AttributeKey::Width | AttributeKey::Height | AttributeKey::Style => Attribute {
+                key,
+                value,
+                scope: AttributeScope::Ignore,
+            },
         }
     }
-
-    pub fn build(self) -> Attributes { Attributes::Custom(self.inner) }
 }

+ 0 - 72
rust-lib/flowy-ot/src/core/attributes/data.rs

@@ -1,72 +0,0 @@
-use crate::core::{Attribute, Attributes};
-use std::{collections::HashMap, fmt};
-
-pub(crate) const REMOVE_FLAG: &'static str = "";
-pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG }
-
-#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
-pub struct AttributesData {
-    #[serde(skip_serializing_if = "HashMap::is_empty")]
-    #[serde(flatten)]
-    pub(crate) inner: HashMap<Attribute, String>,
-}
-
-impl fmt::Display for AttributesData {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_fmt(format_args!("{:?}", self.inner))
-    }
-}
-
-impl AttributesData {
-    pub fn new() -> Self {
-        AttributesData {
-            inner: HashMap::new(),
-        }
-    }
-
-    pub fn is_empty(&self) -> bool { self.inner.is_empty() }
-
-    pub fn add(&mut self, attribute: Attribute) { self.inner.insert(attribute, "true".to_owned()); }
-
-    pub fn remove(&mut self, attribute: &Attribute) {
-        self.inner.insert(attribute.clone(), REMOVE_FLAG.to_owned());
-    }
-
-    // Remove the key if its value is empty. e.g. { bold: "" }
-    pub fn remove_empty_value(&mut self) { self.inner.retain(|_, v| !should_remove(v)); }
-
-    pub fn extend(&mut self, other: Option<AttributesData>) {
-        if other.is_none() {
-            return;
-        }
-        self.inner.extend(other.unwrap().inner);
-    }
-
-    // Update self attributes by constructing new attributes from the other if it's
-    // not None and replace the key/value with self key/value.
-    pub fn merge(&mut self, other: Option<AttributesData>) {
-        if other.is_none() {
-            return;
-        }
-
-        let mut new_attributes = other.unwrap().inner;
-        self.inner.iter().for_each(|(k, v)| {
-            new_attributes.insert(k.clone(), v.clone());
-        });
-        self.inner = new_attributes;
-    }
-}
-
-impl std::ops::Deref for AttributesData {
-    type Target = HashMap<Attribute, String>;
-
-    fn deref(&self) -> &Self::Target { &self.inner }
-}
-
-impl std::ops::DerefMut for AttributesData {
-    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
-}
-
-impl std::convert::Into<Attributes> for AttributesData {
-    fn into(self) -> Attributes { Attributes::Custom(self) }
-}

+ 0 - 2
rust-lib/flowy-ot/src/core/attributes/mod.rs

@@ -1,7 +1,5 @@
 mod attributes;
 mod builder;
-mod data;
 
 pub use attributes::*;
 pub use builder::*;
-pub use data::*;

+ 81 - 19
rust-lib/flowy-ot/src/core/delta/cursor.rs

@@ -1,28 +1,41 @@
-use crate::core::{Attributes, AttributesData, Delta, Interval, Operation};
-use std::{cmp::min, slice::Iter};
+use crate::{
+    core::{Attributes, Delta, Interval, Operation},
+    errors::{ErrorBuilder, OTError, OTErrorCode},
+};
+use std::{
+    cmp::min,
+    ops::{Deref, DerefMut},
+    slice::Iter,
+};
 
 pub struct Cursor<'a> {
     delta: &'a Delta,
     interval: Interval,
     iterator: Iter<'a, Operation>,
     offset: usize,
+    offset_op: Option<&'a Operation>,
 }
 
 impl<'a> Cursor<'a> {
     pub fn new(delta: &'a Delta, interval: Interval) -> Cursor<'a> {
-        let mut cursor = Self {
+        let cursor = Self {
             delta,
             interval,
             iterator: delta.ops.iter(),
             offset: 0,
+            offset_op: None,
         };
         cursor
     }
 
     pub fn next_op(&mut self) -> Option<Operation> {
-        let mut next_op = self.iterator.next();
-        let mut find_op = None;
+        let mut next_op = self.offset_op.take();
+
+        if next_op.is_none() {
+            next_op = self.iterator.next();
+        }
 
+        let mut find_op = None;
         while find_op.is_none() && next_op.is_some() {
             let op = next_op.unwrap();
             if self.offset < self.interval.start {
@@ -69,6 +82,34 @@ impl<'a> Cursor<'a> {
 
         find_op
     }
+
+    pub fn seek_to(&mut self, index: usize) -> Result<(), OTError> {
+        if self.offset > index {
+            let msg = format!(
+                "{} should be greater than current offset: {}",
+                index, self.offset
+            );
+            return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength)
+                .msg(&msg)
+                .build());
+        }
+
+        let mut offset = 0;
+        while let Some(op) = self.iterator.next() {
+            if offset != 0 {
+                self.offset = offset;
+            }
+
+            offset += op.length();
+            self.offset_op = Some(op);
+
+            if offset >= index {
+                break;
+            }
+        }
+
+        Ok(())
+    }
 }
 
 pub struct DeltaIter<'a> {
@@ -88,6 +129,11 @@ impl<'a> DeltaIter<'a> {
     }
 
     pub fn ops(&mut self) -> Vec<Operation> { self.collect::<Vec<_>>() }
+
+    pub fn seek_to(&mut self, n_char: usize) -> Result<(), OTError> {
+        let _ = self.cursor.seek_to(n_char)?;
+        Ok(())
+    }
 }
 
 impl<'a> Iterator for DeltaIter<'a> {
@@ -95,12 +141,12 @@ impl<'a> Iterator for DeltaIter<'a> {
     fn next(&mut self) -> Option<Self::Item> { self.cursor.next_op() }
 }
 
-pub struct DeltaAttributesIter<'a> {
+pub struct AttributesIter<'a> {
     delta_iter: DeltaIter<'a>,
     interval: Interval,
 }
 
-impl<'a> DeltaAttributesIter<'a> {
+impl<'a> AttributesIter<'a> {
     pub fn new(delta: &'a Delta) -> Self {
         let interval = Interval::new(0, usize::MAX);
         Self::from_interval(delta, interval)
@@ -115,7 +161,17 @@ impl<'a> DeltaAttributesIter<'a> {
     }
 }
 
-impl<'a> Iterator for DeltaAttributesIter<'a> {
+impl<'a> Deref for AttributesIter<'a> {
+    type Target = DeltaIter<'a>;
+
+    fn deref(&self) -> &Self::Target { &self.delta_iter }
+}
+
+impl<'a> DerefMut for AttributesIter<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.delta_iter }
+}
+
+impl<'a> Iterator for AttributesIter<'a> {
     type Item = (usize, Attributes);
     fn next(&mut self) -> Option<Self::Item> {
         let next_op = self.delta_iter.next();
@@ -123,28 +179,34 @@ impl<'a> Iterator for DeltaAttributesIter<'a> {
             return None;
         }
         let mut length: usize = 0;
-        let mut attributes_data = AttributesData::new();
+        let mut attributes = Attributes::new();
 
         match next_op.unwrap() {
             Operation::Delete(_n) => {},
             Operation::Retain(retain) => {
-                if let Attributes::Custom(data) = &retain.attributes {
-                    log::debug!("extend retain attributes with {} ", &data);
-                    attributes_data.extend(Some(data.clone()));
-                }
+                log::debug!("extend retain attributes with {} ", &retain.attributes);
+                attributes.extend(retain.attributes.clone());
+
                 length = retain.n;
             },
             Operation::Insert(insert) => {
-                if let Attributes::Custom(data) = &insert.attributes {
-                    log::debug!("extend insert attributes with {} ", &data);
-                    attributes_data.extend(Some(data.clone()));
-                }
+                log::debug!("extend insert attributes with {} ", &insert.attributes);
+                attributes.extend(insert.attributes.clone());
                 length = insert.num_chars();
             },
         }
 
-        let attribute: Attributes = attributes_data.into();
-        Some((length, attribute))
+        Some((length, attributes))
+    }
+}
+
+pub(crate) fn attributes_at_index(delta: &Delta, index: usize) -> Attributes {
+    let mut iter = AttributesIter::new(delta);
+    iter.seek_to(index);
+    match iter.next() {
+        // None => Attributes::Follow,
+        None => Attributes::new(),
+        Some((_, attributes)) => attributes,
     }
 }
 

+ 12 - 21
rust-lib/flowy-ot/src/core/delta/delta.rs

@@ -3,12 +3,7 @@ use crate::{
     errors::{ErrorBuilder, OTError, OTErrorCode},
 };
 use bytecount::num_chars;
-use std::{
-    cmp::{min, Ordering},
-    fmt,
-    iter::FromIterator,
-    str::FromStr,
-};
+use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr};
 
 #[derive(Clone, Debug, PartialEq)]
 pub struct Delta {
@@ -454,7 +449,7 @@ impl Delta {
         for op in &self.ops {
             match &op {
                 Operation::Retain(retain) => {
-                    inverted.retain(retain.n, Attributes::Follow);
+                    inverted.retain(retain.n, Attributes::default());
 
                     // TODO: use advance_by instead, but it's unstable now
                     // chars.advance_by(retain.num)
@@ -531,35 +526,31 @@ impl Delta {
     pub fn is_empty(&self) -> bool { self.ops.is_empty() }
 
     pub fn get_attributes(&self, interval: Interval) -> Attributes {
-        let mut attributes_data = AttributesData::new();
+        let mut attributes = Attributes::new();
         let mut offset: usize = 0;
         log::debug!("Get attributes at {:?}", interval);
         self.ops.iter().for_each(|op| match op {
             Operation::Delete(_n) => {},
             Operation::Retain(retain) => {
-                if let Attributes::Custom(data) = &retain.attributes {
-                    if interval.contains_range(offset, offset + retain.n) {
-                        log::debug!("extend retain attributes with {} ", &data);
-                        attributes_data.extend(Some(data.clone()));
-                    }
+                if interval.contains_range(offset, offset + retain.n) {
+                    log::debug!("extend retain attributes with {} ", &retain.attributes);
+                    attributes.extend(retain.attributes.clone());
                 }
+
                 offset += retain.n;
             },
             Operation::Insert(insert) => {
                 let end = insert.num_chars() as usize;
-                if let Attributes::Custom(data) = &insert.attributes {
-                    if interval.contains_range(offset, offset + end) {
-                        log::debug!("extend insert attributes with {} ", &data);
-                        attributes_data.extend(Some(data.clone()));
-                    }
+                if interval.contains_range(offset, offset + end) {
+                    log::debug!("extend insert attributes with {} ", &insert.attributes);
+                    attributes.extend(insert.attributes.clone());
                 }
                 offset += end;
             },
         });
 
-        let attribute: Attributes = attributes_data.into();
-        log::debug!("Get attributes result: {} ", &attribute);
-        attribute
+        log::debug!("Get attributes result: {} ", &attributes);
+        attributes
     }
 
     pub fn to_json(&self) -> String { serde_json::to_string(self).unwrap_or("".to_owned()) }

+ 13 - 45
rust-lib/flowy-ot/src/core/operation/operation.rs

@@ -52,12 +52,7 @@ impl Operation {
         }
     }
 
-    pub fn has_attribute(&self) -> bool {
-        match self.get_attributes() {
-            Attributes::Follow => false,
-            Attributes::Custom(data) => !data.is_empty(),
-        }
-    }
+    pub fn has_attribute(&self) -> bool { !self.get_attributes().is_empty() }
 
     pub fn length(&self) -> usize {
         match self {
@@ -139,29 +134,15 @@ impl Retain {
             attributes
         );
 
-        match &attributes {
-            Attributes::Follow => {
-                log::debug!("Follow attribute: {:?}", self.attributes);
-                self.n += n;
-                None
-            },
-            Attributes::Custom(_) => {
-                if self.attributes == attributes {
-                    self.n += n;
-                    None
-                } else {
-                    Some(Builder::retain(n).attributes(attributes).build())
-                }
-            },
+        if self.attributes == attributes {
+            self.n += n;
+            None
+        } else {
+            Some(Builder::retain(n).attributes(attributes).build())
         }
     }
 
-    pub fn is_plain(&self) -> bool {
-        match &self.attributes {
-            Attributes::Follow => true,
-            Attributes::Custom(data) => data.is_empty(),
-        }
-    }
+    pub fn is_plain(&self) -> bool { self.attributes.is_empty() }
 }
 
 impl std::convert::From<usize> for Retain {
@@ -217,19 +198,11 @@ impl Insert {
     pub fn num_chars(&self) -> usize { num_chars(self.s.as_bytes()) as _ }
 
     pub fn merge_or_new_op(&mut self, s: &str, attributes: Attributes) -> Option<Operation> {
-        match &attributes {
-            Attributes::Follow => {
-                self.s += s;
-                return None;
-            },
-            Attributes::Custom(_) => {
-                if self.attributes == attributes {
-                    self.s += s;
-                    None
-                } else {
-                    Some(Builder::insert(s).attributes(attributes).build())
-                }
-            },
+        if self.attributes == attributes {
+            self.s += s;
+            None
+        } else {
+            Some(Builder::insert(s).attributes(attributes).build())
         }
     }
 }
@@ -247,9 +220,4 @@ impl std::convert::From<&str> for Insert {
     fn from(s: &str) -> Self { Insert::from(s.to_owned()) }
 }
 
-fn is_empty(attributes: &Attributes) -> bool {
-    match attributes {
-        Attributes::Follow => true,
-        Attributes::Custom(data) => data.is_empty(),
-    }
-}
+fn is_empty(attributes: &Attributes) -> bool { attributes.is_empty() }

+ 1 - 1
rust-lib/flowy-ot/tests/attribute_test.rs

@@ -337,7 +337,7 @@ fn delta_compose_attr_delta_with_attr_delta_test2() {
 
 #[test]
 fn delta_compose_attr_delta_with_no_attr_delta_test() {
-    let expected = r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#;
+    let expected = r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"7"}]"#;
 
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),

+ 18 - 9
rust-lib/flowy-ot/tests/helper/mod.rs

@@ -1,5 +1,8 @@
 use derive_more::Display;
-use flowy_ot::{client::Document, core::*};
+use flowy_ot::{
+    client::Document,
+    core::{REMOVE_FLAG, *},
+};
 use rand::{prelude::*, Rng as WrappedRng};
 use std::{sync::Once, time::Duration};
 
@@ -86,19 +89,25 @@ impl OpTester {
             TestOp::InsertBold(delta_i, s, interval) => {
                 let document = &mut self.documents[*delta_i];
                 document.insert(interval.start, s).unwrap();
-                document.format(*interval, Attribute::Bold, true).unwrap();
+                document
+                    .format(*interval, AttributeKey::Bold.with_value("true".to_owned()))
+                    .unwrap();
             },
             TestOp::Bold(delta_i, interval, enable) => {
                 let document = &mut self.documents[*delta_i];
-                document
-                    .format(*interval, Attribute::Bold, *enable)
-                    .unwrap();
+                let attribute = match *enable {
+                    true => AttributeKey::Bold.with_value("true".to_owned()),
+                    false => AttributeKey::Bold.with_value("".to_owned()),
+                };
+                document.format(*interval, attribute).unwrap();
             },
             TestOp::Italic(delta_i, interval, enable) => {
                 let document = &mut self.documents[*delta_i];
-                document
-                    .format(*interval, Attribute::Italic, *enable)
-                    .unwrap();
+                let attribute = match *enable {
+                    true => AttributeKey::Italic.with_value("true"),
+                    false => AttributeKey::Italic.with_value(REMOVE_FLAG),
+                };
+                document.format(*interval, attribute).unwrap();
             },
             TestOp::Transform(delta_a_i, delta_b_i) => {
                 let (a_prime, b_prime) = self.documents[*delta_a_i]
@@ -207,7 +216,7 @@ impl Rng {
                     delta.delete(i);
                 },
                 _ => {
-                    delta.retain(i, Attributes::Follow);
+                    delta.retain(i, Attributes::default());
                 },
             }
         }