diff --git a/shared-lib/lib-ot/src/core/document/document.rs b/shared-lib/lib-ot/src/core/document/document.rs index 063fe408b0..17cd1d7989 100644 --- a/shared-lib/lib-ot/src/core/document/document.rs +++ b/shared-lib/lib-ot/src/core/document/document.rs @@ -1,5 +1,7 @@ use crate::core::document::position::Position; -use crate::core::{DocumentOperation, NodeAttributes, NodeData, OperationTransform, TextDelta, Transaction}; +use crate::core::{ + DocumentOperation, NodeAttributes, NodeData, NodeSubTree, OperationTransform, TextDelta, Transaction, +}; use crate::errors::{ErrorBuilder, OTError, OTErrorCode}; use indextree::{Arena, NodeId}; @@ -100,40 +102,36 @@ impl DocumentTree { } } - fn apply_insert(&mut self, path: &Position, nodes: &Vec) -> Result<(), OTError> { + fn apply_insert(&mut self, path: &Position, nodes: &Vec) -> Result<(), OTError> { let parent_path = &path.0[0..(path.0.len() - 1)]; let last_index = path.0[path.0.len() - 1]; let parent_node = self .node_at_path(&Position(parent_path.to_vec())) .ok_or(ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; - let mut inserted_nodes = Vec::new(); + // let mut inserted_nodes = Vec::new(); + // + // for node in nodes { + // inserted_nodes.push(self.arena.new_node(node.to_node_data())); + // } - for node in nodes { - inserted_nodes.push(self.arena.new_node(node.clone())); - } - - self.insert_child_at_index(parent_node, last_index, &inserted_nodes) + self.insert_child_at_index(parent_node, last_index, nodes.as_ref()) } fn insert_child_at_index( &mut self, parent: NodeId, index: usize, - insert_children: &[NodeId], + insert_children: &[NodeSubTree], ) -> Result<(), OTError> { if index == 0 && parent.children(&self.arena).next().is_none() { - for id in insert_children { - parent.append(*id, &mut self.arena); - } + self.append_subtree(&parent, insert_children); return Ok(()); } let children_length = parent.children(&self.arena).fold(0, |counter, _| counter + 1); if index == children_length { - for id in insert_children { - parent.append(*id, &mut self.arena); - } + self.append_subtree(&parent, insert_children); return Ok(()); } @@ -141,12 +139,24 @@ impl DocumentTree { .child_at_index_of_path(parent, index) .ok_or(ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; - for id in insert_children { - node_to_insert.insert_before(*id, &mut self.arena); - } + self.insert_subtree_before(&node_to_insert, insert_children); Ok(()) } + fn append_subtree(&mut self, parent: &NodeId, insert_children: &[NodeSubTree]) { + for child in insert_children { + let child_id = self.arena.new_node(child.to_node_data()); + parent.append(child_id, &mut self.arena); + } + } + + fn insert_subtree_before(&mut self, before: &NodeId, insert_children: &[NodeSubTree]) { + for id in insert_children { + let child_id = self.arena.new_node(id.to_node_data()); + before.insert_before(child_id, &mut self.arena); + } + } + fn apply_update(&mut self, path: &Position, attributes: &NodeAttributes) -> Result<(), OTError> { let update_node = self .node_at_path(path) diff --git a/shared-lib/lib-ot/src/core/document/document_operation.rs b/shared-lib/lib-ot/src/core/document/document_operation.rs index 46cb79be4a..6a2e97e976 100644 --- a/shared-lib/lib-ot/src/core/document/document_operation.rs +++ b/shared-lib/lib-ot/src/core/document/document_operation.rs @@ -1,21 +1,21 @@ use crate::core::document::position::Position; -use crate::core::{NodeAttributes, NodeData, TextDelta}; +use crate::core::{NodeAttributes, NodeSubTree, TextDelta}; #[derive(Clone, serde::Serialize, serde::Deserialize)] +#[serde(tag = "type")] pub enum DocumentOperation { - Insert { - path: Position, - nodes: Vec, - }, + #[serde(rename = "insert-operation")] + Insert { path: Position, nodes: Vec }, + #[serde(rename = "update-operation")] Update { path: Position, attributes: NodeAttributes, + #[serde(rename = "oldAttributes")] old_attributes: NodeAttributes, }, - Delete { - path: Position, - nodes: Vec, - }, + #[serde(rename = "delete-operation")] + Delete { path: Position, nodes: Vec }, + #[serde(rename = "text-edit-operation")] TextEdit { path: Position, delta: TextDelta, @@ -101,7 +101,7 @@ impl DocumentOperation { #[cfg(test)] mod tests { - use crate::core::Position; + use crate::core::{Delta, DocumentOperation, NodeAttributes, NodeSubTree, Position}; #[test] fn test_transform_path_1() { @@ -150,4 +150,45 @@ mod tests { vec![0, 6] ); } + + #[test] + fn test_serialize_insert_operation() { + let insert = DocumentOperation::Insert { + path: Position(vec![0, 1]), + nodes: vec![NodeSubTree::new("text")], + }; + let result = serde_json::to_string(&insert).unwrap(); + assert_eq!( + result, + r#"{"type":"insert-operation","path":[0,1],"nodes":[{"node_type":"text","attributes":{}}]}"# + ); + } + + #[test] + fn test_serialize_update_operation() { + let insert = DocumentOperation::Update { + path: Position(vec![0, 1]), + attributes: NodeAttributes::new(), + old_attributes: NodeAttributes::new(), + }; + let result = serde_json::to_string(&insert).unwrap(); + assert_eq!( + result, + r#"{"type":"update-operation","path":[0,1],"attributes":{},"oldAttributes":{}}"# + ); + } + + #[test] + fn test_serialize_text_edit_operation() { + let insert = DocumentOperation::TextEdit { + path: Position(vec![0, 1]), + delta: Delta::new(), + inverted: Delta::new(), + }; + let result = serde_json::to_string(&insert).unwrap(); + assert_eq!( + result, + r#"{"type":"text-edit-operation","path":[0,1],"delta":[],"inverted":[]}"# + ); + } } diff --git a/shared-lib/lib-ot/src/core/document/node.rs b/shared-lib/lib-ot/src/core/document/node.rs index 96747b0fd8..1e298bc72e 100644 --- a/shared-lib/lib-ot/src/core/document/node.rs +++ b/shared-lib/lib-ot/src/core/document/node.rs @@ -1,6 +1,6 @@ use crate::core::{NodeAttributes, TextDelta}; -#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[derive(Clone)] pub struct NodeData { pub node_type: String, pub attributes: NodeAttributes, @@ -16,3 +16,32 @@ impl NodeData { } } } + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct NodeSubTree { + pub node_type: String, + pub attributes: NodeAttributes, + #[serde(skip_serializing_if = "Option::is_none")] + pub delta: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub children: Vec>, +} + +impl NodeSubTree { + pub fn new(node_type: &str) -> NodeSubTree { + NodeSubTree { + node_type: node_type.into(), + attributes: NodeAttributes::new(), + delta: None, + children: Vec::new(), + } + } + + pub fn to_node_data(&self) -> NodeData { + NodeData { + node_type: self.node_type.clone(), + attributes: self.attributes.clone(), + delta: self.delta.clone(), + } + } +} diff --git a/shared-lib/lib-ot/src/core/document/transaction.rs b/shared-lib/lib-ot/src/core/document/transaction.rs index 00697b7da1..b24a085eae 100644 --- a/shared-lib/lib-ot/src/core/document/transaction.rs +++ b/shared-lib/lib-ot/src/core/document/transaction.rs @@ -1,5 +1,5 @@ use crate::core::document::position::Position; -use crate::core::{DocumentOperation, DocumentTree, NodeAttributes, NodeData}; +use crate::core::{DocumentOperation, DocumentTree, NodeAttributes, NodeSubTree}; use std::collections::HashMap; pub struct Transaction { @@ -25,7 +25,7 @@ impl<'a> TransactionBuilder<'a> { } } - pub fn insert_nodes_at_path(&mut self, path: &Position, nodes: &[NodeData]) { + pub fn insert_nodes_at_path(&mut self, path: &Position, nodes: &[NodeSubTree]) { self.push(DocumentOperation::Insert { path: path.clone(), nodes: nodes.to_vec(), @@ -59,11 +59,17 @@ impl<'a> TransactionBuilder<'a> { pub fn delete_nodes_at_path(&mut self, path: &Position, length: usize) { let mut node = self.document.node_at_path(path).unwrap(); - let mut deleted_nodes: Vec = Vec::new(); + let mut deleted_nodes: Vec = Vec::new(); for _ in 0..length { - let data = self.document.arena.get(node).unwrap(); - deleted_nodes.push(data.get().clone()); + let node_data = self.document.arena.get(node).unwrap(); + let data = node_data.get(); + deleted_nodes.push(NodeSubTree { + node_type: data.node_type.clone(), + attributes: data.attributes.clone(), + delta: data.delta.clone(), + children: vec![], + }); node = node.following_siblings(&self.document.arena).next().unwrap(); }