mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
config undo redo for document
This commit is contained in:
parent
e2b8738b65
commit
760c191758
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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"}}]"#,
|
||||
|
0
rust-lib/flowy-ot/tests/undo_redo_test.rs
Normal file
0
rust-lib/flowy-ot/tests/undo_redo_test.rs
Normal file
Loading…
Reference in New Issue
Block a user