config undo redo for document

This commit is contained in:
appflowy 2021-08-05 22:52:19 +08:00
parent e2b8738b65
commit 760c191758
7 changed files with 164 additions and 47 deletions

View File

@ -1,5 +1,5 @@
use crate::{
client::{History, UndoResult},
client::{History, RevId, Revision, UndoResult},
core::{
Attribute,
Attributes,
@ -10,11 +10,13 @@ use crate::{
OpBuilder,
Operation,
},
errors::{ErrorBuilder, OTError, OTErrorCode::*},
};
pub struct Document {
data: Delta,
history: History,
rev_id_counter: u64,
}
impl Document {
@ -22,6 +24,7 @@ impl Document {
Document {
data: Delta::new(),
history: History::new(),
rev_id_counter: 1,
}
}
@ -53,9 +56,38 @@ impl Document {
self.update_with_attribute(attributes, interval);
}
pub fn undo(&mut self) -> UndoResult { unimplemented!() }
pub fn can_undo(&self) -> bool { self.history.can_undo() }
pub fn redo(&mut self) -> UndoResult { unimplemented!() }
pub fn can_redo(&self) -> bool { self.history.can_redo() }
pub fn undo(&mut self) -> Result<UndoResult, OTError> {
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;
self.history.add_redo(redo_delta);
Ok(result)
},
}
}
pub fn redo(&mut self) -> Result<UndoResult, OTError> {
match self.history.redo() {
None => Err(ErrorBuilder::new(RedoFail).build()),
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);
self.data = new_delta;
self.history.add_undo(redo_delta);
Ok(result)
},
}
}
pub fn delete(&mut self, interval: Interval) {
let delete = OpBuilder::delete(interval.size() as u64).build();
@ -96,6 +128,13 @@ impl Document {
}
let new_data = self.data.compose(&new_delta).unwrap();
let undo_delta = new_data.invert_delta(&self.data);
self.rev_id_counter += 1;
if !undo_delta.is_empty() {
self.history.add_undo(undo_delta);
}
self.data = new_data;
}
@ -128,24 +167,19 @@ impl Document {
self.update_with_op(retain, interval);
}
fn next_rev_id(&self) -> RevId { RevId(self.rev_id_counter) }
}
pub fn transform(left: &Document, right: &Document) -> (Document, Document) {
pub fn transform(left: &mut Document, right: &mut Document) {
let (a_prime, b_prime) = left.data.transform(&right.data).unwrap();
log::trace!("a:{:?},b:{:?}", a_prime, b_prime);
let data_left = left.data.compose(&b_prime).unwrap();
let data_right = right.data.compose(&a_prime).unwrap();
(
Document {
data: data_left,
history: left.history.clone(),
},
Document {
data: data_right,
history: right.history.clone(),
},
)
left.set_data(data_left);
right.set_data(data_right);
}
fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) {

View File

@ -2,10 +2,17 @@ use crate::core::{Delta, Interval, OpBuilder, Operation};
const MAX_UNDOS: usize = 20;
#[derive(Debug, Clone)]
pub struct RevId(pub u64);
#[derive(Debug, Clone)]
pub struct Revision {
rev_id: u64,
delta: Delta,
rev_id: RevId,
pub delta: Delta,
}
impl Revision {
pub fn new(rev_id: RevId, delta: Delta) -> Revision { Self { rev_id, delta } }
}
#[derive(Debug, Clone)]
@ -14,11 +21,22 @@ pub struct UndoResult {
len: u64,
}
impl UndoResult {
pub fn fail() -> Self {
UndoResult {
success: false,
len: 0,
}
}
pub fn success(len: u64) -> Self { UndoResult { success: true, len } }
}
#[derive(Debug, Clone)]
pub struct History {
cur_undo: usize,
undos: Vec<Revision>,
redos: Vec<Revision>,
undos: Vec<Delta>,
redos: Vec<Delta>,
}
impl History {
@ -34,17 +52,24 @@ impl History {
pub fn can_redo(&self) -> bool { !self.redos.is_empty() }
pub fn record(&mut self, _change: Delta) {}
pub fn add_undo(&mut self, delta: Delta) { self.undos.push(delta); }
pub fn undo(&mut self) -> Option<Revision> {
pub fn add_redo(&mut self, delta: Delta) { self.redos.push(delta); }
pub fn undo(&mut self) -> Option<Delta> {
if !self.can_undo() {
return None;
}
let revision = self.undos.pop().unwrap();
Some(revision)
let delta = self.undos.pop().unwrap();
Some(delta)
}
pub fn redo(&mut self) -> Option<Revision> { None }
pub fn redo(&mut self) -> Option<Delta> {
if !self.can_redo() {
return None;
}
let delta = self.redos.pop().unwrap();
Some(delta)
}
}

View File

@ -1,6 +1,6 @@
use crate::{
core::{attributes::*, operation::*, Interval},
errors::OTError,
errors::{ErrorBuilder, OTError, OTErrorCode},
};
use bytecount::num_chars;
use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr};
@ -146,7 +146,7 @@ impl Delta {
/// conflicts.
pub fn compose(&self, other: &Self) -> Result<Self, OTError> {
if self.target_len != other.base_len {
return Err(OTError);
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build());
}
let mut new_delta = Delta::default();
@ -167,7 +167,7 @@ impl Delta {
next_op2 = ops2.next();
},
(None, _) | (_, None) => {
return Err(OTError);
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build());
},
(Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => {
let composed_attrs = compose_operation(&next_op1, &next_op2);
@ -297,7 +297,7 @@ impl Delta {
/// length conflicts.
pub fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> {
if self.base_len != other.base_len {
return Err(OTError);
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build());
}
let mut a_prime = Delta::default();
@ -324,10 +324,10 @@ impl Delta {
next_op2 = ops2.next();
},
(None, _) => {
return Err(OTError);
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build());
},
(_, None) => {
return Err(OTError);
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build());
},
(Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => {
let composed_attrs = transform_operation(&next_op1, &next_op2);
@ -418,7 +418,7 @@ impl Delta {
/// conflicts.
pub fn apply(&self, s: &str) -> Result<String, OTError> {
if num_chars(s.as_bytes()) != self.base_len {
return Err(OTError);
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build());
}
let mut new_s = String::new();
let chars = &mut s.chars();

View File

@ -1,7 +1,19 @@
use std::{error::Error, fmt};
#[derive(Clone, Debug)]
pub struct OTError;
pub struct OTError {
pub code: OTErrorCode,
pub msg: String,
}
impl OTError {
pub fn new(code: OTErrorCode, msg: &str) -> OTError {
Self {
code,
msg: msg.to_owned(),
}
}
}
impl fmt::Display for OTError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "incompatible lengths") }
@ -10,3 +22,39 @@ impl fmt::Display for OTError {
impl Error for OTError {
fn source(&self) -> Option<&(dyn Error + 'static)> { None }
}
#[derive(Debug, Clone)]
pub enum OTErrorCode {
IncompatibleLength,
UndoFail,
RedoFail,
}
pub struct ErrorBuilder {
pub code: OTErrorCode,
pub msg: Option<String>,
}
impl ErrorBuilder {
pub fn new(code: OTErrorCode) -> Self { ErrorBuilder { code, msg: None } }
pub fn msg<T>(mut self, msg: T) -> Self
where
T: Into<String>,
{
self.msg = Some(msg.into());
self
}
pub fn error<T>(mut self, msg: T) -> Self
where
T: std::fmt::Debug,
{
self.msg = Some(format!("{:?}", msg));
self
}
pub fn build(mut self) -> OTError {
OTError::new(self.code, &self.msg.take().unwrap_or("".to_owned()))
}
}

View File

@ -29,8 +29,14 @@ pub enum TestOp {
Transform(usize, usize),
// invert the delta_a base on the delta_b
#[display(fmt = "Invert")]
Invert(usize, usize),
#[display(fmt = "Undo")]
Undo(usize, usize),
Undo(usize),
#[display(fmt = "Redo")]
Redo(usize),
#[display(fmt = "AssertOpsJson")]
AssertOpsJson(usize, &'static str),
@ -80,13 +86,12 @@ impl OpTester {
document.format(*interval, Attribute::Italic, *enable);
},
TestOp::Transform(delta_a_i, delta_b_i) => {
let (document_a, document_b) =
transform(&self.documents[*delta_a_i], &self.documents[*delta_b_i]);
self.documents[*delta_a_i] = document_a;
self.documents[*delta_b_i] = document_b;
transform(
&mut self.documents[*delta_a_i],
&mut self.documents[*delta_b_i],
);
},
TestOp::Undo(delta_a_i, delta_b_i) => {
TestOp::Invert(delta_a_i, delta_b_i) => {
let delta_a = &self.documents[*delta_a_i].data();
let delta_b = &self.documents[*delta_b_i].data();
log::debug!("Invert: ");
@ -108,7 +113,12 @@ impl OpTester {
self.documents[*delta_a_i].set_data(new_delta_after_undo);
},
TestOp::Undo(delta_i) => {
self.documents[*delta_i].undo();
},
TestOp::Redo(delta_i) => {
self.documents[*delta_i].redo();
},
TestOp::AssertOpsJson(delta_i, expected) => {
let delta_i_json = self.documents[*delta_i].to_json();

View File

@ -23,7 +23,7 @@ fn delta_invert_no_attribute_delta2() {
let ops = vec![
Insert(0, "123", 0),
Insert(1, "4567", 0),
Undo(0, 1),
Invert(0, 1),
AssertOpsJson(0, r#"[{"insert":"123"}]"#),
];
OpTester::new().run_script(ops);
@ -36,7 +36,7 @@ fn delta_invert_attribute_delta_with_no_attribute_delta() {
Bold(0, Interval::new(0, 3), true),
AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
Insert(1, "4567", 0),
Undo(0, 1),
Invert(0, 1),
AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
];
OpTester::new().run_script(ops);
@ -55,7 +55,7 @@ fn delta_invert_attribute_delta_with_no_attribute_delta2() {
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#,
),
Insert(1, "abc", 0),
Undo(0, 1),
Invert(0, 1),
AssertOpsJson(
0,
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#,
@ -74,7 +74,7 @@ fn delta_invert_no_attribute_delta_with_attribute_delta() {
1,
r#"[{"insert":"456","attributes":{"bold":"true"}},{"insert":"7"}]"#,
),
Undo(0, 1),
Invert(0, 1),
AssertOpsJson(0, r#"[{"insert":"123"}]"#),
];
OpTester::new().run_script(ops);
@ -93,7 +93,7 @@ fn delta_invert_no_attribute_delta_with_attribute_delta2() {
1,
r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"}}]"#,
),
Undo(0, 1),
Invert(0, 1),
AssertOpsJson(0, r#"[{"insert":"123"}]"#),
];
OpTester::new().run_script(ops);
@ -119,7 +119,7 @@ fn delta_invert_attribute_delta_with_attribute_delta() {
1,
r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"}}]"#,
),
Undo(0, 1),
Invert(0, 1),
AssertOpsJson(
0,
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}}]"#,