[rust]: fix delta serial bugs & add some tests

This commit is contained in:
appflowy 2021-11-03 13:52:33 +08:00
parent 63d2ca27d3
commit ce2ccf7d0a
11 changed files with 152 additions and 49 deletions

View File

@ -41,7 +41,8 @@ class _DocPageState extends State<DocPage> {
],
child: BlocBuilder<DocBloc, DocState>(builder: (context, state) {
return state.loadState.map(
loading: (_) => const FlowyProgressIndicator(),
// loading: (_) => const FlowyProgressIndicator(),
loading: (_) => SizedBox.expand(child: Container(color: Colors.white)),
finish: (result) => result.successOrFail.fold(
(_) {
if (state.forceClose) {

View File

@ -4,7 +4,6 @@ import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -164,7 +163,6 @@ class MenuSharedState extends ChangeNotifier {
super.addListener(() {
if (_forcedOpenView != null) {
callback(_forcedOpenView!);
_forcedOpenView = null;
}
});
}
@ -181,6 +179,7 @@ class MenuSharedState extends ChangeNotifier {
selectedView = view;
notifyListeners();
}
_forcedOpenView = null;
}
set selectedView(View? view) {

View File

@ -74,6 +74,10 @@ impl DocController {
Ok(())
}
// the delta's data that contains attributes with null value will be considered
// as None e.g.
// json : {"retain":7,"attributes":{"bold":null}}
// deserialize delta: [ {retain: 7, attributes: {Bold: AttributeValue(None)}} ]
#[tracing::instrument(level = "debug", skip(self, delta), err)]
pub(crate) async fn edit_doc(&self, delta: DocDelta) -> Result<DocDelta, DocError> {
let edit_doc_ctx = self.cache.get(&delta.doc_id)?;

View File

@ -68,6 +68,7 @@ impl Document {
}
pub fn compose_delta(&mut self, delta: &Delta) -> Result<(), DocError> {
log::trace!("😁 {} compose {}", &self.delta.to_json(), delta.to_json());
let composed_delta = self.delta.compose(delta)?;
let mut undo_delta = delta.invert(&self.delta);
@ -88,7 +89,7 @@ impl Document {
self.history.record(undo_delta);
}
log::trace!("document delta: {}", &composed_delta);
log::trace!("😁😁 compose result: {}", composed_delta.to_json());
self.set_delta(composed_delta);
Ok(())
}

View File

@ -13,12 +13,12 @@ use tokio::sync::broadcast;
pub type RevIdReceiver = broadcast::Receiver<i64>;
pub type RevIdSender = broadcast::Sender<i64>;
pub struct RevisionContext {
pub struct RevisionRecord {
pub revision: Revision,
pub state: RevState,
}
impl RevisionContext {
impl RevisionRecord {
pub fn new(revision: Revision) -> Self {
Self {
revision,

View File

@ -19,10 +19,10 @@ use tokio::{
pub struct RevisionStore {
doc_id: String,
persistence: Arc<Persistence>,
revs_map: Arc<DashMap<i64, RevisionContext>>,
revs_map: Arc<DashMap<i64, RevisionRecord>>,
pending_tx: PendingSender,
pending_revs: Arc<RwLock<VecDeque<PendingRevId>>>,
delay_save: RwLock<Option<JoinHandle<()>>>,
defer_save_oper: RwLock<Option<JoinHandle<()>>>,
server: Arc<dyn RevisionServer>,
}
@ -45,7 +45,7 @@ impl RevisionStore {
revs_map,
pending_revs,
pending_tx,
delay_save: RwLock::new(None),
defer_save_oper: RwLock::new(None),
server,
});
@ -75,7 +75,7 @@ impl RevisionStore {
let pending_rev = PendingRevId::new(revision.rev_id, sender);
self.pending_revs.write().await.push_back(pending_rev);
self.revs_map.insert(revision.rev_id, RevisionContext::new(revision));
self.revs_map.insert(revision.rev_id, RevisionRecord::new(revision));
let _ = self.pending_tx.send(PendingMsg::Revision { ret: receiver });
self.save_revisions().await;
@ -94,7 +94,7 @@ impl RevisionStore {
}
async fn save_revisions(&self) {
if let Some(handler) = self.delay_save.write().await.take() {
if let Some(handler) = self.defer_save_oper.write().await.take() {
handler.abort();
}
@ -105,7 +105,7 @@ impl RevisionStore {
let revs_map = self.revs_map.clone();
let persistence = self.persistence.clone();
*self.delay_save.write().await = Some(tokio::spawn(async move {
*self.defer_save_oper.write().await = Some(tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(300)).await;
let ids = revs_map.iter().map(|kv| kv.key().clone()).collect::<Vec<i64>>();
let revisions_state = revs_map
@ -194,7 +194,6 @@ async fn fetch_from_local(doc_id: &str, persistence: Arc<Persistence>) -> DocRes
},
}
}
Result::<Doc, DocError>::Ok(Doc {
id: doc_id,
data: delta.to_json(),

View File

@ -1,6 +1,7 @@
use crate::editor::{TestBuilder, TestOp::*};
use flowy_document::services::doc::{FlowyDoc, PlainDoc};
use flowy_ot::core::{Interval, NEW_LINE, WHITESPACE};
use flowy_ot::core::{Delta, DeltaBuilder, Interval, OperationTransformable, NEW_LINE, WHITESPACE};
use std::str::FromStr;
#[test]
fn attributes_bold_added() {
@ -736,3 +737,42 @@ fn attributes_preserve_list_format_on_merge() {
TestBuilder::new().run_script::<FlowyDoc>(ops);
}
#[test]
fn delta_compose() {
let mut delta = Delta::from_json(r#"[{"insert":"\n"}]"#).unwrap();
let deltas = vec![
Delta::from_json(r#"[{"retain":1,"attributes":{"list":"unchecked"}}]"#).unwrap(),
Delta::from_json(r#"[{"insert":"a"}]"#).unwrap(),
Delta::from_json(r#"[{"retain":1},{"insert":"\n","attributes":{"list":"unchecked"}}]"#).unwrap(),
Delta::from_json(r#"[{"retain":2},{"retain":1,"attributes":{"list":""}}]"#).unwrap(),
];
for d in deltas {
delta = delta.compose(&d).unwrap();
}
assert_eq!(
delta.to_json(),
r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\n"}]"#
);
let ops = vec![
AssertDocJson(0, r#"[{"insert":"\n"}]"#),
Insert(0, "a", 0),
AssertDocJson(0, r#"[{"insert":"a\n"}]"#),
Bullet(0, Interval::new(0, 1), true),
AssertDocJson(0, r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
Insert(0, NEW_LINE, 1),
AssertDocJson(
0,
r#"[{"insert":"a"},{"insert":"\n\n","attributes":{"list":"bullet"}}]"#,
),
Insert(0, NEW_LINE, 2),
AssertDocJson(
0,
r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"\n"}]"#,
),
];
TestBuilder::new().run_script::<FlowyDoc>(ops);
}

View File

@ -105,16 +105,20 @@ impl TestBuilder {
TestOp::Insert(delta_i, s, index) => {
let document = &mut self.documents[*delta_i];
let delta = document.insert(*index, s).unwrap();
log::debug!("Insert delta: {}", delta.to_json());
self.deltas.insert(*delta_i, Some(delta));
},
TestOp::Delete(delta_i, iv) => {
let document = &mut self.documents[*delta_i];
let delta = document.replace(*iv, "").unwrap();
log::trace!("Delete delta: {}", delta.to_json());
self.deltas.insert(*delta_i, Some(delta));
},
TestOp::Replace(delta_i, iv, s) => {
let document = &mut self.documents[*delta_i];
let delta = document.replace(*iv, s).unwrap();
log::trace!("Replace delta: {}", delta.to_json());
self.deltas.insert(*delta_i, Some(delta));
},
TestOp::InsertBold(delta_i, s, iv) => {
@ -126,6 +130,7 @@ impl TestBuilder {
let document = &mut self.documents[*delta_i];
let attribute = Attribute::Bold(*enable);
let delta = document.format(*iv, attribute).unwrap();
log::trace!("Bold delta: {}", delta.to_json());
self.deltas.insert(*delta_i, Some(delta));
},
TestOp::Italic(delta_i, iv, enable) => {
@ -135,24 +140,29 @@ impl TestBuilder {
false => Attribute::Italic(false),
};
let delta = document.format(*iv, attribute).unwrap();
log::trace!("Italic delta: {}", delta.to_json());
self.deltas.insert(*delta_i, Some(delta));
},
TestOp::Header(delta_i, iv, level) => {
let document = &mut self.documents[*delta_i];
let attribute = Attribute::Header(*level);
let delta = document.format(*iv, attribute).unwrap();
log::trace!("Header delta: {}", delta.to_json());
self.deltas.insert(*delta_i, Some(delta));
},
TestOp::Link(delta_i, iv, link) => {
let document = &mut self.documents[*delta_i];
let attribute = Attribute::Link(link.to_owned());
let delta = document.format(*iv, attribute).unwrap();
log::trace!("Link delta: {}", delta.to_json());
self.deltas.insert(*delta_i, Some(delta));
},
TestOp::Bullet(delta_i, iv, enable) => {
let document = &mut self.documents[*delta_i];
let attribute = Attribute::Bullet(*enable);
let delta = document.format(*iv, attribute).unwrap();
log::debug!("Bullet delta: {}", delta.to_json());
self.deltas.insert(*delta_i, Some(delta));
},
TestOp::Transform(delta_a_i, delta_b_i) => {

View File

@ -82,8 +82,21 @@ fn delta_deserialize_null_test() {
let json = r#"[
{"retain":7,"attributes":{"bold":null}}
]"#;
let delta = Delta::from_json(json).unwrap();
println!("{}", delta);
let delta1 = Delta::from_json(json).unwrap();
let mut attribute = Attribute::Bold(true);
attribute.value = AttributeValue(None);
let delta2 = DeltaBuilder::new().retain_with_attributes(7, attribute.into()).build();
assert_eq!(delta2.to_json(), r#"[{"retain":7,"attributes":{"bold":""}}]"#);
assert_eq!(delta1, delta2);
}
#[test]
fn delta_serde_null_test() {
let mut attribute = Attribute::Bold(true);
attribute.value = AttributeValue(None);
assert_eq!(attribute.to_json(), r#"{"bold":""}"#);
}
#[test]

View File

@ -3,8 +3,10 @@
use crate::{block_attribute, core::Attributes, ignore_attribute, inline_attribute, list_attribute};
use lazy_static::lazy_static;
use serde_json::Error;
use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator};
use strum_macros::Display;
#[derive(Debug, Clone)]
pub struct Attribute {
pub key: AttributeKey,
@ -41,6 +43,16 @@ impl Attribute {
list_attribute!(Ordered, "ordered");
list_attribute!(Checked, "checked");
list_attribute!(UnChecked, "unchecked");
pub fn to_json(&self) -> String {
match serde_json::to_string(self) {
Ok(json) => json,
Err(e) => {
log::error!("Attribute serialize to str failed: {}", e);
"".to_owned()
},
}
}
}
impl fmt::Display for Attribute {
@ -100,7 +112,7 @@ pub enum AttributeKey {
// pub trait AttributeValueData<'a>: Serialize + Deserialize<'a> {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AttributeValue(pub(crate) Option<String>);
pub struct AttributeValue(pub Option<String>);
impl std::convert::From<&usize> for AttributeValue {
fn from(val: &usize) -> Self { AttributeValue::from(*val) }

View File

@ -1,9 +1,10 @@
#[rustfmt::skip]
use crate::core::AttributeValue;
use crate::core::{AttributeKey, Attributes};
use crate::core::{Attribute, AttributeKey, Attributes};
use serde::{
de,
de::{MapAccess, Visitor},
ser,
ser::SerializeMap,
Deserialize,
Deserializer,
@ -12,6 +13,17 @@ use serde::{
};
use std::fmt;
impl Serialize for Attribute {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
let _ = serial_attribute(&mut map, &self.key, &self.value)?;
map.end()
}
}
impl Serialize for Attributes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -23,42 +35,53 @@ impl Serialize for Attributes {
let mut map = serializer.serialize_map(Some(self.inner.len()))?;
for (k, v) in &self.inner {
if let Some(v) = &v.0 {
match k {
AttributeKey::Bold
| AttributeKey::Italic
| AttributeKey::Underline
| AttributeKey::StrikeThrough
| AttributeKey::CodeBlock
| AttributeKey::QuoteBlock => match &v.parse::<bool>() {
Ok(value) => map.serialize_entry(k, value)?,
Err(e) => log::error!("Serial {:?} failed. {:?}", k, e),
},
AttributeKey::Font
| AttributeKey::Size
| AttributeKey::Header
| AttributeKey::Indent
| AttributeKey::Width
| AttributeKey::Height => match &v.parse::<i32>() {
Ok(value) => map.serialize_entry(k, value)?,
Err(e) => log::error!("Serial {:?} failed. {:?}", k, e),
},
AttributeKey::Link
| AttributeKey::Color
| AttributeKey::Background
| AttributeKey::Align
| AttributeKey::List => {
map.serialize_entry(k, v)?;
},
}
}
let _ = serial_attribute(&mut map, k, v)?;
}
map.end()
}
}
fn serial_attribute<S, E>(map_serializer: &mut S, key: &AttributeKey, value: &AttributeValue) -> Result<(), E>
where
S: SerializeMap,
E: From<<S as SerializeMap>::Error>,
{
if let Some(v) = &value.0 {
match key {
AttributeKey::Bold
| AttributeKey::Italic
| AttributeKey::Underline
| AttributeKey::StrikeThrough
| AttributeKey::CodeBlock
| AttributeKey::QuoteBlock => match &v.parse::<bool>() {
Ok(value) => map_serializer.serialize_entry(&key, value)?,
Err(e) => log::error!("Serial {:?} failed. {:?}", &key, e),
},
AttributeKey::Font
| AttributeKey::Size
| AttributeKey::Header
| AttributeKey::Indent
| AttributeKey::Width
| AttributeKey::Height => match &v.parse::<i32>() {
Ok(value) => map_serializer.serialize_entry(&key, value)?,
Err(e) => log::error!("Serial {:?} failed. {:?}", &key, e),
},
AttributeKey::Link
| AttributeKey::Color
| AttributeKey::Background
| AttributeKey::Align
| AttributeKey::List => {
map_serializer.serialize_entry(&key, v)?;
},
}
} else {
map_serializer.serialize_entry(&key, "")?;
}
Ok(())
}
impl<'de> Deserialize<'de> for Attributes {
fn deserialize<D>(deserializer: D) -> Result<Attributes, D::Error>
where
@ -190,6 +213,7 @@ impl<'de> Deserialize<'de> for AttributeValue {
where
E: de::Error,
{
// the value that contains null will be processed here.
Ok(AttributeValue(None))
}