From c667ee0f363d604285adfd356ebdcb689e806ec9 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 6 Aug 2021 22:25:09 +0800 Subject: [PATCH] add redo test --- rust-lib/flowy-ot/src/client/document.rs | 25 ++++++++----- rust-lib/flowy-ot/src/core/delta.rs | 37 ++++++++++++------- .../flowy-ot/src/core/operation/operation.rs | 33 +++++++++++++++-- rust-lib/flowy-ot/tests/helper/mod.rs | 9 ++++- rust-lib/flowy-ot/tests/invert_test.rs | 2 +- rust-lib/flowy-ot/tests/op_test.rs | 2 +- rust-lib/flowy-ot/tests/undo_redo_test.rs | 34 ++++++++++++++++- 7 files changed, 110 insertions(+), 32 deletions(-) diff --git a/rust-lib/flowy-ot/src/client/document.rs b/rust-lib/flowy-ot/src/client/document.rs index 42fe7c89b8..fb86a806cd 100644 --- a/rust-lib/flowy-ot/src/client/document.rs +++ b/rust-lib/flowy-ot/src/client/document.rs @@ -72,10 +72,10 @@ impl Document { match self.history.undo() { None => Err(ErrorBuilder::new(UndoFail).build()), Some(undo_delta) => { - let new_delta = self.data.compose(&undo_delta)?; - let result = UndoResult::success(new_delta.target_len as u64); - let redo_delta = undo_delta.invert_delta(&self.data); - self.data = new_delta; + let composed_delta = self.data.compose(&undo_delta)?; + let redo_delta = undo_delta.invert(&self.data); + let result = UndoResult::success(composed_delta.target_len as u64); + self.data = composed_delta; self.history.add_redo(redo_delta); Ok(result) @@ -89,9 +89,9 @@ impl Document { Some(redo_delta) => { let new_delta = self.data.compose(&redo_delta)?; let result = UndoResult::success(new_delta.target_len as u64); - let redo_delta = redo_delta.invert_delta(&self.data); + let undo_delta = redo_delta.invert(&self.data); self.data = new_delta; - self.history.add_undo(redo_delta); + self.history.add_undo(undo_delta); Ok(result) }, } @@ -104,6 +104,8 @@ impl Document { pub fn to_json(&self) -> String { self.data.to_json() } + pub fn to_string(&self) -> String { self.data.apply("").unwrap() } + pub fn data(&self) -> &Delta { &self.data } pub fn set_data(&mut self, data: Delta) { self.data = data; } @@ -135,12 +137,15 @@ impl Document { }); } - let new_data = self.data.compose(&new_delta)?; - let undo_delta = new_delta.invert_delta(&self.data); - self.rev_id_counter += 1; + // 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 = new_data; + self.data = composed_delta; Ok(()) } diff --git a/rust-lib/flowy-ot/src/core/delta.rs b/rust-lib/flowy-ot/src/core/delta.rs index ee3bcf7895..993beca2db 100644 --- a/rust-lib/flowy-ot/src/core/delta.rs +++ b/rust-lib/flowy-ot/src/core/delta.rs @@ -3,7 +3,12 @@ use crate::{ errors::{ErrorBuilder, OTError, OTErrorCode}, }; use bytecount::num_chars; -use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr}; +use std::{ + cmp::{max, min, Ordering}, + fmt, + iter::FromIterator, + str::FromStr, +}; #[derive(Clone, Debug, PartialEq)] pub struct Delta { @@ -444,7 +449,7 @@ impl Delta { /// Computes the inverse of an operation. The inverse of an operation is the /// operation that reverts the effects of the operation - pub fn invert(&self, s: &str) -> Self { + pub fn invert_str(&self, s: &str) -> Self { let mut inverted = Delta::default(); let chars = &mut s.chars(); for op in &self.ops { @@ -472,7 +477,7 @@ impl Delta { inverted } - pub fn invert_delta(&self, other: &Delta) -> Delta { + pub fn invert(&self, other: &Delta) -> Delta { let mut inverted = Delta::default(); if other.is_empty() { return inverted; @@ -517,8 +522,8 @@ impl Delta { }, Operation::Retain(_) => { match op.has_attribute() { - true => inverted.retain(len as u64, op.get_attributes()), - false => inverted_from_other(&mut inverted, op, index, index + len), + true => inverted_from_other(&mut inverted, op, index, index + len), + false => inverted.retain(len as u64, op.get_attributes()), } index += len; }, @@ -547,20 +552,26 @@ impl Delta { let mut ops: Vec = Vec::with_capacity(self.ops.len()); let mut offset: usize = 0; let mut ops_iter = self.ops.iter(); - let mut op = ops_iter.next(); + let mut next_op = ops_iter.next(); - while offset < interval.end && op.is_some() { - if let Some(op) = op { + if offset < interval.end && next_op.is_some() { + let op = next_op.take().unwrap(); + let len = op.length() as usize; + // log::info!("{:?}", op); + while offset < len { if offset < interval.start { - offset += op.length() as usize; + offset += min(interval.start, op.length() as usize); } else { - ops.push(op.clone()); - offset += op.length() as usize; + if interval.contains(offset) { + ops.push(op.shrink_to_interval(interval)); + offset += max(op.length() as usize, interval.size()); + } else { + break; + } } } - op = ops_iter.next(); + next_op = ops_iter.next(); } - ops } diff --git a/rust-lib/flowy-ot/src/core/operation/operation.rs b/rust-lib/flowy-ot/src/core/operation/operation.rs index b37150bc04..c98c3af045 100644 --- a/rust-lib/flowy-ot/src/core/operation/operation.rs +++ b/rust-lib/flowy-ot/src/core/operation/operation.rs @@ -1,6 +1,7 @@ -use crate::core::{Attributes, OpBuilder}; +use crate::core::{Attributes, Interval, OpBuilder}; use bytecount::num_chars; use std::{ + cmp::min, fmt, ops::{Deref, DerefMut}, str::Chars, @@ -52,9 +53,9 @@ impl Operation { pub fn has_attribute(&self) -> bool { match self.get_attributes() { - Attributes::Follow => true, - Attributes::Custom(_) => false, - Attributes::Empty => true, + Attributes::Follow => false, + Attributes::Custom(data) => data.is_empty(), + Attributes::Empty => false, } } @@ -67,6 +68,30 @@ impl Operation { } pub fn is_empty(&self) -> bool { self.length() == 0 } + + pub fn shrink_to_interval(&self, interval: Interval) -> Operation { + match self { + Operation::Delete(_) => Operation::Delete(interval.size() as u64), + Operation::Retain(retain) => { + // + OpBuilder::retain(interval.size() as u64) + .attributes(retain.attributes.clone()) + .build() + }, + Operation::Insert(insert) => { + // debug_assert!(insert.s.len() <= interval.size()); + if interval.start > insert.s.len() { + return OpBuilder::insert("").build(); + } + + let end = min(interval.end, insert.s.len()); + let s = &insert.s[interval.start..end]; + OpBuilder::insert(s) + .attributes(insert.attributes.clone()) + .build() + }, + } + } } impl fmt::Display for Operation { diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index 030b109551..830690f8c1 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -35,6 +35,9 @@ pub enum TestOp { #[display(fmt = "Redo")] Redo(usize), + #[display(fmt = "AssertStr")] + AssertStr(usize, &'static str), + #[display(fmt = "AssertOpsJson")] AssertOpsJson(usize, &'static str), } @@ -107,7 +110,7 @@ impl OpTester { log::debug!("b: {}", delta_b.to_json()); let (_, b_prime) = delta_a.transform(delta_b).unwrap(); - let undo = b_prime.invert_delta(&delta_a); + let undo = b_prime.invert(&delta_a); let new_delta = delta_a.compose(&b_prime).unwrap(); log::debug!("new delta: {}", new_delta.to_json()); @@ -127,6 +130,10 @@ impl OpTester { TestOp::Redo(delta_i) => { self.documents[*delta_i].redo().unwrap(); }, + TestOp::AssertStr(delta_i, expected) => { + assert_eq!(&self.documents[*delta_i].to_string(), expected); + }, + TestOp::AssertOpsJson(delta_i, expected) => { let delta_i_json = self.documents[*delta_i].to_json(); diff --git a/rust-lib/flowy-ot/tests/invert_test.rs b/rust-lib/flowy-ot/tests/invert_test.rs index ab4a9b1242..3b5666c193 100644 --- a/rust-lib/flowy-ot/tests/invert_test.rs +++ b/rust-lib/flowy-ot/tests/invert_test.rs @@ -10,7 +10,7 @@ fn delta_invert_no_attribute_delta() { let mut change = Delta::default(); change.add(OpBuilder::retain(3).build()); change.add(OpBuilder::insert("456").build()); - let undo = change.invert_delta(&delta); + let undo = change.invert(&delta); let new_delta = delta.compose(&change).unwrap(); let delta_after_undo = new_delta.compose(&undo).unwrap(); diff --git a/rust-lib/flowy-ot/tests/op_test.rs b/rust-lib/flowy-ot/tests/op_test.rs index 27ec097656..d43f9ba7ad 100644 --- a/rust-lib/flowy-ot/tests/op_test.rs +++ b/rust-lib/flowy-ot/tests/op_test.rs @@ -82,7 +82,7 @@ fn invert() { let mut rng = Rng::default(); let s = rng.gen_string(50); let delta_a = rng.gen_delta(&s); - let delta_b = delta_a.invert(&s); + let delta_b = delta_a.invert_str(&s); assert_eq!(delta_a.base_len, delta_b.target_len); assert_eq!(delta_a.target_len, delta_b.base_len); assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s); diff --git a/rust-lib/flowy-ot/tests/undo_redo_test.rs b/rust-lib/flowy-ot/tests/undo_redo_test.rs index 93785c9d94..cea475a8cb 100644 --- a/rust-lib/flowy-ot/tests/undo_redo_test.rs +++ b/rust-lib/flowy-ot/tests/undo_redo_test.rs @@ -3,13 +3,13 @@ pub mod helper; use crate::helper::{TestOp::*, *}; #[test] -fn delta_undo_insert_text() { +fn delta_undo_insert() { let ops = vec![Insert(0, "123", 0), Undo(0), AssertOpsJson(0, r#"[]"#)]; OpTester::new().run_script(ops); } #[test] -fn delta_undo_insert_text2() { +fn delta_undo_insert2() { let ops = vec![ Insert(0, "123", 0), Insert(0, "456", 0), @@ -20,3 +20,33 @@ fn delta_undo_insert_text2() { ]; OpTester::new().run_script(ops); } + +#[test] +fn delta_redo_insert() { + let ops = vec![ + Insert(0, "123", 0), + AssertOpsJson(0, r#"[{"insert":"123\n"}]"#), + Undo(0), + AssertOpsJson(0, r#"[{"insert":"\n"}]"#), + Redo(0), + AssertOpsJson(0, r#"[{"insert":"123\n"}]"#), + ]; + OpTester::new().run_script(ops); +} + +#[test] +fn delta_redo_insert2() { + let ops = vec![ + Insert(0, "123", 0), + Insert(0, "456", 3), + AssertStr(0, "123456\n"), + AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#), + Undo(0), + AssertOpsJson(0, r#"[{"insert":"123\n"}]"#), + Redo(0), + AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#), + Undo(0), + AssertOpsJson(0, r#"[{"insert":"123\n"}]"#), + ]; + OpTester::new().run_script(ops); +}