diff --git a/frontend/app_flowy/lib/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart b/frontend/app_flowy/lib/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart index a3eb0429a4..5284db71a3 100644 --- a/frontend/app_flowy/lib/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart +++ b/frontend/app_flowy/lib/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart @@ -285,7 +285,7 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> { final transaction = widget.editorState.transaction; transaction.deleteNodesAtPath( start, - end.last - start.last, + end.last - start.last + 1, ); await widget.editorState.apply(transaction); } diff --git a/shared-lib/lib-ot/src/core/node_tree/path.rs b/shared-lib/lib-ot/src/core/node_tree/path.rs index 781dcf45e7..205290dd9f 100644 --- a/shared-lib/lib-ot/src/core/node_tree/path.rs +++ b/shared-lib/lib-ot/src/core/node_tree/path.rs @@ -43,6 +43,16 @@ impl Path { pub fn is_root(&self) -> bool { self.0.len() == 1 && self.0[0] == 0 } + + pub fn next(&self) -> Self { + let mut cloned_self = self.clone(); + if !self.is_valid() { + return cloned_self; + } + let last = cloned_self.pop(); + cloned_self.push(last.unwrap() + 1); + cloned_self + } } impl std::ops::Deref for Path { diff --git a/shared-lib/lib-ot/src/core/node_tree/tree.rs b/shared-lib/lib-ot/src/core/node_tree/tree.rs index d2dff890cb..286c7cb523 100644 --- a/shared-lib/lib-ot/src/core/node_tree/tree.rs +++ b/shared-lib/lib-ot/src/core/node_tree/tree.rs @@ -293,7 +293,13 @@ impl NodeTree { match op { NodeOperation::Insert { path, nodes } => self.insert_nodes(&path, nodes), NodeOperation::Update { path, changeset } => self.update(&path, changeset), - NodeOperation::Delete { path, nodes: _ } => self.delete_node(&path), + NodeOperation::Delete { path, nodes } => { + if nodes.is_empty() { + self.delete_node(&path) + } else { + self.delete_nodes(&path, nodes) + } + }, } } /// Inserts nodes at given path @@ -456,14 +462,40 @@ impl NodeTree { Ok(()) } + /// Removes a node and the consecutive nodes behide it + /// + /// if the nodes is empty, it will remove the single node at the path. + /// else it will remove the nodes at the path and the consecutive nodes behind it. + fn delete_nodes(&mut self, path: &Path, nodes: Vec) -> Result<(), OTError> { + if !path.is_valid() { + return Err(OTErrorCode::InvalidPath.into()); + } + + let node_id = self.node_id_at_path(path).ok_or_else(|| { + tracing::warn!("Can't find any node at path: {:?}", path); + OTError::internal().context("Can't find any node at path") + }); + + if node_id.is_err() { + return Err(OTErrorCode::PathNotFound.into()); + } + + for _ in 0..nodes.len() { + let res = self.delete_node(path); + res? + } + + Ok(()) + } + /// Update the node at path with the `changeset` /// /// Do nothing if there is no node at the path. /// /// # Arguments /// - /// * `path`: references to the node that will be applied with the changeset - /// * `changeset`: the change that will be applied to the node + /// * `path`: references to the node that will be applied with the changeset + /// * `changeset`: the change that will be applied to the node /// /// returns: Result<(), OTError> fn update(&mut self, path: &Path, changeset: Changeset) -> Result<(), OTError> { diff --git a/shared-lib/lib-ot/tests/node/script.rs b/shared-lib/lib-ot/tests/node/script.rs index 22f911508a..2868b83cc3 100644 --- a/shared-lib/lib-ot/tests/node/script.rs +++ b/shared-lib/lib-ot/tests/node/script.rs @@ -31,6 +31,11 @@ pub enum NodeScript { path: Path, rev_id: usize, }, + DeleteNodes { + path: Path, + node_data_list: Vec, + rev_id: usize, + }, AssertNumberOfChildrenAtPath { path: Option, expected: usize, @@ -137,7 +142,17 @@ impl NodeTest { self.transform_transaction_if_need(&mut transaction, rev_id); self.apply_transaction(transaction); }, - + NodeScript::DeleteNodes { + path, + node_data_list, + rev_id, + } => { + let mut transaction = TransactionBuilder::new() + .delete_nodes_at_path(&self.node_tree, &path, node_data_list.len()) + .build(); + self.transform_transaction_if_need(&mut transaction, rev_id); + self.apply_transaction(transaction); + }, NodeScript::AssertNode { path, expected } => { let node = self.node_tree.get_node_data_at_path(&path); assert_eq!(node, expected.map(|e| e.into())); diff --git a/shared-lib/lib-ot/tests/node/tree_test.rs b/shared-lib/lib-ot/tests/node/tree_test.rs index 212ecf5238..4e6a7dcf4b 100644 --- a/shared-lib/lib-ot/tests/node/tree_test.rs +++ b/shared-lib/lib-ot/tests/node/tree_test.rs @@ -349,6 +349,44 @@ fn node_delete_test() { test.run_scripts(scripts); } +#[test] +fn nodes_delete_test() { + let mut test = NodeTest::new(); + let node_1 = NodeData::new("a"); + let node_2 = NodeData::new("b"); + let node_3 = NodeData::new("c"); + let node_data_list = vec![node_1, node_2, node_3]; + let path: Path = 0.into(); + let scripts = vec![ + InsertNodes { + path: path.clone(), + node_data_list: node_data_list.clone(), + rev_id: 1, + }, + DeleteNodes { + path: path.clone(), + node_data_list: node_data_list.clone(), + rev_id: 2, + }, + AssertNode { + path: path.clone(), + expected: None, + }, + AssertNode { + path: path.next(), + expected: None, + }, + AssertNode { + path: path.next().next(), + expected: None, + }, + AssertTreeJSON { + expected: r#""""#.to_string(), + }, + ]; + test.run_scripts(scripts); +} + #[test] fn node_delete_node_from_list_test() { let mut test = NodeTest::new();