From ca81c990bf4c9e848e7ab80d1658f0bd469e546c Mon Sep 17 00:00:00 2001 From: appflowy Date: Tue, 10 Aug 2021 11:22:57 +0800 Subject: [PATCH] fix undo redo bugs, merge last undo if need --- .../lib/src/model/document/history.dart | 6 +- rust-lib/flowy-ot/Cargo.toml | 1 + rust-lib/flowy-ot/src/client/document.rs | 141 ++++++++++++------ rust-lib/flowy-ot/src/client/history.rs | 12 +- .../src/core/attributes/attributes.rs | 31 ++-- rust-lib/flowy-ot/src/core/attributes/data.rs | 4 +- rust-lib/flowy-ot/src/core/delta.rs | 132 +++++++++------- .../flowy-ot/src/core/operation/operation.rs | 64 +++++--- rust-lib/flowy-ot/tests/attribute_test.rs | 26 +++- rust-lib/flowy-ot/tests/helper/mod.rs | 19 ++- rust-lib/flowy-ot/tests/invert_test.rs | 110 -------------- rust-lib/flowy-ot/tests/op_test.rs | 115 ++++++++++++++ rust-lib/flowy-ot/tests/undo_redo_test.rs | 134 +++++++++++++++-- 13 files changed, 514 insertions(+), 281 deletions(-) diff --git a/app_flowy/packages/flowy_editor/lib/src/model/document/history.dart b/app_flowy/packages/flowy_editor/lib/src/model/document/history.dart index 22b287f970..35fef76752 100644 --- a/app_flowy/packages/flowy_editor/lib/src/model/document/history.dart +++ b/app_flowy/packages/flowy_editor/lib/src/model/document/history.dart @@ -51,10 +51,12 @@ class History { var undoDelta = change.invert(before); final timestamp = DateTime.now().millisecondsSinceEpoch; - if (timestamp - lastRecorded < minRecordThreshold && - stack.undo.isNotEmpty) { + if (stack.undo.isNotEmpty) { final lastDelta = stack.undo.removeLast(); + print("undoDelta: $undoDelta"); + print("lastDelta: $lastDelta"); undoDelta = undoDelta.compose(lastDelta); + print("compose result: $undoDelta"); } else { lastRecorded = timestamp; } diff --git a/rust-lib/flowy-ot/Cargo.toml b/rust-lib/flowy-ot/Cargo.toml index 9e2ff6a547..687ee975c2 100644 --- a/rust-lib/flowy-ot/Cargo.toml +++ b/rust-lib/flowy-ot/Cargo.toml @@ -12,6 +12,7 @@ serde_json = {version = "1.0"} derive_more = {version = "0.99", features = ["display"]} log = "0.4" color-eyre = { version = "0.5", default-features = false } +chrono = "0.4.19" [dev-dependencies] criterion = "0.3" diff --git a/rust-lib/flowy-ot/src/client/document.rs b/rust-lib/flowy-ot/src/client/document.rs index 03bcca7d1e..cfeab02b05 100644 --- a/rust-lib/flowy-ot/src/client/document.rs +++ b/rust-lib/flowy-ot/src/client/document.rs @@ -1,22 +1,16 @@ use crate::{ client::{History, RevId, UndoResult}, - core::{ - Attribute, - Attributes, - AttributesDataRule, - AttrsBuilder, - Builder, - Delta, - Interval, - Operation, - }, + core::*, errors::{ErrorBuilder, OTError, OTErrorCode::*}, }; +pub const RECORD_THRESHOLD: usize = 400; // in milliseconds + pub struct Document { data: Delta, history: History, rev_id_counter: usize, + last_edit_time: usize, } impl Document { @@ -26,6 +20,7 @@ impl Document { data: delta, history: History::new(), rev_id_counter: 1, + last_edit_time: 0, } } @@ -42,10 +37,12 @@ impl Document { if attributes == Attributes::Empty { attributes = Attributes::Follow; } + let mut delta = Delta::new(); let insert = Builder::insert(text).attributes(attributes).build(); let interval = Interval::new(index, index); + delta.add(insert); - self.update_with_op(insert, interval) + self.update_with_op(&delta, interval) } pub fn format( @@ -68,15 +65,14 @@ impl Document { pub fn undo(&mut self) -> Result { match self.history.undo() { - None => Err(ErrorBuilder::new(UndoFail).build()), + None => Err(ErrorBuilder::new(UndoFail) + .msg("Undo stack is empty") + .build()), Some(undo_delta) => { - log::debug!("undo: {:?}", undo_delta); - let composed_delta = self.data.compose(&undo_delta)?; - let redo_delta = undo_delta.invert(&self.data); - log::debug!("computed redo: {:?}", redo_delta); - let result = UndoResult::success(composed_delta.target_len as usize); - self.data = composed_delta; - self.history.add_redo(redo_delta); + let (new_delta, inverted_delta) = self.invert_change(&undo_delta)?; + let result = UndoResult::success(new_delta.target_len as usize); + self.data = new_delta; + self.history.add_redo(inverted_delta); Ok(result) }, @@ -87,22 +83,29 @@ impl Document { match self.history.redo() { None => Err(ErrorBuilder::new(RedoFail).build()), Some(redo_delta) => { - log::debug!("redo: {:?}", redo_delta); - let new_delta = self.data.compose(&redo_delta)?; + let (new_delta, inverted_delta) = self.invert_change(&redo_delta)?; let result = UndoResult::success(new_delta.target_len as usize); - let undo_delta = redo_delta.invert(&self.data); - log::debug!("computed undo: {:?}", undo_delta); self.data = new_delta; - self.history.add_undo(undo_delta); + self.history.add_undo(inverted_delta); Ok(result) }, } } - pub fn delete(&mut self, interval: Interval) -> Result<(), OTError> { - let delete = Builder::delete(interval.size()).build(); - self.update_with_op(delete, interval) + pub fn replace(&mut self, interval: Interval, s: &str) -> Result<(), OTError> { + let mut delta = Delta::default(); + if !s.is_empty() { + let insert = Builder::insert(s).attributes(Attributes::Follow).build(); + delta.add(insert); + } + + if !interval.is_empty() { + let delete = Builder::delete(interval.size()).build(); + delta.add(delete); + } + + self.update_with_op(&delta, interval) } pub fn to_json(&self) -> String { self.data.to_json() } @@ -113,7 +116,7 @@ impl Document { pub fn set_data(&mut self, data: Delta) { self.data = data; } - fn update_with_op(&mut self, op: Operation, interval: Interval) -> Result<(), OTError> { + fn update_with_op(&mut self, delta: &Delta, interval: Interval) -> Result<(), OTError> { let mut new_delta = Delta::default(); let (prefix, interval, suffix) = split_length_with_interval(self.data.target_len, interval); @@ -122,33 +125,26 @@ impl Document { let intervals = split_interval_with_delta(&self.data, &prefix); intervals.into_iter().for_each(|i| { let attributes = self.data.get_attributes(i); - log::debug!("prefix attribute: {:?}, interval: {:?}", attributes, i); + log::trace!("prefix attribute: {:?}, interval: {:?}", attributes, i); new_delta.retain(i.size() as usize, attributes); }); } - log::debug!("add new op: {:?}", op); - new_delta.add(op); + delta.ops.iter().for_each(|op| { + new_delta.add(op.clone()); + }); // suffix if suffix.is_empty() == false { let intervals = split_interval_with_delta(&self.data, &suffix); intervals.into_iter().for_each(|i| { let attributes = self.data.get_attributes(i); - log::debug!("suffix attribute: {:?}, interval: {:?}", attributes, i); + log::trace!("suffix attribute: {:?}, interval: {:?}", attributes, i); new_delta.retain(i.size() as usize, attributes); }); } - // c = a.compose(b) - // d = b.invert(a) - // a = c.compose(d) - let composed_delta = self.data.compose(&new_delta)?; - let undo_delta = new_delta.invert(&self.data); - - self.rev_id_counter += 1; - self.history.record(undo_delta); - self.data = composed_delta; + self.data = self.record_change(&new_delta)?; Ok(()) } @@ -159,7 +155,7 @@ impl Document { ) -> Result<(), OTError> { let old_attributes = self.data.get_attributes(interval); log::debug!( - "merge attributes: {:?}, with old: {:?}", + "combine attributes: {:?} : {:?}", attributes, old_attributes ); @@ -172,21 +168,54 @@ impl Document { Attributes::Empty => Attributes::Empty, }; - log::debug!("new attributes: {:?}", new_attributes); + log::debug!("combined result: {:?}", new_attributes); let retain = Builder::retain(interval.size()) .attributes(new_attributes) .build(); - log::debug!( - "Update delta with new attributes: {:?} at: {:?}", - retain, - interval - ); + let mut delta = Delta::new(); + delta.add(retain); - self.update_with_op(retain, interval) + self.update_with_op(&delta, interval) } fn next_rev_id(&self) -> RevId { RevId(self.rev_id_counter) } + + fn record_change(&mut self, delta: &Delta) -> Result { + let (composed_delta, mut undo_delta) = self.invert_change(&delta)?; + self.rev_id_counter += 1; + + let now = chrono::Utc::now().timestamp_millis() as usize; + if now - self.last_edit_time < RECORD_THRESHOLD { + if let Some(last_delta) = self.history.undo() { + log::debug!("compose previous change"); + log::debug!("current = {}", undo_delta); + log::debug!("previous = {}", last_delta); + undo_delta = undo_delta.compose(&last_delta)?; + } + } else { + self.last_edit_time = now; + } + + if !undo_delta.is_empty() { + log::debug!("record change: {}", undo_delta); + self.history.record(undo_delta); + } + + Ok(composed_delta) + } + + fn invert_change(&self, change: &Delta) -> Result<(Delta, Delta), OTError> { + // c = a.compose(b) + // d = b.invert(a) + // a = c.compose(d) + log::debug!("πŸ‘‰invert change {}", change); + let new_delta = self.data.compose(change)?; + let mut inverted_delta = change.invert(&self.data); + // trim(&mut inverted_delta); + + Ok((new_delta, inverted_delta)) + } } fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) { @@ -216,3 +245,17 @@ fn split_interval_with_delta(delta: &Delta, interval: &Interval) -> Vec false, + Some(op) => match op { + Operation::Delete(_) => false, + Operation::Retain(retain) => retain.is_plain(), + Operation::Insert(_) => false, + }, + }; + if remove_last { + delta.ops.pop(); + } +} diff --git a/rust-lib/flowy-ot/src/client/history.rs b/rust-lib/flowy-ot/src/client/history.rs index cec10a0734..a1b0695280 100644 --- a/rust-lib/flowy-ot/src/client/history.rs +++ b/rust-lib/flowy-ot/src/client/history.rs @@ -36,7 +36,7 @@ impl UndoResult { pub struct History { cur_undo: usize, undos: Vec, - redos: Vec, + redoes: Vec, capacity: usize, } @@ -45,25 +45,25 @@ impl History { History { cur_undo: 1, undos: Vec::new(), - redos: Vec::new(), + redoes: Vec::new(), capacity: 20, } } pub fn can_undo(&self) -> bool { !self.undos.is_empty() } - pub fn can_redo(&self) -> bool { !self.redos.is_empty() } + pub fn can_redo(&self) -> bool { !self.redoes.is_empty() } pub fn add_undo(&mut self, delta: Delta) { self.undos.push(delta); } - pub fn add_redo(&mut self, delta: Delta) { self.redos.push(delta); } + pub fn add_redo(&mut self, delta: Delta) { self.redoes.push(delta); } pub fn record(&mut self, delta: Delta) { if delta.ops.is_empty() { return; } - self.redos.clear(); + self.redoes.clear(); self.add_undo(delta); if self.undos.len() > self.capacity { @@ -84,7 +84,7 @@ impl History { return None; } - let delta = self.redos.pop().unwrap(); + let delta = self.redoes.pop().unwrap(); Some(delta) } } diff --git a/rust-lib/flowy-ot/src/core/attributes/attributes.rs b/rust-lib/flowy-ot/src/core/attributes/attributes.rs index d3039daabd..10c249ee0b 100644 --- a/rust-lib/flowy-ot/src/core/attributes/attributes.rs +++ b/rust-lib/flowy-ot/src/core/attributes/attributes.rs @@ -1,5 +1,5 @@ use crate::core::{Attribute, AttributesData, AttributesRule, Operation}; -use std::{collections::HashMap, fmt}; +use std::{collections::HashMap, fmt, fmt::Formatter}; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] #[serde(untagged)] @@ -11,6 +11,16 @@ pub enum Attributes { Empty, } +impl fmt::Display for Attributes { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Attributes::Follow => f.write_str("follow"), + Attributes::Custom(data) => f.write_fmt(format_args!("{:?}", data.inner)), + Attributes::Empty => f.write_str("empty"), + } + } +} + impl Attributes { pub fn data(&self) -> Option { match self { @@ -25,23 +35,6 @@ impl std::default::Default for Attributes { fn default() -> Self { Attributes::Empty } } -impl fmt::Display for Attributes { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Attributes::Follow => { - f.write_str("")?; - }, - Attributes::Custom(data) => { - f.write_fmt(format_args!("{:?}", data.inner))?; - }, - Attributes::Empty => { - f.write_str("")?; - }, - } - Ok(()) - } -} - pub(crate) fn attributes_from(operation: &Option) -> Option { match operation { None => None, @@ -139,7 +132,7 @@ fn compose_attributes(left: Attributes, right: Attributes) -> Attributes { }; log::trace!("composed_attributes: a: {:?}", attr); - attr.apply_rule() + attr } fn transform_attributes(left: Attributes, right: Attributes) -> Attributes { diff --git a/rust-lib/flowy-ot/src/core/attributes/data.rs b/rust-lib/flowy-ot/src/core/attributes/data.rs index 6cc1d16f59..31fdba17b1 100644 --- a/rust-lib/flowy-ot/src/core/attributes/data.rs +++ b/rust-lib/flowy-ot/src/core/attributes/data.rs @@ -18,9 +18,7 @@ impl AttributesData { } } - pub fn is_plain(&self) -> bool { - self.inner.values().filter(|v| !should_remove(v)).count() == 0 - } + pub fn is_plain(&self) -> bool { self.inner.is_empty() } pub fn remove(&mut self, attribute: &Attribute) { self.inner.insert(attribute.clone(), REMOVE_FLAG.to_owned()); diff --git a/rust-lib/flowy-ot/src/core/delta.rs b/rust-lib/flowy-ot/src/core/delta.rs index b8a1f6be30..d0af7281a8 100644 --- a/rust-lib/flowy-ot/src/core/delta.rs +++ b/rust-lib/flowy-ot/src/core/delta.rs @@ -43,10 +43,12 @@ impl> From for Delta { impl fmt::Display for Delta { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?; - // for op in &self.ops { - // f.write_fmt(format_args!("{}", op)); - // } + // f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?; + f.write_str("[ ")?; + for op in &self.ops { + f.write_fmt(format_args!("{} ", op)); + } + f.write_str("]")?; Ok(()) } } @@ -184,7 +186,11 @@ impl Delta { match retain.cmp(&o_retain) { Ordering::Less => { new_delta.retain(retain.n, composed_attrs); - next_op2 = Some(Builder::retain(o_retain.n - retain.n).build()); + next_op2 = Some( + Builder::retain(o_retain.n - retain.n) + .attributes(o_retain.attributes.clone()) + .build(), + ); next_op1 = ops1.next(); }, std::cmp::Ordering::Equal => { @@ -225,11 +231,13 @@ impl Delta { } }, (Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => { - let composed_attrs = compose_operation(&next_op1, &next_op2); + let mut composed_attrs = compose_operation(&next_op1, &next_op2); + composed_attrs = composed_attrs.apply_rule(); + log::debug!( - "[insert:{} - retain:{}]: {:?}", - insert.s, - o_retain.n, + "compose: [{} - {}], composed_attrs: {}", + insert, + o_retain, composed_attrs ); match (insert.num_chars()).cmp(o_retain) { @@ -481,7 +489,9 @@ impl Delta { if other.is_empty() { return inverted; } - + log::debug!("🌜Calculate invert delta"); + log::debug!("current: {}", self); + log::debug!("other: {}", other); let mut index = 0; for op in &self.ops { let len: usize = op.length() as usize; @@ -494,19 +504,25 @@ impl Delta { match op.has_attribute() { true => invert_from_other(&mut inverted, other, op, index, index + len), false => { - log::debug!("invert retain op: {:?}", op); + log::debug!( + "invert retain: {} by retain {} {}", + op, + len, + op.get_attributes() + ); inverted.retain(len as usize, op.get_attributes()) }, } index += len; }, Operation::Insert(_) => { - log::debug!("invert insert op: {:?}", op); + log::debug!("invert insert: {} by delete {}", op, len); inverted.delete(len as usize); }, } } + log::debug!("πŸŒ›invert result: {}", inverted); inverted } @@ -523,7 +539,8 @@ impl Delta { pub fn is_empty(&self) -> bool { self.ops.is_empty() } pub fn ops_in_interval(&self, mut interval: Interval) -> Vec { - log::debug!("ops in delta: {:?}, at {:?}", self, interval); + log::debug!("try get ops in delta: {} at {}", self, interval); + let mut ops: Vec = Vec::with_capacity(self.ops.len()); let mut ops_iter = self.ops.iter(); let mut maybe_next_op = ops_iter.next(); @@ -534,32 +551,32 @@ impl Delta { if offset < interval.start { let next_op_i = Interval::new(offset, offset + next_op.length()); let intersect = next_op_i.intersect(interval); - if !intersect.is_empty() { - // if the op's length larger than the interval size, just shrink the op to that - // interval Checkout the delta_get_ops_in_interval_3 test for more details. - // β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - // β”‚ 1 2 3 4 5 6 β”‚ - // β””β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β–²β”€β”€β”˜ - // β”‚ β”‚ - // [3, 5) - // op = "45" - if let Some(new_op) = next_op.shrink(intersect) { - offset += min(intersect.end, next_op.length()); - interval = Interval::new(offset, interval.end); + if intersect.is_empty() { + offset += next_op_i.size(); + } else { + if let Some(new_op) = next_op.shrink(intersect.translate_neg(offset)) { + // shrink the op to fit the intersect range + // β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + // β”‚ 1 2 3 4 5 6 β”‚ + // β””β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β–²β”€β”€β”˜ + // β”‚ β”‚ + // [3, 5) + // op = "45" ops.push(new_op); } - } else { - offset += next_op_i.size(); + offset = intersect.end; + interval = Interval::new(offset, interval.end); } } else { // the interval passed in the shrink function is base on the op not the delta. if let Some(new_op) = next_op.shrink(interval.translate_neg(offset)) { ops.push(new_op); } - // take the small value between interval.size() and next_op.length(). // for example: extract the ops from three insert ops with interval [2,5). the - // interval size is larger than the op. Run the - // delta_get_ops_in_interval_4 for more details. + // interval size is larger than the op. Moving the offset to extract each part. + // Each step would be the small value between interval.size() and + // next_op.length(). Checkout the delta_get_ops_in_interval_4 for more details. + // // β”Œβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β” // β”‚ 1 2 β”‚ β”‚ 3 4 β”‚ β”‚ 5 6 β”‚ // β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β–²β”€β”€β”€β”€β”˜ β””β”€β”€β”€β–²β”€β”€β”˜ @@ -570,6 +587,7 @@ impl Delta { } maybe_next_op = ops_iter.next(); } + log::debug!("did get ops : {:?}", ops); ops } @@ -614,36 +632,38 @@ impl Delta { } fn invert_from_other( - inverted: &mut Delta, + base: &mut Delta, other: &Delta, operation: &Operation, start: usize, end: usize, ) { - log::debug!("invert op: {:?} [{}:{}]", operation, start, end); - let ops = other.ops_in_interval(Interval::new(start, end)); - ops.into_iter().for_each(|other_op| { - match operation { - Operation::Delete(_) => { - log::debug!("add: {}", other_op); - inverted.add(other_op); - }, - Operation::Retain(_) => { - log::debug!( - "Start invert attributes: {:?}, {:?}", - operation.get_attributes(), - other_op.get_attributes() - ); - let inverted_attrs = - invert_attributes(operation.get_attributes(), other_op.get_attributes()); - log::debug!("End invert attributes: {:?}", inverted_attrs); - log::debug!("invert retain: {}, {}", other_op.length(), inverted_attrs); - inverted.retain(other_op.length(), inverted_attrs); - }, - Operation::Insert(_) => { - // Impossible to here - panic!() - }, - } + log::debug!("invert op: {} [{}:{}]", operation, start, end); + let other_ops = other.ops_in_interval(Interval::new(start, end)); + other_ops.into_iter().for_each(|other_op| match operation { + Operation::Delete(n) => { + log::debug!("invert delete: {} by add {}", n, other_op); + base.add(other_op); + }, + Operation::Retain(retain) => { + log::debug!( + "invert attributes: {:?}, {:?}", + operation.get_attributes(), + other_op.get_attributes() + ); + let inverted_attrs = + invert_attributes(operation.get_attributes(), other_op.get_attributes()); + log::debug!("invert result: {:?}", inverted_attrs); + log::debug!( + "invert retain: {} by retain len: {}, {}", + retain, + other_op.length(), + inverted_attrs + ); + base.retain(other_op.length(), inverted_attrs); + }, + Operation::Insert(_) => { + log::error!("Impossible to here. Insert operation should be treated as delete") + }, }); } diff --git a/rust-lib/flowy-ot/src/core/operation/operation.rs b/rust-lib/flowy-ot/src/core/operation/operation.rs index 05f92d9800..cb893dd01c 100644 --- a/rust-lib/flowy-ot/src/core/operation/operation.rs +++ b/rust-lib/flowy-ot/src/core/operation/operation.rs @@ -1,5 +1,6 @@ use crate::core::{Attributes, Builder, Interval}; use bytecount::num_chars; +use serde::__private::Formatter; use std::{ cmp::min, fmt, @@ -60,14 +61,6 @@ impl Operation { } } - pub fn is_plain(&self) -> bool { - match self.get_attributes() { - Attributes::Follow => true, - Attributes::Custom(data) => data.is_plain(), - Attributes::Empty => true, - } - } - pub fn length(&self) -> usize { match self { Operation::Delete(n) => *n, @@ -105,23 +98,19 @@ impl Operation { impl fmt::Display for Operation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("{"); match self { Operation::Delete(n) => { f.write_fmt(format_args!("delete: {}", n))?; }, Operation::Retain(r) => { - f.write_fmt(format_args!( - "retain: {}, attributes: {}", - r.n, r.attributes - ))?; + f.write_fmt(format_args!("{}", r))?; }, Operation::Insert(i) => { - f.write_fmt(format_args!( - "insert: {}, attributes: {}", - i.s, i.attributes - ))?; + f.write_fmt(format_args!("{}", i))?; }, } + f.write_str("}"); Ok(()) } } @@ -134,9 +123,23 @@ pub struct Retain { pub attributes: Attributes, } +impl fmt::Display for Retain { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!( + "retain: {}, attributes: {}", + self.n, self.attributes + )) + } +} + impl Retain { pub fn merge_or_new_op(&mut self, n: usize, attributes: Attributes) -> Option { - log::debug!("merge_retain_or_new_op: {:?}, {:?}", n, attributes); + log::debug!( + "merge_retain_or_new_op: len: {:?}, l: {} - r: {}", + n, + self.attributes, + attributes + ); match &attributes { Attributes::Follow => { @@ -146,16 +149,22 @@ impl Retain { }, Attributes::Custom(_) | Attributes::Empty => { if self.attributes == attributes { - log::debug!("Attribute equal"); self.n += n; None } else { - log::debug!("New retain op"); Some(Builder::retain(n).attributes(attributes).build()) } }, } } + + pub fn is_plain(&self) -> bool { + match &self.attributes { + Attributes::Follow => true, + Attributes::Custom(data) => data.is_plain(), + Attributes::Empty => true, + } + } } impl std::convert::From for Retain { @@ -186,6 +195,23 @@ pub struct Insert { pub attributes: Attributes, } +impl fmt::Display for Insert { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut s = self.s.clone(); + if s.ends_with("\n") { + s.pop(); + if s.is_empty() { + s = "new_line".to_owned(); + } + } + + f.write_fmt(format_args!( + "insert: {}, attributes: {}", + s, self.attributes + )) + } +} + impl Insert { pub fn as_bytes(&self) -> &[u8] { self.s.as_bytes() } diff --git a/rust-lib/flowy-ot/tests/attribute_test.rs b/rust-lib/flowy-ot/tests/attribute_test.rs index 9a4f8ea472..a35ba6d194 100644 --- a/rust-lib/flowy-ot/tests/attribute_test.rs +++ b/rust-lib/flowy-ot/tests/attribute_test.rs @@ -352,7 +352,7 @@ fn delta_compose_attr_delta_with_no_attr_delta_test() { } #[test] -fn delta_delete_heading() { +fn delta_replace_heading() { let ops = vec![ InsertBold(0, "123456", Interval::new(0, 6)), AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), @@ -364,7 +364,7 @@ fn delta_delete_heading() { } #[test] -fn delta_delete_trailing() { +fn delta_replace_trailing() { let ops = vec![ InsertBold(0, "123456", Interval::new(0, 6)), AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), @@ -376,7 +376,7 @@ fn delta_delete_trailing() { } #[test] -fn delta_delete_middle() { +fn delta_replace_middle() { let ops = vec![ InsertBold(0, "123456", Interval::new(0, 6)), AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), @@ -390,7 +390,7 @@ fn delta_delete_middle() { } #[test] -fn delta_delete_all() { +fn delta_replace_all() { let ops = vec![ InsertBold(0, "123456", Interval::new(0, 6)), AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), @@ -400,3 +400,21 @@ fn delta_delete_all() { OpTester::new().run_script(ops); } + +#[test] +fn delta_replace_with_text() { + let ops = vec![ + InsertBold(0, "123456", Interval::new(0, 6)), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + Replace(0, Interval::new(0, 3), "ab"), + AssertOpsJson( + 0, + r#"[ + {"insert":"ab"}, + {"insert":"456","attributes":{"bold":"true"}}] + "#, + ), + ]; + + OpTester::new().run_script(ops); +} diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index 235620c8cd..aa1c2df008 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -1,7 +1,7 @@ use derive_more::Display; use flowy_ot::{client::Document, core::*}; use rand::{prelude::*, Rng as WrappedRng}; -use std::sync::Once; +use std::{sync::Once, thread::sleep_ms, time::Duration}; #[derive(Clone, Debug, Display)] pub enum TestOp { @@ -19,6 +19,9 @@ pub enum TestOp { #[display(fmt = "Delete")] Delete(usize, Interval), + #[display(fmt = "Replace")] + Replace(usize, Interval, &'static str), + #[display(fmt = "Italic")] Italic(usize, Interval, bool), @@ -35,6 +38,9 @@ pub enum TestOp { #[display(fmt = "Redo")] Redo(usize), + #[display(fmt = "Wait")] + Wait(usize), + #[display(fmt = "AssertStr")] AssertStr(usize, &'static str), @@ -51,7 +57,7 @@ impl OpTester { static INIT: Once = Once::new(); INIT.call_once(|| { color_eyre::install().unwrap(); - std::env::set_var("RUST_LOG", "info"); + std::env::set_var("RUST_LOG", "debug"); env_logger::init(); }); @@ -71,7 +77,11 @@ impl OpTester { }, TestOp::Delete(delta_i, interval) => { let document = &mut self.documents[*delta_i]; - document.delete(*interval).unwrap(); + document.replace(*interval, "").unwrap(); + }, + TestOp::Replace(delta_i, interval, s) => { + let document = &mut self.documents[*delta_i]; + document.replace(*interval, s).unwrap(); }, TestOp::InsertBold(delta_i, s, interval) => { let document = &mut self.documents[*delta_i]; @@ -131,6 +141,9 @@ impl OpTester { TestOp::Redo(delta_i) => { self.documents[*delta_i].redo().unwrap(); }, + TestOp::Wait(mills_sec) => { + std::thread::sleep(Duration::from_millis(*mills_sec as u64)); + }, TestOp::AssertStr(delta_i, expected) => { assert_eq!(&self.documents[*delta_i].to_string(), expected); }, diff --git a/rust-lib/flowy-ot/tests/invert_test.rs b/rust-lib/flowy-ot/tests/invert_test.rs index 01018044e2..8ee59dddac 100644 --- a/rust-lib/flowy-ot/tests/invert_test.rs +++ b/rust-lib/flowy-ot/tests/invert_test.rs @@ -154,113 +154,3 @@ fn delta_invert_attribute_delta_with_attribute_delta() { ]; OpTester::new().run_script(ops); } - -#[test] -fn delta_get_ops_in_interval_1() { - let mut delta = Delta::default(); - let insert_a = Builder::insert("123").build(); - let insert_b = Builder::insert("4").build(); - - delta.add(insert_a.clone()); - delta.add(insert_b.clone()); - - assert_eq!( - delta.ops_in_interval(Interval::new(0, 4)), - vec![delta.ops.last().unwrap().clone()] - ); -} - -#[test] -fn delta_get_ops_in_interval_2() { - let mut delta = Delta::default(); - let insert_a = Builder::insert("123").build(); - let insert_b = Builder::insert("4").build(); - let insert_c = Builder::insert("5").build(); - let retain_a = Builder::retain(3).build(); - - delta.add(insert_a.clone()); - delta.add(retain_a.clone()); - delta.add(insert_b.clone()); - delta.add(insert_c.clone()); - - assert_eq!( - delta.ops_in_interval(Interval::new(0, 2)), - vec![Builder::insert("12").build()] - ); - - assert_eq!( - delta.ops_in_interval(Interval::new(0, 3)), - vec![insert_a.clone()] - ); - - assert_eq!( - delta.ops_in_interval(Interval::new(0, 4)), - vec![insert_a.clone(), Builder::retain(1).build()] - ); - - assert_eq!( - delta.ops_in_interval(Interval::new(0, 6)), - vec![insert_a.clone(), retain_a.clone()] - ); - - assert_eq!( - delta.ops_in_interval(Interval::new(0, 7)), - vec![insert_a.clone(), retain_a.clone(), insert_b.clone()] - ); -} - -#[test] -fn delta_get_ops_in_interval_3() { - let mut delta = Delta::default(); - let insert_a = Builder::insert("123456").build(); - delta.add(insert_a.clone()); - assert_eq!( - delta.ops_in_interval(Interval::new(3, 5)), - vec![Builder::insert("45").build()] - ); -} - -#[test] -fn delta_get_ops_in_interval_4() { - let mut delta = Delta::default(); - let insert_a = Builder::insert("12").build(); - let insert_b = Builder::insert("34").build(); - let insert_c = Builder::insert("56").build(); - - delta.ops.push(insert_a.clone()); - delta.ops.push(insert_b.clone()); - delta.ops.push(insert_c.clone()); - - assert_eq!(delta.ops_in_interval(Interval::new(0, 2)), vec![insert_a]); - assert_eq!(delta.ops_in_interval(Interval::new(2, 4)), vec![insert_b]); - assert_eq!(delta.ops_in_interval(Interval::new(4, 6)), vec![insert_c]); - - assert_eq!( - delta.ops_in_interval(Interval::new(2, 5)), - vec![Builder::insert("34").build(), Builder::insert("5").build()] - ); -} - -#[test] -fn delta_get_ops_in_interval_5() { - let mut delta = Delta::default(); - let insert_a = Builder::insert("123456").build(); - let insert_b = Builder::insert("789").build(); - delta.ops.push(insert_a.clone()); - delta.ops.push(insert_b.clone()); - assert_eq!( - delta.ops_in_interval(Interval::new(4, 8)), - vec![Builder::insert("56").build(), Builder::insert("78").build()] - ); -} - -#[test] -fn delta_get_ops_in_interval_6() { - let mut delta = Delta::default(); - let insert_a = Builder::insert("12345678").build(); - delta.add(insert_a.clone()); - assert_eq!( - delta.ops_in_interval(Interval::new(4, 6)), - vec![Builder::insert("56").build()] - ); -} diff --git a/rust-lib/flowy-ot/tests/op_test.rs b/rust-lib/flowy-ot/tests/op_test.rs index d7cc1c0286..adbd18d4cd 100644 --- a/rust-lib/flowy-ot/tests/op_test.rs +++ b/rust-lib/flowy-ot/tests/op_test.rs @@ -5,6 +5,121 @@ use bytecount::num_chars; use flowy_ot::core::*; use helper::*; +#[test] +fn delta_get_ops_in_interval_1() { + let mut delta = Delta::default(); + let insert_a = Builder::insert("123").build(); + let insert_b = Builder::insert("4").build(); + + delta.add(insert_a.clone()); + delta.add(insert_b.clone()); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 4)), + vec![delta.ops.last().unwrap().clone()] + ); +} + +#[test] +fn delta_get_ops_in_interval_2() { + let mut delta = Delta::default(); + let insert_a = Builder::insert("123").build(); + let insert_b = Builder::insert("4").build(); + let insert_c = Builder::insert("5").build(); + let retain_a = Builder::retain(3).build(); + + delta.add(insert_a.clone()); + delta.add(retain_a.clone()); + delta.add(insert_b.clone()); + delta.add(insert_c.clone()); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 2)), + vec![Builder::insert("12").build()] + ); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 3)), + vec![insert_a.clone()] + ); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 4)), + vec![insert_a.clone(), Builder::retain(1).build()] + ); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 6)), + vec![insert_a.clone(), retain_a.clone()] + ); + + assert_eq!( + delta.ops_in_interval(Interval::new(0, 7)), + vec![insert_a.clone(), retain_a.clone(), insert_b.clone()] + ); +} + +#[test] +fn delta_get_ops_in_interval_3() { + let mut delta = Delta::default(); + let insert_a = Builder::insert("123456").build(); + delta.add(insert_a.clone()); + assert_eq!( + delta.ops_in_interval(Interval::new(3, 5)), + vec![Builder::insert("45").build()] + ); +} + +#[test] +fn delta_get_ops_in_interval_4() { + let mut delta = Delta::default(); + let insert_a = Builder::insert("12").build(); + let insert_b = Builder::insert("34").build(); + let insert_c = Builder::insert("56").build(); + + delta.ops.push(insert_a.clone()); + delta.ops.push(insert_b.clone()); + delta.ops.push(insert_c.clone()); + + assert_eq!(delta.ops_in_interval(Interval::new(0, 2)), vec![insert_a]); + assert_eq!(delta.ops_in_interval(Interval::new(2, 4)), vec![insert_b]); + assert_eq!(delta.ops_in_interval(Interval::new(4, 6)), vec![insert_c]); + + assert_eq!( + delta.ops_in_interval(Interval::new(2, 5)), + vec![Builder::insert("34").build(), Builder::insert("5").build()] + ); +} + +#[test] +fn delta_get_ops_in_interval_5() { + let mut delta = Delta::default(); + let insert_a = Builder::insert("123456").build(); + let insert_b = Builder::insert("789").build(); + delta.ops.push(insert_a.clone()); + delta.ops.push(insert_b.clone()); + assert_eq!( + delta.ops_in_interval(Interval::new(4, 8)), + vec![Builder::insert("56").build(), Builder::insert("78").build()] + ); + + assert_eq!( + delta.ops_in_interval(Interval::new(8, 9)), + vec![Builder::insert("9").build()] + ); +} + +#[test] +fn delta_get_ops_in_interval_6() { + let mut delta = Delta::default(); + let insert_a = Builder::insert("12345678").build(); + delta.add(insert_a.clone()); + assert_eq!( + delta.ops_in_interval(Interval::new(4, 6)), + vec![Builder::insert("56").build()] + ); +} + #[test] fn lengths() { let mut delta = Delta::default(); diff --git a/rust-lib/flowy-ot/tests/undo_redo_test.rs b/rust-lib/flowy-ot/tests/undo_redo_test.rs index 2ca2fc551f..8cdef33b84 100644 --- a/rust-lib/flowy-ot/tests/undo_redo_test.rs +++ b/rust-lib/flowy-ot/tests/undo_redo_test.rs @@ -1,7 +1,7 @@ pub mod helper; use crate::helper::{TestOp::*, *}; -use flowy_ot::core::Interval; +use flowy_ot::{client::RECORD_THRESHOLD, core::Interval}; #[test] fn delta_undo_insert() { @@ -19,6 +19,7 @@ fn delta_undo_insert2() { let ops = vec![ Insert(0, "\n", 0), Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), Insert(0, "456", 0), Undo(0), AssertOpsJson(0, r#"[{"insert":"123\n"}]"#), @@ -43,11 +44,13 @@ fn delta_redo_insert() { } #[test] -fn delta_redo_insert2() { +fn delta_redo_insert_with_lagging() { let ops = vec![ Insert(0, "\n", 0), Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), Insert(0, "456", 3), + Wait(RECORD_THRESHOLD), AssertStr(0, "123456\n"), AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#), Undo(0), @@ -67,6 +70,19 @@ fn delta_undo_attributes() { Insert(0, "123", 0), Bold(0, Interval::new(0, 3), true), Undo(0), + AssertOpsJson(0, r#"[{"insert":"\n"}]"#), + ]; + OpTester::new().run_script(ops); +} + +#[test] +fn delta_undo_attributes_with_lagging() { + let ops = vec![ + Insert(0, "\n", 0), + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Bold(0, Interval::new(0, 3), true), + Undo(0), AssertOpsJson(0, r#"[{"insert":"123\n"}]"#), ]; OpTester::new().run_script(ops); @@ -79,7 +95,7 @@ fn delta_redo_attributes() { Insert(0, "123", 0), Bold(0, Interval::new(0, 3), true), Undo(0), - AssertOpsJson(0, r#"[{"insert":"123\n"}]"#), + AssertOpsJson(0, r#"[{"insert":"\n"}]"#), Redo(0), AssertOpsJson( 0, @@ -90,25 +106,36 @@ fn delta_redo_attributes() { } #[test] -fn delta_undo_delete() { +fn delta_redo_attributes_with_lagging() { let ops = vec![ Insert(0, "\n", 0), Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), Bold(0, Interval::new(0, 3), true), - Delete(0, Interval::new(0, 3)), - AssertOpsJson(0, r#"[{"insert":"\n"}]"#), Undo(0), + AssertOpsJson(0, r#"[{"insert":"123\n"}]"#), + Redo(0), AssertOpsJson( 0, - r#"[ - {"insert":"123","attributes":{"bold":"true"}}, - {"insert":"\n"}] - "#, + r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#, ), ]; OpTester::new().run_script(ops); } +#[test] +fn delta_undo_delete() { + let ops = vec![ + Insert(0, "123", 0), + AssertOpsJson(0, r#"[{"insert":"123"}]"#), + Delete(0, Interval::new(0, 3)), + AssertOpsJson(0, r#"[]"#), + Undo(0), + AssertOpsJson(0, r#"[{"insert":"123"}]"#), + ]; + OpTester::new().run_script(ops); +} + #[test] fn delta_undo_delete2() { let ops = vec![ @@ -124,6 +151,28 @@ fn delta_undo_delete2() { "#, ), Undo(0), + AssertOpsJson(0, r#"[{"insert":"\n"}]"#), + ]; + OpTester::new().run_script(ops); +} + +#[test] +fn delta_undo_delete2_with_lagging() { + let ops = vec![ + Insert(0, "\n", 0), + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Bold(0, Interval::new(0, 3), true), + Wait(RECORD_THRESHOLD), + Delete(0, Interval::new(0, 1)), + AssertOpsJson( + 0, + r#"[ + {"insert":"23","attributes":{"bold":"true"}}, + {"insert":"\n"}] + "#, + ), + Undo(0), AssertOpsJson( 0, r#"[ @@ -148,3 +197,68 @@ fn delta_redo_delete() { ]; OpTester::new().run_script(ops); } + +#[test] +fn delta_undo_replace() { + let ops = vec![ + Insert(0, "\n", 0), + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Replace(0, Interval::new(0, 2), "ab"), + AssertOpsJson( + 0, + r#"[ + {"insert":"ab"}, + {"insert":"3","attributes":{"bold":"true"}},{"insert":"\n"}] + "#, + ), + Undo(0), + AssertOpsJson(0, r#"[{"insert":"\n"}]"#), + ]; + OpTester::new().run_script(ops); +} + +#[test] +fn delta_undo_replace_with_lagging() { + let ops = vec![ + Insert(0, "\n", 0), + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Bold(0, Interval::new(0, 3), true), + Wait(RECORD_THRESHOLD), + Replace(0, Interval::new(0, 2), "ab"), + AssertOpsJson( + 0, + r#"[ + {"insert":"ab"}, + {"insert":"3","attributes":{"bold":"true"}},{"insert":"\n"}] + "#, + ), + Undo(0), + AssertOpsJson( + 0, + r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#, + ), + ]; + OpTester::new().run_script(ops); +} + +#[test] +fn delta_redo_replace() { + let ops = vec![ + Insert(0, "\n", 0), + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Replace(0, Interval::new(0, 2), "ab"), + Undo(0), + Redo(0), + AssertOpsJson( + 0, + r#"[ + {"insert":"ab"}, + {"insert":"3","attributes":{"bold":"true"}},{"insert":"\n"}] + "#, + ), + ]; + OpTester::new().run_script(ops); +}