Skip to content

Commit cebee48

Browse files
authored
Merge pull request #859 from AppFlowy-IO/feat/document-model-in-rust
Feat: document model in rust
2 parents c7b671c + a309a9c commit cebee48

File tree

15 files changed

+839
-14
lines changed

15 files changed

+839
-14
lines changed

frontend/app_flowy/packages/appflowy_editor/lib/src/operation/operation.dart

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import 'package:appflowy_editor/appflowy_editor.dart';
22

33
abstract class Operation {
44
factory Operation.fromJson(Map<String, dynamic> map) {
5-
String t = map["type"] as String;
6-
if (t == "insert-operation") {
5+
String t = map["op"] as String;
6+
if (t == "insert") {
77
return InsertOperation.fromJson(map);
8-
} else if (t == "update-operation") {
8+
} else if (t == "update") {
99
return UpdateOperation.fromJson(map);
10-
} else if (t == "delete-operation") {
10+
} else if (t == "delete") {
1111
return DeleteOperation.fromJson(map);
12-
} else if (t == "text-edit-operation") {
12+
} else if (t == "text-edit") {
1313
return TextEditOperation.fromJson(map);
1414
}
1515

@@ -51,7 +51,7 @@ class InsertOperation extends Operation {
5151
@override
5252
Map<String, dynamic> toJson() {
5353
return {
54-
"type": "insert-operation",
54+
"op": "insert",
5555
"path": path.toList(),
5656
"nodes": nodes.map((n) => n.toJson()),
5757
};
@@ -95,7 +95,7 @@ class UpdateOperation extends Operation {
9595
@override
9696
Map<String, dynamic> toJson() {
9797
return {
98-
"type": "update-operation",
98+
"op": "update",
9999
"path": path.toList(),
100100
"attributes": {...attributes},
101101
"oldAttributes": {...oldAttributes},
@@ -132,7 +132,7 @@ class DeleteOperation extends Operation {
132132
@override
133133
Map<String, dynamic> toJson() {
134134
return {
135-
"type": "delete-operation",
135+
"op": "delete",
136136
"path": path.toList(),
137137
"nodes": nodes.map((n) => n.toJson()),
138138
};
@@ -171,7 +171,7 @@ class TextEditOperation extends Operation {
171171
@override
172172
Map<String, dynamic> toJson() {
173173
return {
174-
"type": "text-edit-operation",
174+
"op": "text-edit",
175175
"path": path.toList(),
176176
"delta": delta.toJson(),
177177
"invert": inverted.toJson(),
@@ -207,10 +207,10 @@ Path transformPath(Path preInsertPath, Path b, [int delta = 1]) {
207207

208208
Operation transformOperation(Operation a, Operation b) {
209209
if (a is InsertOperation) {
210-
final newPath = transformPath(a.path, b.path);
210+
final newPath = transformPath(a.path, b.path, a.nodes.length);
211211
return b.copyWithPath(newPath);
212212
} else if (a is DeleteOperation) {
213-
final newPath = transformPath(a.path, b.path, -1);
213+
final newPath = transformPath(a.path, b.path, -1 * a.nodes.length);
214214
return b.copyWithPath(newPath);
215215
}
216216
// TODO: transform update and textedit

frontend/app_flowy/packages/appflowy_editor/test/legacy/operation_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ void main() {
8484
expect(transaction.toJson(), {
8585
"operations": [
8686
{
87-
"type": "insert-operation",
87+
"op": "insert",
8888
"path": [0],
8989
"nodes": [item1.toJson()],
9090
}
@@ -107,7 +107,7 @@ void main() {
107107
expect(transaction.toJson(), {
108108
"operations": [
109109
{
110-
"type": "delete-operation",
110+
"op": "delete",
111111
"path": [0],
112112
"nodes": [item1.toJson()],
113113
}

frontend/rust-lib/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shared-lib/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shared-lib/lib-ot/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ lazy_static = "1.4.0"
2424
strum = "0.21"
2525
strum_macros = "0.21"
2626
bytes = "1.0"
27+
indextree = "4.4.0"
2728

2829

2930
[features]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use std::collections::HashMap;
2+
3+
#[derive(Clone, serde::Serialize, serde::Deserialize)]
4+
pub struct NodeAttributes(pub HashMap<String, Option<String>>);
5+
6+
impl NodeAttributes {
7+
pub fn new() -> NodeAttributes {
8+
NodeAttributes(HashMap::new())
9+
}
10+
11+
pub fn compose(a: &NodeAttributes, b: &NodeAttributes) -> NodeAttributes {
12+
let mut new_map: HashMap<String, Option<String>> = b.0.clone();
13+
14+
for (key, value) in &a.0 {
15+
if b.0.contains_key(key.as_str()) {
16+
new_map.insert(key.into(), value.clone());
17+
}
18+
}
19+
20+
NodeAttributes(new_map)
21+
}
22+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
use crate::core::document::position::Position;
2+
use crate::core::{
3+
DocumentOperation, NodeAttributes, NodeData, NodeSubTree, OperationTransform, TextDelta, Transaction,
4+
};
5+
use crate::errors::{ErrorBuilder, OTError, OTErrorCode};
6+
use indextree::{Arena, NodeId};
7+
8+
pub struct DocumentTree {
9+
pub arena: Arena<NodeData>,
10+
pub root: NodeId,
11+
}
12+
13+
impl DocumentTree {
14+
pub fn new() -> DocumentTree {
15+
let mut arena = Arena::new();
16+
let root = arena.new_node(NodeData::new("root".into()));
17+
DocumentTree { arena, root }
18+
}
19+
20+
pub fn node_at_path(&self, position: &Position) -> Option<NodeId> {
21+
if position.is_empty() {
22+
return Some(self.root);
23+
}
24+
25+
let mut iterate_node = self.root;
26+
27+
for id in &position.0 {
28+
let child = self.child_at_index_of_path(iterate_node, id.clone());
29+
iterate_node = match child {
30+
Some(node) => node,
31+
None => return None,
32+
};
33+
}
34+
35+
Some(iterate_node)
36+
}
37+
38+
pub fn path_of_node(&self, node_id: NodeId) -> Position {
39+
let mut path: Vec<usize> = Vec::new();
40+
41+
let mut ancestors = node_id.ancestors(&self.arena);
42+
let mut current_node = node_id;
43+
let mut parent = ancestors.next();
44+
45+
while parent.is_some() {
46+
let parent_node = parent.unwrap();
47+
let counter = self.index_of_node(parent_node, current_node);
48+
path.push(counter);
49+
current_node = parent_node;
50+
parent = ancestors.next();
51+
}
52+
53+
Position(path)
54+
}
55+
56+
fn index_of_node(&self, parent_node: NodeId, child_node: NodeId) -> usize {
57+
let mut counter: usize = 0;
58+
59+
let mut children_iterator = parent_node.children(&self.arena);
60+
let mut node = children_iterator.next();
61+
62+
while node.is_some() {
63+
if node.unwrap() == child_node {
64+
return counter;
65+
}
66+
67+
node = children_iterator.next();
68+
counter += 1;
69+
}
70+
71+
counter
72+
}
73+
74+
fn child_at_index_of_path(&self, at_node: NodeId, index: usize) -> Option<NodeId> {
75+
let children = at_node.children(&self.arena);
76+
77+
let mut counter = 0;
78+
for child in children {
79+
if counter == index {
80+
return Some(child);
81+
}
82+
83+
counter += 1;
84+
}
85+
86+
None
87+
}
88+
89+
pub fn apply(&mut self, transaction: Transaction) -> Result<(), OTError> {
90+
for op in &transaction.operations {
91+
self.apply_op(op)?;
92+
}
93+
Ok(())
94+
}
95+
96+
fn apply_op(&mut self, op: &DocumentOperation) -> Result<(), OTError> {
97+
match op {
98+
DocumentOperation::Insert { path, nodes } => self.apply_insert(path, nodes),
99+
DocumentOperation::Update { path, attributes, .. } => self.apply_update(path, attributes),
100+
DocumentOperation::Delete { path, nodes } => self.apply_delete(path, nodes.len()),
101+
DocumentOperation::TextEdit { path, delta, .. } => self.apply_text_edit(path, delta),
102+
}
103+
}
104+
105+
fn apply_insert(&mut self, path: &Position, nodes: &[Box<NodeSubTree>]) -> Result<(), OTError> {
106+
let parent_path = &path.0[0..(path.0.len() - 1)];
107+
let last_index = path.0[path.0.len() - 1];
108+
let parent_node = self
109+
.node_at_path(&Position(parent_path.to_vec()))
110+
.ok_or(ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
111+
112+
self.insert_child_at_index(parent_node, last_index, nodes.as_ref())
113+
}
114+
115+
fn insert_child_at_index(
116+
&mut self,
117+
parent: NodeId,
118+
index: usize,
119+
insert_children: &[Box<NodeSubTree>],
120+
) -> Result<(), OTError> {
121+
if index == 0 && parent.children(&self.arena).next().is_none() {
122+
self.append_subtree(&parent, insert_children);
123+
return Ok(());
124+
}
125+
126+
let children_length = parent.children(&self.arena).fold(0, |counter, _| counter + 1);
127+
128+
if index == children_length {
129+
self.append_subtree(&parent, insert_children);
130+
return Ok(());
131+
}
132+
133+
let node_to_insert = self
134+
.child_at_index_of_path(parent, index)
135+
.ok_or(ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
136+
137+
self.insert_subtree_before(&node_to_insert, insert_children);
138+
Ok(())
139+
}
140+
141+
// recursive append the subtrees to the node
142+
fn append_subtree(&mut self, parent: &NodeId, insert_children: &[Box<NodeSubTree>]) {
143+
for child in insert_children {
144+
let child_id = self.arena.new_node(child.to_node_data());
145+
parent.append(child_id, &mut self.arena);
146+
147+
self.append_subtree(&child_id, child.children.as_ref());
148+
}
149+
}
150+
151+
fn insert_subtree_before(&mut self, before: &NodeId, insert_children: &[Box<NodeSubTree>]) {
152+
for child in insert_children {
153+
let child_id = self.arena.new_node(child.to_node_data());
154+
before.insert_before(child_id, &mut self.arena);
155+
156+
self.append_subtree(&child_id, child.children.as_ref());
157+
}
158+
}
159+
160+
fn apply_update(&mut self, path: &Position, attributes: &NodeAttributes) -> Result<(), OTError> {
161+
let update_node = self
162+
.node_at_path(path)
163+
.ok_or(ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
164+
let node_data = self.arena.get_mut(update_node).unwrap();
165+
let new_node = {
166+
let old_attributes = &node_data.get().attributes;
167+
let new_attributes = NodeAttributes::compose(&old_attributes, attributes);
168+
NodeData {
169+
attributes: new_attributes,
170+
..node_data.get().clone()
171+
}
172+
};
173+
*node_data.get_mut() = new_node;
174+
Ok(())
175+
}
176+
177+
fn apply_delete(&mut self, path: &Position, len: usize) -> Result<(), OTError> {
178+
let mut update_node = self
179+
.node_at_path(path)
180+
.ok_or(ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
181+
for _ in 0..len {
182+
let next = update_node.following_siblings(&self.arena).next();
183+
update_node.remove_subtree(&mut self.arena);
184+
if let Some(next_id) = next {
185+
update_node = next_id;
186+
} else {
187+
break;
188+
}
189+
}
190+
Ok(())
191+
}
192+
193+
fn apply_text_edit(&mut self, path: &Position, delta: &TextDelta) -> Result<(), OTError> {
194+
let edit_node = self
195+
.node_at_path(path)
196+
.ok_or(ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
197+
let node_data = self.arena.get_mut(edit_node).unwrap();
198+
let new_delta = if let Some(old_delta) = &node_data.get().delta {
199+
Some(old_delta.compose(delta)?)
200+
} else {
201+
None
202+
};
203+
if let Some(new_delta) = new_delta {
204+
*node_data.get_mut() = NodeData {
205+
delta: Some(new_delta),
206+
..node_data.get().clone()
207+
};
208+
};
209+
Ok(())
210+
}
211+
}

0 commit comments

Comments
 (0)