Explorar o código

feat: support delete consecutive nodes on backend (#1877)

Lucas.Xu %!s(int64=2) %!d(string=hai) anos
pai
achega
b356354cd8

+ 1 - 1
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);
       }

+ 10 - 0
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 {

+ 35 - 3
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<NodeData>) -> 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> {

+ 16 - 1
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<NodeData>,
+    rev_id: usize,
+  },
   AssertNumberOfChildrenAtPath {
     path: Option<Path>,
     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()));

+ 38 - 0
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();