mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: updata path documentation
This commit is contained in:
parent
2d738fedaa
commit
4b654de2e6
@ -216,8 +216,8 @@ impl NodeTree {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Append the node to the end of the children list if index greater or equal to the
|
||||
/// length of the children.
|
||||
// Append the node to the end of the children list if index greater or equal to the
|
||||
// length of the children.
|
||||
if index >= parent.children(&self.arena).count() {
|
||||
self.append_nodes(&parent, nodes);
|
||||
return Ok(());
|
||||
|
@ -66,15 +66,12 @@ impl NodeOperation {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mut_path<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Path),
|
||||
{
|
||||
pub fn get_mut_path(&mut self) -> &mut Path {
|
||||
match self {
|
||||
NodeOperation::Insert { path, .. } => f(path),
|
||||
NodeOperation::UpdateAttributes { path, .. } => f(path),
|
||||
NodeOperation::Delete { path, .. } => f(path),
|
||||
NodeOperation::UpdateBody { path, .. } => f(path),
|
||||
NodeOperation::Insert { path, .. } => path,
|
||||
NodeOperation::UpdateAttributes { path, .. } => path,
|
||||
NodeOperation::Delete { path, .. } => path,
|
||||
NodeOperation::UpdateBody { path, .. } => path,
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,15 +101,15 @@ impl NodeOperation {
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform the `other` operation into a new operation that carries the changes made by
|
||||
/// the current operation.
|
||||
/// Make the `other` operation to be applied to the version that has been modified.
|
||||
/// The semantics of transform is used when editing conflicts occur, which is often determined by the version id。
|
||||
/// For example, if the inserted position has been acquired by others, then you need to do transform to make sure
|
||||
/// your position is value.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `other`: The operation that is going to be transformed
|
||||
///
|
||||
/// returns: NodeOperation
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
@ -130,39 +127,21 @@ impl NodeOperation {
|
||||
/// nodes: vec![node_2],
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,1],
|
||||
/// "nodes":[{"type":"text_2"}]}"#);
|
||||
/// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text_2"}]}"#);
|
||||
///
|
||||
/// let new_op = op_1.transform(&op_2);
|
||||
/// assert_eq!(serde_json::to_string(&new_op).unwrap(), r#"{"op":"insert","path":[0,2],
|
||||
/// "nodes":[{"type":"text_2"}]}"#);
|
||||
/// op_1.transform(&mut op_2);
|
||||
/// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#);
|
||||
///
|
||||
/// ```
|
||||
pub fn transform(&self, other: &NodeOperation) -> NodeOperation {
|
||||
let mut other = other.clone();
|
||||
pub fn transform(&self, other: &mut NodeOperation) {
|
||||
match self {
|
||||
NodeOperation::Insert { path, nodes } => {
|
||||
let new_path = Path::transform(path, other.get_path(), nodes.len() as i64);
|
||||
other.mut_path(|path| *path = new_path);
|
||||
let new_path = path.transform(other.get_path(), nodes.len());
|
||||
*other.get_mut_path() = new_path;
|
||||
}
|
||||
NodeOperation::Delete { path: a_path, nodes } => {
|
||||
let new_path = Path::transform(a_path, other.get_path(), nodes.len() as i64);
|
||||
other.mut_path(|path| *path = new_path);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
other
|
||||
}
|
||||
|
||||
pub fn mut_transform(&self, other: &mut NodeOperation) {
|
||||
match self {
|
||||
NodeOperation::Insert { path, nodes } => {
|
||||
let new_path = Path::transform(path, other.get_path(), nodes.len() as i64);
|
||||
other.mut_path(|path| *path = new_path);
|
||||
}
|
||||
NodeOperation::Delete { path: a_path, nodes } => {
|
||||
let new_path = Path::transform(a_path, other.get_path(), nodes.len() as i64);
|
||||
other.mut_path(|path| *path = new_path);
|
||||
NodeOperation::Delete { path, nodes } => {
|
||||
let new_path = path.transform(other.get_path(), nodes.len());
|
||||
*other.get_mut_path() = new_path;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The `Path` represents as a path to reference to the node in the `NodeTree`.
|
||||
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)]
|
||||
pub struct Path(pub Vec<usize>);
|
||||
|
||||
@ -48,8 +49,56 @@ impl From<&[usize]> for Path {
|
||||
}
|
||||
|
||||
impl Path {
|
||||
///
|
||||
///
|
||||
/// The path will be changed is
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `other`:
|
||||
/// * `offset`: represents the len of nodes referenced by this path
|
||||
///
|
||||
/// returns: Path
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
pub fn transform(&self, other: &Path, offset: usize) -> Path {
|
||||
if self.len() > other.len() {
|
||||
return other.clone();
|
||||
}
|
||||
if self.is_empty() || other.is_empty() {
|
||||
return other.clone();
|
||||
}
|
||||
for i in 0..(self.len() - 1) {
|
||||
if self.0[i] != other.0[i] {
|
||||
return other.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Splits the `Path` into two part. The suffix will contain the last element of the `Path`.
|
||||
let second_last_index = self.0.len() - 1;
|
||||
let mut prefix: Vec<usize> = self.0[0..second_last_index].into();
|
||||
let mut suffix: Vec<usize> = other.0[self.0.len()..].into();
|
||||
let last_value = self.0.last().unwrap().clone();
|
||||
|
||||
let other_second_last_value = other.0[second_last_index];
|
||||
|
||||
//
|
||||
if last_value <= other_second_last_value {
|
||||
prefix.push(other_second_last_value + offset);
|
||||
} else {
|
||||
prefix.push(other_second_last_value);
|
||||
}
|
||||
|
||||
// concat the prefix and suffix into a new path
|
||||
prefix.append(&mut suffix);
|
||||
Path(prefix)
|
||||
}
|
||||
|
||||
// delta is default to be 1
|
||||
pub fn transform(pre_insert_path: &Path, b: &Path, offset: i64) -> Path {
|
||||
pub fn transform3(pre_insert_path: &Path, b: &Path, offset: i64) -> Path {
|
||||
if pre_insert_path.len() > b.len() {
|
||||
return b.clone();
|
||||
}
|
||||
@ -83,12 +132,12 @@ mod tests {
|
||||
#[test]
|
||||
fn path_transform_test_1() {
|
||||
assert_eq!(
|
||||
{ Path::transform(&Path(vec![0, 1]), &Path(vec![0, 1]), 1) }.0,
|
||||
{ Path::transform3(&Path(vec![0, 1]), &Path(vec![0, 1]), 1) }.0,
|
||||
vec![0, 2]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
{ Path::transform(&Path(vec![0, 1]), &Path(vec![0, 1]), 5) }.0,
|
||||
{ Path::transform3(&Path(vec![0, 1]), &Path(vec![0, 1]), 5) }.0,
|
||||
vec![0, 6]
|
||||
);
|
||||
}
|
||||
@ -96,7 +145,7 @@ mod tests {
|
||||
#[test]
|
||||
fn path_transform_test_2() {
|
||||
assert_eq!(
|
||||
{ Path::transform(&Path(vec![0, 1]), &Path(vec![0, 2]), 1) }.0,
|
||||
{ Path::transform3(&Path(vec![0, 1]), &Path(vec![0, 2]), 1) }.0,
|
||||
vec![0, 3]
|
||||
);
|
||||
}
|
||||
@ -104,7 +153,7 @@ mod tests {
|
||||
#[test]
|
||||
fn path_transform_test_3() {
|
||||
assert_eq!(
|
||||
{ Path::transform(&Path(vec![0, 1]), &Path(vec![0, 2, 7, 8, 9]), 1) }.0,
|
||||
{ Path::transform3(&Path(vec![0, 1]), &Path(vec![0, 2, 7, 8, 9]), 1) }.0,
|
||||
vec![0, 3, 7, 8, 9]
|
||||
);
|
||||
}
|
||||
@ -112,15 +161,15 @@ mod tests {
|
||||
#[test]
|
||||
fn path_transform_no_changed_test() {
|
||||
assert_eq!(
|
||||
{ Path::transform(&Path(vec![0, 1, 2]), &Path(vec![0, 0, 7, 8, 9]), 1) }.0,
|
||||
{ Path::transform3(&Path(vec![0, 1, 2]), &Path(vec![0, 0, 7, 8, 9]), 1) }.0,
|
||||
vec![0, 0, 7, 8, 9]
|
||||
);
|
||||
assert_eq!(
|
||||
{ Path::transform(&Path(vec![0, 1, 2]), &Path(vec![0, 1]), 1) }.0,
|
||||
{ Path::transform3(&Path(vec![0, 1, 2]), &Path(vec![0, 1]), 1) }.0,
|
||||
vec![0, 1]
|
||||
);
|
||||
assert_eq!(
|
||||
{ Path::transform(&Path(vec![1, 1]), &Path(vec![1, 0]), 1) }.0,
|
||||
{ Path::transform3(&Path(vec![1, 1]), &Path(vec![1, 0]), 1) }.0,
|
||||
vec![1, 0]
|
||||
);
|
||||
}
|
||||
|
@ -27,10 +27,14 @@ impl Transaction {
|
||||
self.operations.into_inner()
|
||||
}
|
||||
|
||||
/// Make the `other` to be applied to the version that has been modified.
|
||||
///
|
||||
/// The semantics of transform is used when editing conflicts occur, which is often determined by the version id。
|
||||
/// the operations of the transaction will be transformed into the conflict operations.
|
||||
pub fn transform(&self, other: &mut Transaction) {
|
||||
for other_operation in other.iter_mut() {
|
||||
for operation in self.operations.iter() {
|
||||
operation.mut_transform(other_operation);
|
||||
operation.transform(other_operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ fn operation_insert_op_transform_test() {
|
||||
nodes: vec![node_1],
|
||||
};
|
||||
|
||||
let insert_2 = NodeOperation::Insert {
|
||||
let mut insert_2 = NodeOperation::Insert {
|
||||
path: Path(vec![0, 1]),
|
||||
nodes: vec![node_2],
|
||||
};
|
||||
@ -89,29 +89,33 @@ fn operation_insert_op_transform_test() {
|
||||
// let mut node_tree = NodeTree::new("root");
|
||||
// node_tree.apply_op(insert_1.clone()).unwrap();
|
||||
|
||||
let new_op = op_1.transform(&insert_2);
|
||||
let json = serde_json::to_string(&new_op).unwrap();
|
||||
op_1.transform(&mut insert_2);
|
||||
let json = serde_json::to_string(&insert_2).unwrap();
|
||||
assert_eq!(json, r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operation_insert_transform_test() {
|
||||
fn operation_insert_transform_one_level_path_test() {
|
||||
let mut test = NodeTest::new();
|
||||
let node_data_1 = NodeDataBuilder::new("text_1").build();
|
||||
let node_data_2 = NodeDataBuilder::new("text_2").build();
|
||||
let node_data_3 = NodeDataBuilder::new("text_3").build();
|
||||
let node_3: Node = node_data_3.clone().into();
|
||||
// 0: text_1
|
||||
// 1: text_2
|
||||
//
|
||||
// rev_id:1 0: text_1
|
||||
// rev_id:2 1: text_2
|
||||
// Insert a new operation with rev_id 1,but the rev_id:1 is already exist, so
|
||||
// it needs to be transformed.
|
||||
// 1:text_3 => 2:text_3
|
||||
//
|
||||
// Insert a new operation with rev_id 1.But the rev_id:1 is already exist, so
|
||||
// it needs to do the transform.
|
||||
//
|
||||
// --> 1:text_3
|
||||
// transform into:
|
||||
// --> 2:text_3
|
||||
// 0: text_1
|
||||
// 1: text_2
|
||||
// 2: text_3
|
||||
//
|
||||
// If the rev_id of the insert operation is 3. then the tree will be:
|
||||
// 0: text_1
|
||||
// 1: text_3
|
||||
// 2: text_2
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
@ -137,7 +141,45 @@ fn operation_insert_transform_test() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operation_delete_transform_test() {
|
||||
fn operation_insert_transform_multiple_level_path_test() {
|
||||
let mut test = NodeTest::new();
|
||||
let node_data_1 = NodeDataBuilder::new("text_1")
|
||||
.add_node(NodeDataBuilder::new("text_1_1").build())
|
||||
.add_node(NodeDataBuilder::new("text_1_2").build())
|
||||
.build();
|
||||
|
||||
let node_data_2 = NodeDataBuilder::new("text_2")
|
||||
.add_node(NodeDataBuilder::new("text_2_1").build())
|
||||
.add_node(NodeDataBuilder::new("text_2_2").build())
|
||||
.build();
|
||||
|
||||
let node_data_3 = NodeDataBuilder::new("text_3").build();
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
node_data: node_data_1.clone(),
|
||||
rev_id: 1,
|
||||
},
|
||||
InsertNode {
|
||||
path: 1.into(),
|
||||
node_data: node_data_2.clone(),
|
||||
rev_id: 2,
|
||||
},
|
||||
InsertNode {
|
||||
path: 1.into(),
|
||||
node_data: node_data_3.clone(),
|
||||
rev_id: 1,
|
||||
},
|
||||
AssertNode {
|
||||
path: 2.into(),
|
||||
expected: Some(node_data_3.into()),
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operation_delete_transform_path_test() {
|
||||
let mut test = NodeTest::new();
|
||||
let node_data_1 = NodeDataBuilder::new("text_1").build();
|
||||
let node_data_2 = NodeDataBuilder::new("text_2").build();
|
||||
@ -167,7 +209,6 @@ fn operation_delete_transform_test() {
|
||||
node_data: node_data_3.clone(),
|
||||
rev_id: 3,
|
||||
},
|
||||
//
|
||||
// 0: text_1
|
||||
// 1: text_3
|
||||
// 2: text_2
|
||||
@ -178,6 +219,10 @@ fn operation_delete_transform_test() {
|
||||
path: 1.into(),
|
||||
rev_id: 3,
|
||||
},
|
||||
// After perform the delete action, the tree will be:
|
||||
// 0: text_1
|
||||
// 1: text_3
|
||||
AssertNumberOfNodesAtPath { path: None, len: 2 },
|
||||
AssertNode {
|
||||
path: 1.into(),
|
||||
expected: Some(node_3),
|
||||
@ -186,7 +231,6 @@ fn operation_delete_transform_test() {
|
||||
path: 2.into(),
|
||||
expected: None,
|
||||
},
|
||||
AssertNumberOfNodesAtPath { path: None, len: 2 },
|
||||
];
|
||||
test.run_scripts(scripts);
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ impl NodeTest {
|
||||
|
||||
fn transform_transaction_if_need(&mut self, transaction: &mut Transaction, rev_id: usize) {
|
||||
if self.rev_id >= rev_id {
|
||||
for rev_id in rev_id..self.rev_id {
|
||||
for rev_id in rev_id..=self.rev_id {
|
||||
let old_transaction = self.rev_operations.get(&rev_id).unwrap();
|
||||
old_transaction.transform(transaction);
|
||||
}
|
||||
|
@ -101,8 +101,6 @@ fn node_insert_node_in_ordered_nodes_test() {
|
||||
let path_3: Path = 2.into();
|
||||
let node_3 = NodeData::new("text_3");
|
||||
|
||||
let path_4: Path = 3.into();
|
||||
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: path_1.clone(),
|
||||
@ -119,13 +117,18 @@ fn node_insert_node_in_ordered_nodes_test() {
|
||||
node_data: node_3.clone(),
|
||||
rev_id: 3,
|
||||
},
|
||||
// 0:note_1 , 1: note_2_1, 2: note_3
|
||||
// 0:text_1
|
||||
// 1:text_2_1
|
||||
// 2:text_3
|
||||
InsertNode {
|
||||
path: path_2.clone(),
|
||||
node_data: node_2_2.clone(),
|
||||
rev_id: 4,
|
||||
},
|
||||
// 0:note_1 , 1:note_2_2, 2: note_2_1, 3: note_3
|
||||
// 0:text_1
|
||||
// 1:text_2_2
|
||||
// 2:text_2_1
|
||||
// 3:text_3
|
||||
AssertNodeData {
|
||||
path: path_1,
|
||||
expected: Some(node_1),
|
||||
@ -138,15 +141,105 @@ fn node_insert_node_in_ordered_nodes_test() {
|
||||
path: path_3,
|
||||
expected: Some(node_2_1),
|
||||
},
|
||||
AssertNodeData {
|
||||
path: path_4,
|
||||
expected: Some(node_3),
|
||||
},
|
||||
AssertNumberOfNodesAtPath { path: None, len: 4 },
|
||||
];
|
||||
test.run_scripts(scripts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_insert_nested_nodes_test() {
|
||||
let mut test = NodeTest::new();
|
||||
let node_data_1_1 = NodeDataBuilder::new("text_1_1").build();
|
||||
let node_data_1_2 = NodeDataBuilder::new("text_1_2").build();
|
||||
let node_data_1 = NodeDataBuilder::new("text_1")
|
||||
.add_node(node_data_1_1.clone())
|
||||
.add_node(node_data_1_2.clone())
|
||||
.build();
|
||||
|
||||
let node_data_2_1 = NodeDataBuilder::new("text_2_1").build();
|
||||
let node_data_2_2 = NodeDataBuilder::new("text_2_2").build();
|
||||
let node_data_2 = NodeDataBuilder::new("text_2")
|
||||
.add_node(node_data_2_1.clone())
|
||||
.add_node(node_data_2_2.clone())
|
||||
.build();
|
||||
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
node_data: node_data_1.clone(),
|
||||
rev_id: 1,
|
||||
},
|
||||
InsertNode {
|
||||
path: 1.into(),
|
||||
node_data: node_data_2.clone(),
|
||||
rev_id: 2,
|
||||
},
|
||||
// the tree will be:
|
||||
// 0:text_1
|
||||
// 0:text_1_1
|
||||
// 1:text_1_2
|
||||
// 1:text_2
|
||||
// 0:text_2_1
|
||||
// 1:text_2_2
|
||||
AssertNode {
|
||||
path: vec![0, 0].into(),
|
||||
expected: Some(node_data_1_1.into()),
|
||||
},
|
||||
AssertNode {
|
||||
path: vec![0, 1].into(),
|
||||
expected: Some(node_data_1_2.into()),
|
||||
},
|
||||
AssertNode {
|
||||
path: vec![1, 0].into(),
|
||||
expected: Some(node_data_2_1.into()),
|
||||
},
|
||||
AssertNode {
|
||||
path: vec![1, 1].into(),
|
||||
expected: Some(node_data_2_2.into()),
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_insert_node_before_existing_nested_nodes_test() {
|
||||
let mut test = NodeTest::new();
|
||||
let node_data_1_1 = NodeDataBuilder::new("text_1_1").build();
|
||||
let node_data_1_2 = NodeDataBuilder::new("text_1_2").build();
|
||||
let node_data_1 = NodeDataBuilder::new("text_1")
|
||||
.add_node(node_data_1_1.clone())
|
||||
.add_node(node_data_1_2.clone())
|
||||
.build();
|
||||
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
node_data: node_data_1.clone(),
|
||||
rev_id: 1,
|
||||
},
|
||||
// 0:text_1
|
||||
// 0:text_1_1
|
||||
// 1:text_1_2
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
node_data: NodeDataBuilder::new("text_0").build(),
|
||||
rev_id: 2,
|
||||
},
|
||||
// 0:text_0
|
||||
// 1:text_1
|
||||
// 0:text_1_1
|
||||
// 1:text_1_2
|
||||
AssertNode {
|
||||
path: vec![1, 0].into(),
|
||||
expected: Some(node_data_1_1.into()),
|
||||
},
|
||||
AssertNode {
|
||||
path: vec![1, 1].into(),
|
||||
expected: Some(node_data_1_2.into()),
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts);
|
||||
}
|
||||
#[test]
|
||||
fn node_insert_with_attributes_test() {
|
||||
let mut test = NodeTest::new();
|
||||
|
Loading…
x
Reference in New Issue
Block a user