chore: implement document editor trait (#1321)

This commit is contained in:
Nathan.fooo 2022-10-20 15:33:18 +08:00 committed by GitHub
parent f1a5726fcb
commit 7dbd9fe8cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 274 additions and 57 deletions

View File

@ -2,7 +2,9 @@ use bytes::Bytes;
use flowy_error::{FlowyError, FlowyResult};
use flowy_revision::{RevisionObjectDeserializer, RevisionObjectSerializer};
use flowy_sync::entities::revision::Revision;
use lib_ot::core::{Body, Extension, Interval, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Transaction};
use lib_ot::core::{
Body, Extension, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Selection, Transaction,
};
use lib_ot::text_delta::TextOperationBuilder;
#[derive(Debug)]
@ -46,8 +48,8 @@ pub fn initial_document_content() -> String {
nodes: vec![editor_node],
};
let extension = Extension::TextSelection {
before_selection: Interval::default(),
after_selection: Interval::default(),
before_selection: Selection::default(),
after_selection: Selection::default(),
};
let transaction = Transaction {
operations: vec![node_operation].into(),

View File

@ -1,6 +1,11 @@
use crate::editor::document::Document;
use lib_ot::core::{AttributeHashMap, Body, NodeData, NodeId, NodeTree};
use bytes::Bytes;
use flowy_error::FlowyResult;
use lib_ot::core::{
AttributeHashMap, Body, Changeset, Extension, NodeData, NodeId, NodeOperation, NodeTree, Path, Selection,
Transaction,
};
use lib_ot::text_delta::TextOperations;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::{SerializeMap, SerializeSeq};
@ -67,8 +72,157 @@ impl<'de> Deserialize<'de> for Document {
#[derive(Debug)]
struct DocumentContentSerializer<'a>(pub &'a Document);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentTransaction {
#[serde(default)]
operations: Vec<DocumentOperation>,
#[serde(default)]
before_selection: Selection,
#[serde(default)]
after_selection: Selection,
}
impl DocumentTransaction {
pub fn to_json(&self) -> FlowyResult<String> {
let json = serde_json::to_string(self)?;
Ok(json)
}
pub fn to_bytes(&self) -> FlowyResult<Bytes> {
let data = serde_json::to_vec(&self)?;
Ok(Bytes::from(data))
}
pub fn from_bytes(bytes: Bytes) -> FlowyResult<Self> {
let transaction = serde_json::from_slice(&bytes)?;
Ok(transaction)
}
}
impl std::convert::From<Transaction> for DocumentTransaction {
fn from(transaction: Transaction) -> Self {
let (before_selection, after_selection) = match transaction.extension {
Extension::Empty => (Selection::default(), Selection::default()),
Extension::TextSelection {
before_selection,
after_selection,
} => (before_selection, after_selection),
};
DocumentTransaction {
operations: transaction
.operations
.into_inner()
.into_iter()
.map(|operation| operation.as_ref().into())
.collect(),
before_selection,
after_selection,
}
}
}
impl std::convert::From<DocumentTransaction> for Transaction {
fn from(transaction: DocumentTransaction) -> Self {
Transaction {
operations: transaction
.operations
.into_iter()
.map(|operation| operation.into())
.collect::<Vec<NodeOperation>>()
.into(),
extension: Extension::TextSelection {
before_selection: transaction.before_selection,
after_selection: transaction.after_selection,
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "op")]
pub enum DocumentOperation {
#[serde(rename = "insert")]
Insert { path: Path, nodes: Vec<DocumentNode> },
#[serde(rename = "delete")]
Delete { path: Path, nodes: Vec<DocumentNode> },
#[serde(rename = "update")]
Update {
path: Path,
attributes: AttributeHashMap,
#[serde(rename = "oldAttributes")]
old_attributes: AttributeHashMap,
},
#[serde(rename = "update_text")]
UpdateText {
path: Path,
delta: TextOperations,
inverted: TextOperations,
},
}
impl std::convert::From<DocumentOperation> for NodeOperation {
fn from(document_operation: DocumentOperation) -> Self {
match document_operation {
DocumentOperation::Insert { path, nodes } => NodeOperation::Insert {
path,
nodes: nodes.into_iter().map(|node| node.into()).collect(),
},
DocumentOperation::Delete { path, nodes } => NodeOperation::Delete {
path,
nodes: nodes.into_iter().map(|node| node.into()).collect(),
},
DocumentOperation::Update {
path,
attributes,
old_attributes,
} => NodeOperation::Update {
path,
changeset: Changeset::Attributes {
new: attributes,
old: old_attributes,
},
},
DocumentOperation::UpdateText { path, delta, inverted } => NodeOperation::Update {
path,
changeset: Changeset::Delta { delta, inverted },
},
}
}
}
impl std::convert::From<&NodeOperation> for DocumentOperation {
fn from(node_operation: &NodeOperation) -> Self {
let node_operation = node_operation.clone();
match node_operation {
NodeOperation::Insert { path, nodes } => DocumentOperation::Insert {
path,
nodes: nodes.into_iter().map(|node| node.into()).collect(),
},
NodeOperation::Update { path, changeset } => match changeset {
Changeset::Delta { delta, inverted } => DocumentOperation::UpdateText { path, delta, inverted },
Changeset::Attributes { new, old } => DocumentOperation::Update {
path,
attributes: new,
old_attributes: old,
},
},
NodeOperation::Delete { path, nodes } => DocumentOperation::Delete {
path,
nodes: nodes.into_iter().map(|node| node.into()).collect(),
},
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct DocumentNodeData {
pub struct DocumentNode {
#[serde(rename = "type")]
pub node_type: String,
@ -81,21 +235,32 @@ pub struct DocumentNodeData {
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub children: Vec<DocumentNodeData>,
pub children: Vec<DocumentNode>,
}
impl std::convert::From<NodeData> for DocumentNodeData {
impl std::convert::From<NodeData> for DocumentNode {
fn from(node_data: NodeData) -> Self {
let delta = if let Body::Delta(operations) = node_data.body {
operations
} else {
TextOperations::default()
};
DocumentNodeData {
DocumentNode {
node_type: node_data.node_type,
attributes: node_data.attributes,
delta,
children: node_data.children.into_iter().map(DocumentNodeData::from).collect(),
children: node_data.children.into_iter().map(DocumentNode::from).collect(),
}
}
}
impl std::convert::From<DocumentNode> for NodeData {
fn from(document_node: DocumentNode) -> Self {
NodeData {
node_type: document_node.node_type,
attributes: document_node.attributes,
body: Body::Delta(document_node.delta),
children: document_node.children.into_iter().map(|child| child.into()).collect(),
}
}
}
@ -109,7 +274,7 @@ impl<'a> Serialize for DocumentContentSerializer<'a> {
let root_node_id = tree.root_node_id();
// transform the NodeData to DocumentNodeData
let get_document_node_data = |node_id: NodeId| tree.get_node_data(node_id).map(DocumentNodeData::from);
let get_document_node_data = |node_id: NodeId| tree.get_node_data(node_id).map(DocumentNode::from);
let mut children = tree.get_children_ids(root_node_id);
if children.len() == 1 {
@ -133,6 +298,40 @@ impl<'a> Serialize for DocumentContentSerializer<'a> {
#[cfg(test)]
mod tests {
use crate::editor::document::Document;
use crate::editor::document_serde::DocumentTransaction;
#[test]
fn transaction_deserialize_update_text_operation_test() {
// bold
let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":3,"attributes":{"bold":true}}],"inverted":[{"retain":3,"attributes":{"bold":null}}]}],"after_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}}}"#;
let _ = serde_json::from_str::<DocumentTransaction>(json).unwrap();
// delete character
let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":2},{"delete":1}],"inverted":[{"retain":2},{"insert":"C","attributes":{"bold":true}}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[0],"offset":3},"end":{"path":[0],"offset":3}}}"#;
let _ = serde_json::from_str::<DocumentTransaction>(json).unwrap();
}
#[test]
fn transaction_deserialize_insert_operation_test() {
let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"insert":"a"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0],"offset":1},"end":{"path":[0],"offset":1}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":0}}}"#;
let _ = serde_json::from_str::<DocumentTransaction>(json).unwrap();
}
#[test]
fn transaction_deserialize_delete_operation_test() {
let json = r#"{"operations": [{"op":"delete","path":[1],"nodes":[{"type":"text","delta":[]}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[1],"offset":0},"end":{"path":[1],"offset":0}}}"#;
let _transaction = serde_json::from_str::<DocumentTransaction>(json).unwrap();
}
#[test]
fn transaction_deserialize_update_attribute_operation_test() {
// let json = r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3,"attributes":{"bold":true}},"oldAttributes":{"retain":3,"attributes":{"bold":null}}}]}"#;
// let transaction = serde_json::from_str::<DocumentTransaction>(&json).unwrap();
let json =
r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3},"oldAttributes":{"retain":3}}]}"#;
let _ = serde_json::from_str::<DocumentTransaction>(json).unwrap();
}
#[test]
fn document_serde_test() {

View File

@ -1,4 +1,5 @@
use crate::editor::document::{Document, DocumentRevisionSerde};
use crate::editor::document_serde::DocumentTransaction;
use crate::editor::queue::{Command, CommandSender, DocumentQueue};
use crate::{DocumentEditor, DocumentUser};
use bytes::Bytes;
@ -66,25 +67,27 @@ fn spawn_edit_queue(
}
impl DocumentEditor for Arc<AppFlowyDocumentEditor> {
fn get_operations_str(&self) -> FutureResult<String, FlowyError> {
todo!()
fn export(&self) -> FutureResult<String, FlowyError> {
let this = self.clone();
FutureResult::new(async move { this.get_content(false).await })
}
fn compose_local_operations(&self, _data: Bytes) -> FutureResult<(), FlowyError> {
todo!()
fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> {
let this = self.clone();
FutureResult::new(async move {
let transaction = DocumentTransaction::from_bytes(data)?;
let _ = this.apply_transaction(transaction.into()).await?;
Ok(())
})
}
fn close(&self) {
todo!()
}
fn close(&self) {}
fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FutureResult<(), FlowyError> {
todo!()
FutureResult::new(async move { Ok(()) })
}
fn receive_ws_state(&self, _state: &WSConnectState) {
todo!()
}
fn receive_ws_state(&self, _state: &WSConnectState) {}
fn as_any(&self) -> &dyn Any {
self

View File

@ -12,7 +12,7 @@ pub(crate) async fn get_document_handler(
) -> DataResult<DocumentSnapshotPB, FlowyError> {
let document_id: DocumentIdPB = data.into_inner();
let editor = manager.open_document_editor(&document_id).await?;
let operations_str = editor.get_operations_str().await?;
let operations_str = editor.export().await?;
data_result(DocumentSnapshotPB {
doc_id: document_id.into(),
snapshot: operations_str,
@ -35,9 +35,9 @@ pub(crate) async fn export_handler(
) -> DataResult<ExportDataPB, FlowyError> {
let params: ExportParams = data.into_inner().try_into()?;
let editor = manager.open_document_editor(&params.view_id).await?;
let operations_str = editor.get_operations_str().await?;
let document_data = editor.export().await?;
data_result(ExportDataPB {
data: operations_str,
data: document_data,
export_type: params.export_type,
})
}

View File

@ -1,6 +1,6 @@
use crate::editor::{initial_document_content, AppFlowyDocumentEditor};
use crate::entities::EditParams;
use crate::old_editor::editor::{DocumentRevisionCompress, OldDocumentEditor};
use crate::old_editor::editor::{DeltaDocumentEditor, DocumentRevisionCompress};
use crate::{errors::FlowyError, DocumentCloudService};
use bytes::Bytes;
use dashmap::DashMap;
@ -30,7 +30,7 @@ pub trait DocumentUser: Send + Sync {
}
pub trait DocumentEditor: Send + Sync {
fn get_operations_str(&self) -> FutureResult<String, FlowyError>;
fn export(&self) -> FutureResult<String, FlowyError>;
fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError>;
fn close(&self);
@ -110,7 +110,7 @@ impl DocumentManager {
let _ = editor
.compose_local_operations(Bytes::from(payload.operations_str))
.await?;
let operations_str = editor.get_operations_str().await?;
let operations_str = editor.export().await?;
Ok(DocumentOperationsPB {
doc_id: payload.doc_id.clone(),
operations_str,
@ -198,7 +198,7 @@ impl DocumentManager {
Arc::new(editor)
} else {
let editor =
OldDocumentEditor::new(doc_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?;
DeltaDocumentEditor::new(doc_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?;
Arc::new(editor)
};
self.editor_map.insert(doc_id, editor.clone());

View File

@ -25,7 +25,7 @@ use std::any::Any;
use std::sync::Arc;
use tokio::sync::{mpsc, oneshot};
pub struct OldDocumentEditor {
pub struct DeltaDocumentEditor {
pub doc_id: String,
#[allow(dead_code)]
rev_manager: Arc<RevisionManager>,
@ -34,7 +34,7 @@ pub struct OldDocumentEditor {
edit_cmd_tx: EditorCommandSender,
}
impl OldDocumentEditor {
impl DeltaDocumentEditor {
#[allow(unused_variables)]
pub(crate) async fn new(
doc_id: &str,
@ -146,8 +146,8 @@ impl OldDocumentEditor {
}
}
impl DocumentEditor for Arc<OldDocumentEditor> {
fn get_operations_str(&self) -> FutureResult<String, FlowyError> {
impl DocumentEditor for Arc<DeltaDocumentEditor> {
fn export(&self) -> FutureResult<String, FlowyError> {
let (ret, rx) = oneshot::channel::<CollaborateResult<String>>();
let msg = EditorCommand::GetOperationsString { ret };
let edit_cmd_tx = self.edit_cmd_tx.clone();
@ -197,7 +197,7 @@ impl DocumentEditor for Arc<OldDocumentEditor> {
self
}
}
impl std::ops::Drop for OldDocumentEditor {
impl std::ops::Drop for DeltaDocumentEditor {
fn drop(&mut self) {
tracing::trace!("{} DocumentEditor was dropped", self.doc_id)
}
@ -225,7 +225,7 @@ fn spawn_edit_queue(
}
#[cfg(feature = "flowy_unit_test")]
impl OldDocumentEditor {
impl DeltaDocumentEditor {
pub async fn document_operations(&self) -> FlowyResult<TextOperations> {
let (ret, rx) = oneshot::channel::<CollaborateResult<TextOperations>>();
let msg = EditorCommand::GetOperations { ret };

View File

@ -14,7 +14,7 @@ async fn text_block_sync_current_rev_id_check() {
AssertNextSyncRevId(None),
AssertJson(r#"[{"insert":"123\n"}]"#),
];
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
@ -28,7 +28,7 @@ async fn text_block_sync_state_check() {
AssertRevisionState(3, RevisionState::Ack),
AssertJson(r#"[{"insert":"123\n"}]"#),
];
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
@ -40,7 +40,7 @@ async fn text_block_sync_insert_test() {
AssertJson(r#"[{"insert":"123\n"}]"#),
AssertNextSyncRevId(None),
];
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
@ -52,7 +52,7 @@ async fn text_block_sync_insert_in_chinese() {
InsertText("", offset),
AssertJson(r#"[{"insert":"你好\n"}]"#),
];
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
@ -64,7 +64,7 @@ async fn text_block_sync_insert_with_emoji() {
InsertText("☺️", offset),
AssertJson(r#"[{"insert":"😁☺️\n"}]"#),
];
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
@ -76,7 +76,7 @@ async fn text_block_sync_delete_in_english() {
Delete(Interval::new(0, 2)),
AssertJson(r#"[{"insert":"3\n"}]"#),
];
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
@ -89,7 +89,7 @@ async fn text_block_sync_delete_in_chinese() {
Delete(Interval::new(0, offset)),
AssertJson(r#"[{"insert":"好\n"}]"#),
];
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
@ -101,5 +101,5 @@ async fn text_block_sync_replace_test() {
Replace(Interval::new(0, 3), "abc"),
AssertJson(r#"[{"insert":"abc\n"}]"#),
];
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
}

View File

@ -1,4 +1,4 @@
use flowy_document::old_editor::editor::OldDocumentEditor;
use flowy_document::old_editor::editor::DeltaDocumentEditor;
use flowy_document::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS;
use flowy_revision::disk::RevisionState;
use flowy_test::{helper::ViewTest, FlowySDKTest};
@ -17,18 +17,18 @@ pub enum EditorScript {
AssertJson(&'static str),
}
pub struct OldDocumentEditorTest {
pub struct DeltaDocumentEditorTest {
pub sdk: FlowySDKTest,
pub editor: Arc<OldDocumentEditor>,
pub editor: Arc<DeltaDocumentEditor>,
}
impl OldDocumentEditorTest {
impl DeltaDocumentEditorTest {
pub async fn new() -> Self {
let sdk = FlowySDKTest::default();
let _ = sdk.init_user().await;
let test = ViewTest::new_document_view(&sdk).await;
let document_editor = sdk.document_manager.open_document_editor(&test.view.id).await.unwrap();
let editor = match document_editor.as_any().downcast_ref::<Arc<OldDocumentEditor>>() {
let editor = match document_editor.as_any().downcast_ref::<Arc<DeltaDocumentEditor>>() {
None => panic!(),
Some(editor) => editor.clone(),
};

View File

@ -174,7 +174,7 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
let manager = self.0.clone();
FutureResult::new(async move {
let editor = manager.open_document_editor(view_id).await?;
let delta_bytes = Bytes::from(editor.get_operations_str().await?);
let delta_bytes = Bytes::from(editor.export().await?);
Ok(delta_bytes)
})
}

View File

@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
/// The path of Node A-1 will be [0,0]
/// The path of Node A-2 will be [0,1]
/// The path of Node B-2 will be [1,1]
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)]
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug, Default)]
pub struct Path(pub Vec<usize>);
impl Path {

View File

@ -1,6 +1,6 @@
use super::{Changeset, NodeOperations};
use crate::core::attributes::AttributeHashMap;
use crate::core::{Interval, NodeData, NodeOperation, NodeTree, Path};
use crate::core::{NodeData, NodeOperation, NodeTree, Path};
use crate::errors::OTError;
use indextree::NodeId;
@ -93,8 +93,8 @@ impl std::ops::DerefMut for Transaction {
pub enum Extension {
Empty,
TextSelection {
before_selection: Interval,
after_selection: Interval,
before_selection: Selection,
after_selection: Selection,
},
}
@ -110,6 +110,18 @@ impl Extension {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Selection {
start: Position,
end: Position,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Position {
path: Path,
offset: usize,
}
pub struct TransactionBuilder<'a> {
node_tree: &'a NodeTree,
operations: NodeOperations,

View File

@ -1,5 +1,5 @@
use lib_ot::core::{
AttributeBuilder, Changeset, Extension, Interval, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path,
AttributeBuilder, Changeset, Extension, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path, Selection,
Transaction,
};
use lib_ot::text_delta::TextOperationBuilder;
@ -78,19 +78,20 @@ fn transaction_serialize_test() {
};
let mut transaction = Transaction::from_operations(vec![insert]);
transaction.extension = Extension::TextSelection {
before_selection: Interval::new(0, 1),
after_selection: Interval::new(1, 2),
before_selection: Selection::default(),
after_selection: Selection::default(),
};
let json = serde_json::to_string(&transaction).unwrap();
assert_eq!(
json,
r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":0,"end":1},"after_selection":{"start":1,"end":2}}}"#
r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}},"after_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}}}}"#
);
}
#[test]
fn transaction_deserialize_test() {
let json = r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":0,"end":1},"after_selection":{"start":1,"end":2}}}"#;
let json = r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}},"after_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}}}}"#;
let transaction: Transaction = serde_json::from_str(json).unwrap();
assert_eq!(transaction.operations.len(), 1);
}