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() {
|
match self.history.undo() {
|
||||||
None => Err(ErrorBuilder::new(UndoFail).build()),
|
None => Err(ErrorBuilder::new(UndoFail).build()),
|
||||||
Some(undo_delta) => {
|
Some(undo_delta) => {
|
||||||
let new_delta = self.data.compose(&undo_delta)?;
|
let composed_delta = self.data.compose(&undo_delta)?;
|
||||||
let result = UndoResult::success(new_delta.target_len as u64);
|
let redo_delta = undo_delta.invert(&self.data);
|
||||||
let redo_delta = undo_delta.invert_delta(&self.data);
|
let result = UndoResult::success(composed_delta.target_len as u64);
|
||||||
self.data = new_delta;
|
self.data = composed_delta;
|
||||||
self.history.add_redo(redo_delta);
|
self.history.add_redo(redo_delta);
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
@ -89,9 +89,9 @@ impl Document {
|
|||||||
Some(redo_delta) => {
|
Some(redo_delta) => {
|
||||||
let new_delta = self.data.compose(&redo_delta)?;
|
let new_delta = self.data.compose(&redo_delta)?;
|
||||||
let result = UndoResult::success(new_delta.target_len as u64);
|
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.data = new_delta;
|
||||||
self.history.add_undo(redo_delta);
|
self.history.add_undo(undo_delta);
|
||||||
Ok(result)
|
Ok(result)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -104,6 +104,8 @@ impl Document {
|
|||||||
|
|
||||||
pub fn to_json(&self) -> String { self.data.to_json() }
|
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 data(&self) -> &Delta { &self.data }
|
||||||
|
|
||||||
pub fn set_data(&mut self, data: Delta) { self.data = 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)?;
|
// c = a.compose(b)
|
||||||
let undo_delta = new_delta.invert_delta(&self.data);
|
// d = b.invert(a)
|
||||||
self.rev_id_counter += 1;
|
// 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.history.record(undo_delta);
|
||||||
self.data = new_data;
|
self.data = composed_delta;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,12 @@ use crate::{
|
|||||||
errors::{ErrorBuilder, OTError, OTErrorCode},
|
errors::{ErrorBuilder, OTError, OTErrorCode},
|
||||||
};
|
};
|
||||||
use bytecount::num_chars;
|
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)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Delta {
|
pub struct Delta {
|
||||||
@ -444,7 +449,7 @@ impl Delta {
|
|||||||
|
|
||||||
/// Computes the inverse of an operation. The inverse of an operation is the
|
/// Computes the inverse of an operation. The inverse of an operation is the
|
||||||
/// operation that reverts the effects of the operation
|
/// 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 mut inverted = Delta::default();
|
||||||
let chars = &mut s.chars();
|
let chars = &mut s.chars();
|
||||||
for op in &self.ops {
|
for op in &self.ops {
|
||||||
@ -472,7 +477,7 @@ impl Delta {
|
|||||||
inverted
|
inverted
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invert_delta(&self, other: &Delta) -> Delta {
|
pub fn invert(&self, other: &Delta) -> Delta {
|
||||||
let mut inverted = Delta::default();
|
let mut inverted = Delta::default();
|
||||||
if other.is_empty() {
|
if other.is_empty() {
|
||||||
return inverted;
|
return inverted;
|
||||||
@ -517,8 +522,8 @@ impl Delta {
|
|||||||
},
|
},
|
||||||
Operation::Retain(_) => {
|
Operation::Retain(_) => {
|
||||||
match op.has_attribute() {
|
match op.has_attribute() {
|
||||||
true => inverted.retain(len as u64, op.get_attributes()),
|
true => inverted_from_other(&mut inverted, op, index, index + len),
|
||||||
false => inverted_from_other(&mut inverted, op, index, index + len),
|
false => inverted.retain(len as u64, op.get_attributes()),
|
||||||
}
|
}
|
||||||
index += len;
|
index += len;
|
||||||
},
|
},
|
||||||
@ -547,20 +552,26 @@ impl Delta {
|
|||||||
let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
|
let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
|
||||||
let mut offset: usize = 0;
|
let mut offset: usize = 0;
|
||||||
let mut ops_iter = self.ops.iter();
|
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 offset < interval.end && next_op.is_some() {
|
||||||
if let Some(op) = op {
|
let op = next_op.take().unwrap();
|
||||||
|
let len = op.length() as usize;
|
||||||
|
// log::info!("{:?}", op);
|
||||||
|
while offset < len {
|
||||||
if offset < interval.start {
|
if offset < interval.start {
|
||||||
offset += op.length() as usize;
|
offset += min(interval.start, op.length() as usize);
|
||||||
} else {
|
} else {
|
||||||
ops.push(op.clone());
|
if interval.contains(offset) {
|
||||||
offset += op.length() as usize;
|
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
|
ops
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::core::{Attributes, OpBuilder};
|
use crate::core::{Attributes, Interval, OpBuilder};
|
||||||
use bytecount::num_chars;
|
use bytecount::num_chars;
|
||||||
use std::{
|
use std::{
|
||||||
|
cmp::min,
|
||||||
fmt,
|
fmt,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
str::Chars,
|
str::Chars,
|
||||||
@ -52,9 +53,9 @@ impl Operation {
|
|||||||
|
|
||||||
pub fn has_attribute(&self) -> bool {
|
pub fn has_attribute(&self) -> bool {
|
||||||
match self.get_attributes() {
|
match self.get_attributes() {
|
||||||
Attributes::Follow => true,
|
Attributes::Follow => false,
|
||||||
Attributes::Custom(_) => false,
|
Attributes::Custom(data) => data.is_empty(),
|
||||||
Attributes::Empty => true,
|
Attributes::Empty => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +68,30 @@ impl Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool { self.length() == 0 }
|
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 {
|
impl fmt::Display for Operation {
|
||||||
|
@ -35,6 +35,9 @@ pub enum TestOp {
|
|||||||
#[display(fmt = "Redo")]
|
#[display(fmt = "Redo")]
|
||||||
Redo(usize),
|
Redo(usize),
|
||||||
|
|
||||||
|
#[display(fmt = "AssertStr")]
|
||||||
|
AssertStr(usize, &'static str),
|
||||||
|
|
||||||
#[display(fmt = "AssertOpsJson")]
|
#[display(fmt = "AssertOpsJson")]
|
||||||
AssertOpsJson(usize, &'static str),
|
AssertOpsJson(usize, &'static str),
|
||||||
}
|
}
|
||||||
@ -107,7 +110,7 @@ impl OpTester {
|
|||||||
log::debug!("b: {}", delta_b.to_json());
|
log::debug!("b: {}", delta_b.to_json());
|
||||||
|
|
||||||
let (_, b_prime) = delta_a.transform(delta_b).unwrap();
|
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();
|
let new_delta = delta_a.compose(&b_prime).unwrap();
|
||||||
log::debug!("new delta: {}", new_delta.to_json());
|
log::debug!("new delta: {}", new_delta.to_json());
|
||||||
@ -127,6 +130,10 @@ impl OpTester {
|
|||||||
TestOp::Redo(delta_i) => {
|
TestOp::Redo(delta_i) => {
|
||||||
self.documents[*delta_i].redo().unwrap();
|
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) => {
|
TestOp::AssertOpsJson(delta_i, expected) => {
|
||||||
let delta_i_json = self.documents[*delta_i].to_json();
|
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();
|
let mut change = Delta::default();
|
||||||
change.add(OpBuilder::retain(3).build());
|
change.add(OpBuilder::retain(3).build());
|
||||||
change.add(OpBuilder::insert("456").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 new_delta = delta.compose(&change).unwrap();
|
||||||
let delta_after_undo = new_delta.compose(&undo).unwrap();
|
let delta_after_undo = new_delta.compose(&undo).unwrap();
|
||||||
|
@ -82,7 +82,7 @@ fn invert() {
|
|||||||
let mut rng = Rng::default();
|
let mut rng = Rng::default();
|
||||||
let s = rng.gen_string(50);
|
let s = rng.gen_string(50);
|
||||||
let delta_a = rng.gen_delta(&s);
|
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.base_len, delta_b.target_len);
|
||||||
assert_eq!(delta_a.target_len, delta_b.base_len);
|
assert_eq!(delta_a.target_len, delta_b.base_len);
|
||||||
assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);
|
assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);
|
||||||
|
@ -3,13 +3,13 @@ pub mod helper;
|
|||||||
use crate::helper::{TestOp::*, *};
|
use crate::helper::{TestOp::*, *};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_insert_text() {
|
fn delta_undo_insert() {
|
||||||
let ops = vec![Insert(0, "123", 0), Undo(0), AssertOpsJson(0, r#"[]"#)];
|
let ops = vec![Insert(0, "123", 0), Undo(0), AssertOpsJson(0, r#"[]"#)];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_insert_text2() {
|
fn delta_undo_insert2() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Insert(0, "456", 0),
|
Insert(0, "456", 0),
|
||||||
@ -20,3 +20,33 @@ fn delta_undo_insert_text2() {
|
|||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
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