diff --git a/rust-lib/flowy-ot/src/client/mod.rs b/rust-lib/flowy-ot/src/client/mod.rs index e69de29bb2..0f154e5d72 100644 --- a/rust-lib/flowy-ot/src/client/mod.rs +++ b/rust-lib/flowy-ot/src/client/mod.rs @@ -0,0 +1,5 @@ +mod document; +mod history; + +pub use document::*; +pub use history::*; diff --git a/rust-lib/flowy-ot/src/core/attributes.rs b/rust-lib/flowy-ot/src/core/attributes.rs deleted file mode 100644 index 60a4a665f8..0000000000 --- a/rust-lib/flowy-ot/src/core/attributes.rs +++ /dev/null @@ -1,295 +0,0 @@ -use crate::core::Operation; -use std::{collections::HashMap, fmt}; - -const REMOVE_FLAG: &'static str = ""; -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), - #[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 struct AttrsBuilder { - inner: AttributesData, -} - -impl AttrsBuilder { - pub fn new() -> Self { - Self { - inner: AttributesData::default(), - } - } - - pub fn bold(mut self, bold: bool) -> Self { - let val = match bold { - true => "true", - false => REMOVE_FLAG, - }; - self.inner.insert("bold".to_owned(), val.to_owned()); - self - } - - pub fn italic(mut self, italic: bool) -> Self { - let val = match italic { - true => "true", - false => REMOVE_FLAG, - }; - self.inner.insert("italic".to_owned(), val.to_owned()); - self - } - - pub fn underline(mut self) -> Self { - self.inner.insert("underline".to_owned(), "true".to_owned()); - self - } - - pub fn build(self) -> Attributes { Attributes::Custom(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.insert(k.clone(), "".to_owned()); - 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/delta.rs b/rust-lib/flowy-ot/src/core/delta.rs index bf88953d8b..e751e120ac 100644 --- a/rust-lib/flowy-ot/src/core/delta.rs +++ b/rust-lib/flowy-ot/src/core/delta.rs @@ -57,6 +57,8 @@ impl FromIterator for Delta { } impl Delta { + pub fn new() -> Self { Self::default() } + #[inline] pub fn with_capacity(capacity: usize) -> Self { Self { @@ -562,7 +564,7 @@ impl Delta { ops } - pub fn attributes_in_interval(&self, interval: Interval) -> Attributes { + pub fn get_attributes(&self, interval: Interval) -> Attributes { let mut attributes_data = AttributesData::new(); let mut offset: usize = 0; diff --git a/rust-lib/flowy-ot/src/core/mod.rs b/rust-lib/flowy-ot/src/core/mod.rs index 58b6c0bec0..de6c5ec44c 100644 --- a/rust-lib/flowy-ot/src/core/mod.rs +++ b/rust-lib/flowy-ot/src/core/mod.rs @@ -2,7 +2,6 @@ mod attributes; mod delta; mod interval; mod operation; -mod operation_serde; pub use attributes::*; pub use delta::*; diff --git a/rust-lib/flowy-ot/src/core/operation.rs b/rust-lib/flowy-ot/src/core/operation.rs deleted file mode 100644 index a1719320ea..0000000000 --- a/rust-lib/flowy-ot/src/core/operation.rs +++ /dev/null @@ -1,232 +0,0 @@ -use crate::core::Attributes; -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(()) - } -} - -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 - } -} - -#[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_serde.rs b/rust-lib/flowy-ot/src/core/operation_serde.rs deleted file mode 100644 index 4d3ab82688..0000000000 --- a/rust-lib/flowy-ot/src/core/operation_serde.rs +++ /dev/null @@ -1,137 +0,0 @@ -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 aa4a4cc68a..e0634d2e6c 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -26,8 +26,8 @@ pub enum TestOp { Transform(usize, usize), // invert the delta_a base on the delta_b - #[display(fmt = "Invert")] - Invert(usize, usize), + #[display(fmt = "Undo")] + Undo(usize, usize), #[display(fmt = "AssertStr")] AssertStr(usize, &'static str), @@ -91,7 +91,7 @@ impl OpTester { self.deltas[*delta_a_i] = new_delta_a; self.deltas[*delta_b_i] = new_delta_b; }, - TestOp::Invert(delta_a_i, delta_b_i) => { + TestOp::Undo(delta_a_i, delta_b_i) => { let delta_a = &self.deltas[*delta_a_i]; let delta_b = &self.deltas[*delta_b_i]; log::debug!("Invert: "); @@ -149,7 +149,7 @@ impl OpTester { log::error!("{} out of bounds {}", index, target_interval); } - let mut attributes = old_delta.attributes_in_interval(Interval::new(index, index + 1)); + let mut attributes = old_delta.get_attributes(Interval::new(index, index + 1)); if attributes == Attributes::Empty { attributes = Attributes::Follow; } @@ -165,7 +165,7 @@ impl OpTester { interval: &Interval, ) { let old_delta = &self.deltas[delta_index]; - let old_attributes = old_delta.attributes_in_interval(*interval); + let old_attributes = old_delta.get_attributes(*interval); log::debug!( "merge attributes: {:?}, with old: {:?}", attributes, @@ -211,7 +211,7 @@ fn new_delta_with_op(delta: &Delta, op: Operation, interval: Interval) -> Delta 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.attributes_in_interval(p_interval); + let attributes = delta.get_attributes(p_interval); log::debug!( "prefix attribute: {:?}, interval: {:?}", attributes, @@ -228,7 +228,7 @@ fn new_delta_with_op(delta: &Delta, op: Operation, interval: Interval) -> Delta if suffix.is_empty() == false { let intervals = split_interval_with_delta(delta, &suffix); intervals.into_iter().for_each(|s_interval| { - let attributes = delta.attributes_in_interval(s_interval); + let attributes = delta.get_attributes(s_interval); log::debug!( "suffix attribute: {:?}, interval: {:?}", attributes, @@ -241,27 +241,6 @@ fn new_delta_with_op(delta: &Delta, op: Operation, interval: Interval) -> Delta delta.compose(&new_delta).unwrap() } -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 -} - pub fn target_length_split_with_interval( length: usize, interval: Interval, diff --git a/rust-lib/flowy-ot/tests/invert_test.rs b/rust-lib/flowy-ot/tests/invert_test.rs index 0f739561c4..c92d79d5b1 100644 --- a/rust-lib/flowy-ot/tests/invert_test.rs +++ b/rust-lib/flowy-ot/tests/invert_test.rs @@ -3,7 +3,7 @@ use crate::helper::{TestOp::*, *}; use flowy_ot::core::{Delta, Interval, OpBuilder}; #[test] -fn delta_invert_delta_test() { +fn delta_invert_no_attribute_delta() { let mut delta = Delta::default(); delta.add(OpBuilder::insert("123").build()); @@ -19,31 +19,31 @@ fn delta_invert_delta_test() { } #[test] -fn delta_invert_delta_test2() { +fn delta_invert_no_attribute_delta2() { let ops = vec![ Insert(0, "123", 0), Insert(1, "4567", 0), - Invert(0, 1), + Undo(0, 1), AssertOpsJson(0, r#"[{"insert":"123"}]"#), ]; OpTester::new().run_script(ops); } #[test] -fn delta_invert_delta_with_attribute() { +fn delta_invert_attribute_delta_with_no_attribute_delta() { let ops = vec![ Insert(0, "123", 0), Bold(0, Interval::new(0, 3), true), AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#), Insert(1, "4567", 0), - Invert(0, 1), + Undo(0, 1), AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#), ]; OpTester::new().run_script(ops); } #[test] -fn delta_invert_delta() { +fn delta_invert_attribute_delta_with_no_attribute_delta2() { let ops = vec![ Insert(0, "123", 0), Bold(0, Interval::new(0, 3), true), @@ -55,7 +55,71 @@ fn delta_invert_delta() { r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#, ), Insert(1, "abc", 0), - Invert(0, 1), + Undo(0, 1), + AssertOpsJson( + 0, + r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#, + ), + ]; + OpTester::new().run_script(ops); +} + +#[test] +fn delta_invert_no_attribute_delta_with_attribute_delta() { + let ops = vec![ + Insert(0, "123", 0), + Insert(1, "4567", 0), + Bold(1, Interval::new(0, 3), true), + AssertOpsJson( + 1, + r#"[{"insert":"456","attributes":{"bold":"true"}},{"insert":"7"}]"#, + ), + Undo(0, 1), + AssertOpsJson(0, r#"[{"insert":"123"}]"#), + ]; + OpTester::new().run_script(ops); +} + +#[test] +fn delta_invert_no_attribute_delta_with_attribute_delta2() { + let ops = vec![ + Insert(0, "123", 0), + AssertOpsJson(0, r#"[{"insert":"123"}]"#), + Insert(1, "abc", 0), + Bold(1, Interval::new(0, 3), true), + Insert(1, "d", 3), + Italic(1, Interval::new(1, 3), true), + AssertOpsJson( + 1, + r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"}}]"#, + ), + Undo(0, 1), + AssertOpsJson(0, r#"[{"insert":"123"}]"#), + ]; + OpTester::new().run_script(ops); +} + +#[test] +fn delta_invert_attribute_delta_with_attribute_delta() { + let ops = vec![ + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Insert(0, "456", 3), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + Italic(0, Interval::new(2, 4), true), + AssertOpsJson( + 0, + r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#, + ), + Insert(1, "abc", 0), + Bold(1, Interval::new(0, 3), true), + Insert(1, "d", 3), + Italic(1, Interval::new(1, 3), true), + AssertOpsJson( + 1, + r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"}}]"#, + ), + Undo(0, 1), AssertOpsJson( 0, r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#,