mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add redo test
This commit is contained in:
parent
3d837c51f4
commit
c667ee0f36
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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<Operation> = 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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user