mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix redo attribut bugs
This commit is contained in:
parent
51ae139f89
commit
efa78dd456
@ -11,6 +11,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = {version = "1.0"}
|
serde_json = {version = "1.0"}
|
||||||
derive_more = {version = "0.99", features = ["display"]}
|
derive_more = {version = "0.99", features = ["display"]}
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
color-eyre = { version = "0.5", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
@ -5,9 +5,9 @@ use crate::{
|
|||||||
Attributes,
|
Attributes,
|
||||||
AttributesDataRule,
|
AttributesDataRule,
|
||||||
AttrsBuilder,
|
AttrsBuilder,
|
||||||
|
Builder,
|
||||||
Delta,
|
Delta,
|
||||||
Interval,
|
Interval,
|
||||||
OpBuilder,
|
|
||||||
Operation,
|
Operation,
|
||||||
},
|
},
|
||||||
errors::{ErrorBuilder, OTError, OTErrorCode::*},
|
errors::{ErrorBuilder, OTError, OTErrorCode::*},
|
||||||
@ -42,7 +42,7 @@ impl Document {
|
|||||||
if attributes == Attributes::Empty {
|
if attributes == Attributes::Empty {
|
||||||
attributes = Attributes::Follow;
|
attributes = Attributes::Follow;
|
||||||
}
|
}
|
||||||
let insert = OpBuilder::insert(text).attributes(attributes).build();
|
let insert = Builder::insert(text).attributes(attributes).build();
|
||||||
let interval = Interval::new(index, index);
|
let interval = Interval::new(index, index);
|
||||||
|
|
||||||
self.update_with_op(insert, interval)
|
self.update_with_op(insert, interval)
|
||||||
@ -70,8 +70,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) => {
|
||||||
|
log::debug!("undo: {:?}", undo_delta);
|
||||||
let composed_delta = self.data.compose(&undo_delta)?;
|
let composed_delta = self.data.compose(&undo_delta)?;
|
||||||
let redo_delta = undo_delta.invert(&self.data);
|
let redo_delta = undo_delta.invert(&self.data);
|
||||||
|
log::debug!("computed redo: {:?}", redo_delta);
|
||||||
let result = UndoResult::success(composed_delta.target_len as usize);
|
let result = UndoResult::success(composed_delta.target_len as usize);
|
||||||
self.data = composed_delta;
|
self.data = composed_delta;
|
||||||
self.history.add_redo(redo_delta);
|
self.history.add_redo(redo_delta);
|
||||||
@ -85,10 +87,13 @@ impl Document {
|
|||||||
match self.history.redo() {
|
match self.history.redo() {
|
||||||
None => Err(ErrorBuilder::new(RedoFail).build()),
|
None => Err(ErrorBuilder::new(RedoFail).build()),
|
||||||
Some(redo_delta) => {
|
Some(redo_delta) => {
|
||||||
|
log::debug!("redo: {:?}", 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 usize);
|
let result = UndoResult::success(new_delta.target_len as usize);
|
||||||
let undo_delta = redo_delta.invert(&self.data);
|
let undo_delta = redo_delta.invert(&self.data);
|
||||||
|
log::debug!("computed undo: {:?}", undo_delta);
|
||||||
self.data = new_delta;
|
self.data = new_delta;
|
||||||
|
|
||||||
self.history.add_undo(undo_delta);
|
self.history.add_undo(undo_delta);
|
||||||
Ok(result)
|
Ok(result)
|
||||||
},
|
},
|
||||||
@ -96,7 +101,7 @@ impl Document {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, interval: Interval) -> Result<(), OTError> {
|
pub fn delete(&mut self, interval: Interval) -> Result<(), OTError> {
|
||||||
let delete = OpBuilder::delete(interval.size()).build();
|
let delete = Builder::delete(interval.size()).build();
|
||||||
self.update_with_op(delete, interval)
|
self.update_with_op(delete, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +173,7 @@ impl Document {
|
|||||||
};
|
};
|
||||||
|
|
||||||
log::debug!("new attributes: {:?}", new_attributes);
|
log::debug!("new attributes: {:?}", new_attributes);
|
||||||
let retain = OpBuilder::retain(interval.size())
|
let retain = Builder::retain(interval.size())
|
||||||
.attributes(new_attributes)
|
.attributes(new_attributes)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -19,14 +19,6 @@ impl Attributes {
|
|||||||
Attributes::Empty => None,
|
Attributes::Empty => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Attributes::Follow => true,
|
|
||||||
Attributes::Custom(data) => data.is_empty(),
|
|
||||||
Attributes::Empty => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::default::Default for Attributes {
|
impl std::default::Default for Attributes {
|
||||||
@ -95,7 +87,6 @@ pub fn transform_operation(left: &Option<Operation>, right: &Option<Operation>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes {
|
pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes {
|
||||||
log::info!("Invert attributes: {:?} : {:?}", attr, base);
|
|
||||||
let attr = attr.data();
|
let attr = attr.data();
|
||||||
let base = base.data();
|
let base = base.data();
|
||||||
|
|
||||||
|
@ -17,7 +17,8 @@ impl AttributesData {
|
|||||||
inner: HashMap::new(),
|
inner: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
|
pub fn is_plain(&self) -> bool {
|
||||||
self.inner.values().filter(|v| !should_remove(v)).count() == 0
|
self.inner.values().filter(|v| !should_remove(v)).count() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ impl AttributesDataRule for AttributesData {
|
|||||||
fn into_attributes(mut self) -> Attributes {
|
fn into_attributes(mut self) -> Attributes {
|
||||||
self.apply_rule();
|
self.apply_rule();
|
||||||
|
|
||||||
if self.is_empty() {
|
if self.is_plain() {
|
||||||
Attributes::Empty
|
Attributes::Empty
|
||||||
} else {
|
} else {
|
||||||
Attributes::Custom(self)
|
Attributes::Custom(self)
|
||||||
|
@ -89,7 +89,7 @@ impl Delta {
|
|||||||
if let Some(Operation::Delete(n_last)) = self.ops.last_mut() {
|
if let Some(Operation::Delete(n_last)) = self.ops.last_mut() {
|
||||||
*n_last += n;
|
*n_last += n;
|
||||||
} else {
|
} else {
|
||||||
self.ops.push(OpBuilder::delete(n).build());
|
self.ops.push(Builder::delete(n).build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,10 +110,10 @@ impl Delta {
|
|||||||
},
|
},
|
||||||
[.., op_last @ Operation::Delete(_)] => {
|
[.., op_last @ Operation::Delete(_)] => {
|
||||||
let new_last = op_last.clone();
|
let new_last = op_last.clone();
|
||||||
*op_last = OpBuilder::insert(s).attributes(attrs).build();
|
*op_last = Builder::insert(s).attributes(attrs).build();
|
||||||
Some(new_last)
|
Some(new_last)
|
||||||
},
|
},
|
||||||
_ => Some(OpBuilder::insert(s).attributes(attrs).build()),
|
_ => Some(Builder::insert(s).attributes(attrs).build()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match new_last {
|
match new_last {
|
||||||
@ -134,8 +134,7 @@ impl Delta {
|
|||||||
self.ops.push(new_op);
|
self.ops.push(new_op);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.ops
|
self.ops.push(Builder::retain(n).attributes(attrs).build());
|
||||||
.push(OpBuilder::retain(n).attributes(attrs).build());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +184,7 @@ impl Delta {
|
|||||||
match retain.cmp(&o_retain) {
|
match retain.cmp(&o_retain) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
new_delta.retain(retain.n, composed_attrs);
|
new_delta.retain(retain.n, composed_attrs);
|
||||||
next_op2 = Some(OpBuilder::retain(o_retain.n - retain.n).build());
|
next_op2 = Some(Builder::retain(o_retain.n - retain.n).build());
|
||||||
next_op1 = ops1.next();
|
next_op1 = ops1.next();
|
||||||
},
|
},
|
||||||
std::cmp::Ordering::Equal => {
|
std::cmp::Ordering::Equal => {
|
||||||
@ -195,7 +194,7 @@ impl Delta {
|
|||||||
},
|
},
|
||||||
std::cmp::Ordering::Greater => {
|
std::cmp::Ordering::Greater => {
|
||||||
new_delta.retain(o_retain.n, composed_attrs);
|
new_delta.retain(o_retain.n, composed_attrs);
|
||||||
next_op1 = Some(OpBuilder::retain(retain.n - o_retain.n).build());
|
next_op1 = Some(Builder::retain(retain.n - o_retain.n).build());
|
||||||
next_op2 = ops2.next();
|
next_op2 = ops2.next();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -204,7 +203,7 @@ impl Delta {
|
|||||||
match (num_chars(insert.as_bytes()) as usize).cmp(o_num) {
|
match (num_chars(insert.as_bytes()) as usize).cmp(o_num) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
next_op2 = Some(
|
next_op2 = Some(
|
||||||
OpBuilder::delete(*o_num - num_chars(insert.as_bytes()) as usize)
|
Builder::delete(*o_num - num_chars(insert.as_bytes()) as usize)
|
||||||
.attributes(insert.attributes.clone())
|
.attributes(insert.attributes.clone())
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
@ -216,7 +215,7 @@ impl Delta {
|
|||||||
},
|
},
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
next_op1 = Some(
|
next_op1 = Some(
|
||||||
OpBuilder::insert(
|
Builder::insert(
|
||||||
&insert.chars().skip(*o_num as usize).collect::<String>(),
|
&insert.chars().skip(*o_num as usize).collect::<String>(),
|
||||||
)
|
)
|
||||||
.build(),
|
.build(),
|
||||||
@ -237,7 +236,7 @@ impl Delta {
|
|||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
new_delta.insert(&insert.s, composed_attrs.clone());
|
new_delta.insert(&insert.s, composed_attrs.clone());
|
||||||
next_op2 = Some(
|
next_op2 = Some(
|
||||||
OpBuilder::retain(o_retain.n - insert.num_chars())
|
Builder::retain(o_retain.n - insert.num_chars())
|
||||||
.attributes(o_retain.attributes.clone())
|
.attributes(o_retain.attributes.clone())
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
@ -255,7 +254,7 @@ impl Delta {
|
|||||||
composed_attrs,
|
composed_attrs,
|
||||||
);
|
);
|
||||||
next_op1 = Some(
|
next_op1 = Some(
|
||||||
OpBuilder::insert(&chars.collect::<String>())
|
Builder::insert(&chars.collect::<String>())
|
||||||
// consider this situation:
|
// consider this situation:
|
||||||
// [insert:12345678 - retain:4],
|
// [insert:12345678 - retain:4],
|
||||||
// the attributes of "5678" should be empty and the following
|
// the attributes of "5678" should be empty and the following
|
||||||
@ -271,7 +270,7 @@ impl Delta {
|
|||||||
match retain.cmp(&o_num) {
|
match retain.cmp(&o_num) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
new_delta.delete(retain.n);
|
new_delta.delete(retain.n);
|
||||||
next_op2 = Some(OpBuilder::delete(*o_num - retain.n).build());
|
next_op2 = Some(Builder::delete(*o_num - retain.n).build());
|
||||||
next_op1 = ops1.next();
|
next_op1 = ops1.next();
|
||||||
},
|
},
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
@ -281,7 +280,7 @@ impl Delta {
|
|||||||
},
|
},
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
new_delta.delete(*o_num);
|
new_delta.delete(*o_num);
|
||||||
next_op1 = Some(OpBuilder::retain(retain.n - *o_num).build());
|
next_op1 = Some(Builder::retain(retain.n - *o_num).build());
|
||||||
next_op2 = ops2.next();
|
next_op2 = ops2.next();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -340,7 +339,7 @@ impl Delta {
|
|||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
a_prime.retain(retain.n, composed_attrs.clone());
|
a_prime.retain(retain.n, composed_attrs.clone());
|
||||||
b_prime.retain(retain.n, composed_attrs.clone());
|
b_prime.retain(retain.n, composed_attrs.clone());
|
||||||
next_op2 = Some(OpBuilder::retain(o_retain.n - retain.n).build());
|
next_op2 = Some(Builder::retain(o_retain.n - retain.n).build());
|
||||||
next_op1 = ops1.next();
|
next_op1 = ops1.next();
|
||||||
},
|
},
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
@ -352,14 +351,14 @@ impl Delta {
|
|||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
a_prime.retain(o_retain.n, composed_attrs.clone());
|
a_prime.retain(o_retain.n, composed_attrs.clone());
|
||||||
b_prime.retain(o_retain.n, composed_attrs.clone());
|
b_prime.retain(o_retain.n, composed_attrs.clone());
|
||||||
next_op1 = Some(OpBuilder::retain(retain.n - o_retain.n).build());
|
next_op1 = Some(Builder::retain(retain.n - o_retain.n).build());
|
||||||
next_op2 = ops2.next();
|
next_op2 = ops2.next();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(Some(Operation::Delete(i)), Some(Operation::Delete(j))) => match i.cmp(&j) {
|
(Some(Operation::Delete(i)), Some(Operation::Delete(j))) => match i.cmp(&j) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
next_op2 = Some(OpBuilder::delete(*j - *i).build());
|
next_op2 = Some(Builder::delete(*j - *i).build());
|
||||||
next_op1 = ops1.next();
|
next_op1 = ops1.next();
|
||||||
},
|
},
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
@ -367,7 +366,7 @@ impl Delta {
|
|||||||
next_op2 = ops2.next();
|
next_op2 = ops2.next();
|
||||||
},
|
},
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
next_op1 = Some(OpBuilder::delete(*i - *j).build());
|
next_op1 = Some(Builder::delete(*i - *j).build());
|
||||||
next_op2 = ops2.next();
|
next_op2 = ops2.next();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -375,7 +374,7 @@ impl Delta {
|
|||||||
match i.cmp(&o_retain) {
|
match i.cmp(&o_retain) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
a_prime.delete(*i);
|
a_prime.delete(*i);
|
||||||
next_op2 = Some(OpBuilder::retain(o_retain.n - *i).build());
|
next_op2 = Some(Builder::retain(o_retain.n - *i).build());
|
||||||
next_op1 = ops1.next();
|
next_op1 = ops1.next();
|
||||||
},
|
},
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
@ -385,7 +384,7 @@ impl Delta {
|
|||||||
},
|
},
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
a_prime.delete(o_retain.n);
|
a_prime.delete(o_retain.n);
|
||||||
next_op1 = Some(OpBuilder::delete(*i - o_retain.n).build());
|
next_op1 = Some(Builder::delete(*i - o_retain.n).build());
|
||||||
next_op2 = ops2.next();
|
next_op2 = ops2.next();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -394,7 +393,7 @@ impl Delta {
|
|||||||
match retain.cmp(&j) {
|
match retain.cmp(&j) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
b_prime.delete(retain.n);
|
b_prime.delete(retain.n);
|
||||||
next_op2 = Some(OpBuilder::delete(*j - retain.n).build());
|
next_op2 = Some(Builder::delete(*j - retain.n).build());
|
||||||
next_op1 = ops1.next();
|
next_op1 = ops1.next();
|
||||||
},
|
},
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
@ -404,7 +403,7 @@ impl Delta {
|
|||||||
},
|
},
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
b_prime.delete(*j);
|
b_prime.delete(*j);
|
||||||
next_op1 = Some(OpBuilder::retain(retain.n - *j).build());
|
next_op1 = Some(Builder::retain(retain.n - *j).build());
|
||||||
next_op2 = ops2.next();
|
next_op2 = ops2.next();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -485,10 +484,13 @@ impl Delta {
|
|||||||
|
|
||||||
let inverted_from_other =
|
let inverted_from_other =
|
||||||
|inverted: &mut Delta, operation: &Operation, start: usize, end: usize| {
|
|inverted: &mut Delta, operation: &Operation, start: usize, end: usize| {
|
||||||
|
log::debug!("invert op: {:?} [{}:{}]", operation, start, end);
|
||||||
|
|
||||||
let ops = other.ops_in_interval(Interval::new(start, end));
|
let ops = other.ops_in_interval(Interval::new(start, end));
|
||||||
ops.into_iter().for_each(|other_op| {
|
ops.into_iter().for_each(|other_op| {
|
||||||
match operation {
|
match operation {
|
||||||
Operation::Delete(_) => {
|
Operation::Delete(_) => {
|
||||||
|
log::debug!("add: {}", other_op);
|
||||||
inverted.add(other_op);
|
inverted.add(other_op);
|
||||||
},
|
},
|
||||||
Operation::Retain(_) => {
|
Operation::Retain(_) => {
|
||||||
@ -502,6 +504,7 @@ impl Delta {
|
|||||||
other_op.get_attributes(),
|
other_op.get_attributes(),
|
||||||
);
|
);
|
||||||
log::debug!("End invert attributes: {:?}", inverted_attrs);
|
log::debug!("End invert attributes: {:?}", inverted_attrs);
|
||||||
|
log::debug!("invert retain: {}, {}", other_op.length(), inverted_attrs);
|
||||||
inverted.retain(other_op.length(), inverted_attrs);
|
inverted.retain(other_op.length(), inverted_attrs);
|
||||||
},
|
},
|
||||||
Operation::Insert(_) => {
|
Operation::Insert(_) => {
|
||||||
@ -515,21 +518,25 @@ impl Delta {
|
|||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
for op in &self.ops {
|
for op in &self.ops {
|
||||||
let len: usize = op.length() as usize;
|
let len: usize = op.length() as usize;
|
||||||
log::info!("{:?}", op);
|
|
||||||
match op {
|
match op {
|
||||||
Operation::Delete(_) => {
|
Operation::Delete(n) => {
|
||||||
inverted_from_other(&mut inverted, op, index, index + len);
|
inverted_from_other(&mut inverted, op, index, index + *n);
|
||||||
index += len;
|
index += len;
|
||||||
},
|
},
|
||||||
Operation::Retain(_) => {
|
Operation::Retain(_) => {
|
||||||
match op.has_attribute() {
|
match op.has_attribute() {
|
||||||
true => inverted_from_other(&mut inverted, op, index, index + len),
|
true => inverted_from_other(&mut inverted, op, index, index + len),
|
||||||
false => inverted.retain(len as usize, op.get_attributes()),
|
false => {
|
||||||
|
log::debug!("invert retain op: {:?}", op);
|
||||||
|
inverted.retain(len as usize, op.get_attributes())
|
||||||
|
},
|
||||||
}
|
}
|
||||||
index += len;
|
index += len;
|
||||||
},
|
},
|
||||||
Operation::Insert(_) => {
|
Operation::Insert(insert) => {
|
||||||
|
log::debug!("invert insert op: {:?}", op);
|
||||||
inverted.delete(len as usize);
|
inverted.delete(len as usize);
|
||||||
|
// index += insert.s.len();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -549,29 +556,42 @@ impl Delta {
|
|||||||
|
|
||||||
pub fn is_empty(&self) -> bool { self.ops.is_empty() }
|
pub fn is_empty(&self) -> bool { self.ops.is_empty() }
|
||||||
|
|
||||||
pub fn ops_in_interval(&self, interval: Interval) -> Vec<Operation> {
|
pub fn ops_in_interval(&self, mut interval: Interval) -> Vec<Operation> {
|
||||||
let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
|
log::debug!("ops in delta: {:?}, at {:?}", self, interval);
|
||||||
let mut offset: usize = 0;
|
|
||||||
let mut ops_iter = self.ops.iter();
|
|
||||||
let mut next_op = ops_iter.next();
|
|
||||||
|
|
||||||
while offset < interval.end && next_op.is_some() {
|
let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
|
||||||
let op = next_op.take().unwrap();
|
let mut ops_iter = self.ops.iter();
|
||||||
let len = offset + op.length() as usize;
|
let mut maybe_next_op = ops_iter.next();
|
||||||
// log::info!("{:?}", op);
|
let mut offset: usize = 0;
|
||||||
while offset < len {
|
|
||||||
if offset < interval.start {
|
while maybe_next_op.is_some() {
|
||||||
offset += min(interval.start, op.length() as usize);
|
let next_op = maybe_next_op.take().unwrap();
|
||||||
} else {
|
if offset < interval.start {
|
||||||
if interval.contains(offset) {
|
if next_op.length() > interval.size() {
|
||||||
ops.push(op.shrink_to_interval(interval));
|
// if the op's length larger than the interval size, just shrink the op to that
|
||||||
offset += min(op.length() as usize, interval.size());
|
// interval. for example: delta: "123456", interval: [3,6).
|
||||||
} else {
|
// ┌──────────────┐
|
||||||
break;
|
// │ 1 2 3 4 5 6 │
|
||||||
|
// └───────▲───▲──┘
|
||||||
|
// │ │
|
||||||
|
// [3, 6)
|
||||||
|
if let Some(new_op) = next_op.shrink(interval) {
|
||||||
|
ops.push(new_op);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// adding the op's length to offset until the offset is contained in the
|
||||||
|
// interval
|
||||||
|
offset += next_op.length();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// the interval passed in the shrink function is base on the op not the delta.
|
||||||
|
if let Some(new_op) = next_op.shrink(interval.translate_neg(offset)) {
|
||||||
|
ops.push(new_op);
|
||||||
|
}
|
||||||
|
offset += min(interval.size(), next_op.length());
|
||||||
|
interval = Interval::new(offset, interval.end);
|
||||||
}
|
}
|
||||||
next_op = ops_iter.next();
|
maybe_next_op = ops_iter.next();
|
||||||
}
|
}
|
||||||
ops
|
ops
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
use crate::core::{Attributes, Operation};
|
use crate::core::{Attributes, Operation};
|
||||||
|
|
||||||
pub struct OpBuilder {
|
pub struct Builder {
|
||||||
ty: Operation,
|
ty: Operation,
|
||||||
attrs: Attributes,
|
attrs: Attributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpBuilder {
|
impl Builder {
|
||||||
pub fn new(ty: Operation) -> OpBuilder {
|
pub fn new(ty: Operation) -> Builder {
|
||||||
OpBuilder {
|
Builder {
|
||||||
ty,
|
ty,
|
||||||
attrs: Attributes::Empty,
|
attrs: Attributes::Empty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retain(n: usize) -> OpBuilder { OpBuilder::new(Operation::Retain(n.into())) }
|
pub fn retain(n: usize) -> Builder { Builder::new(Operation::Retain(n.into())) }
|
||||||
|
|
||||||
pub fn delete(n: usize) -> OpBuilder { OpBuilder::new(Operation::Delete(n)) }
|
pub fn delete(n: usize) -> Builder { Builder::new(Operation::Delete(n)) }
|
||||||
|
|
||||||
pub fn insert(s: &str) -> OpBuilder { OpBuilder::new(Operation::Insert(s.into())) }
|
pub fn insert(s: &str) -> Builder { Builder::new(Operation::Insert(s.into())) }
|
||||||
|
|
||||||
pub fn attributes(mut self, attrs: Attributes) -> OpBuilder {
|
pub fn attributes(mut self, attrs: Attributes) -> Builder {
|
||||||
self.attrs = attrs;
|
self.attrs = attrs;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::core::{Attributes, Interval, OpBuilder};
|
use crate::core::{Attributes, Builder, Interval};
|
||||||
use bytecount::num_chars;
|
use bytecount::num_chars;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::min,
|
cmp::min,
|
||||||
@ -54,11 +54,20 @@ impl Operation {
|
|||||||
pub fn has_attribute(&self) -> bool {
|
pub fn has_attribute(&self) -> bool {
|
||||||
match self.get_attributes() {
|
match self.get_attributes() {
|
||||||
Attributes::Follow => false,
|
Attributes::Follow => false,
|
||||||
Attributes::Custom(data) => !data.is_empty(),
|
// Attributes::Custom(data) => !data.is_plain(),
|
||||||
|
Attributes::Custom(data) => true,
|
||||||
Attributes::Empty => false,
|
Attributes::Empty => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_plain(&self) -> bool {
|
||||||
|
match self.get_attributes() {
|
||||||
|
Attributes::Follow => true,
|
||||||
|
Attributes::Custom(data) => data.is_plain(),
|
||||||
|
Attributes::Empty => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn length(&self) -> usize {
|
pub fn length(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Operation::Delete(n) => *n,
|
Operation::Delete(n) => *n,
|
||||||
@ -69,30 +78,27 @@ 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 {
|
pub fn shrink(&self, interval: Interval) -> Option<Operation> {
|
||||||
match self {
|
let op = match self {
|
||||||
Operation::Delete(n) => {
|
Operation::Delete(n) => Builder::delete(min(*n, interval.size())).build(),
|
||||||
//
|
Operation::Retain(retain) => Builder::retain(min(retain.n, interval.size()))
|
||||||
OpBuilder::delete(min(*n, interval.size())).build()
|
.attributes(retain.attributes.clone())
|
||||||
},
|
.build(),
|
||||||
Operation::Retain(retain) => {
|
|
||||||
//
|
|
||||||
OpBuilder::retain(min(retain.n, interval.size()))
|
|
||||||
.attributes(retain.attributes.clone())
|
|
||||||
.build()
|
|
||||||
},
|
|
||||||
Operation::Insert(insert) => {
|
Operation::Insert(insert) => {
|
||||||
// debug_assert!(insert.s.len() <= interval.size());
|
|
||||||
if interval.start > insert.s.len() {
|
if interval.start > insert.s.len() {
|
||||||
return OpBuilder::insert("").build();
|
Builder::insert("").build()
|
||||||
|
} else {
|
||||||
|
let s = &insert.s[interval.start..min(interval.end, insert.s.len())];
|
||||||
|
Builder::insert(s)
|
||||||
|
.attributes(insert.attributes.clone())
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
let end = min(interval.end, insert.s.len());
|
|
||||||
let s = &insert.s[interval.start..end];
|
|
||||||
OpBuilder::insert(s)
|
|
||||||
.attributes(insert.attributes.clone())
|
|
||||||
.build()
|
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match op.is_empty() {
|
||||||
|
true => None,
|
||||||
|
false => Some(op),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,11 +136,7 @@ pub struct Retain {
|
|||||||
|
|
||||||
impl Retain {
|
impl Retain {
|
||||||
pub fn merge_or_new_op(&mut self, n: usize, attributes: Attributes) -> Option<Operation> {
|
pub fn merge_or_new_op(&mut self, n: usize, attributes: Attributes) -> Option<Operation> {
|
||||||
log::debug!(
|
log::debug!("merge_retain_or_new_op: {:?}, {:?}", n, attributes);
|
||||||
"merge_retain_or_new_op: {:?}, {:?}",
|
|
||||||
self.attributes,
|
|
||||||
attributes
|
|
||||||
);
|
|
||||||
|
|
||||||
match &attributes {
|
match &attributes {
|
||||||
Attributes::Follow => {
|
Attributes::Follow => {
|
||||||
@ -149,7 +151,7 @@ impl Retain {
|
|||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
log::debug!("New retain op");
|
log::debug!("New retain op");
|
||||||
Some(OpBuilder::retain(n).attributes(attributes).build())
|
Some(Builder::retain(n).attributes(attributes).build())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -202,7 +204,7 @@ impl Insert {
|
|||||||
self.s += s;
|
self.s += s;
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(OpBuilder::insert(s).attributes(attributes).build())
|
Some(Builder::insert(s).attributes(attributes).build())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -222,4 +224,10 @@ impl std::convert::From<&str> for Insert {
|
|||||||
fn from(s: &str) -> Self { Insert::from(s.to_owned()) }
|
fn from(s: &str) -> Self { Insert::from(s.to_owned()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_empty(attributes: &Attributes) -> bool { attributes.is_empty() }
|
fn is_empty(attributes: &Attributes) -> bool {
|
||||||
|
match attributes {
|
||||||
|
Attributes::Follow => true,
|
||||||
|
Attributes::Custom(data) => data.is_plain(),
|
||||||
|
Attributes::Empty => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -50,7 +50,8 @@ impl OpTester {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
std::env::set_var("RUST_LOG", "debug");
|
color_eyre::install().unwrap();
|
||||||
|
std::env::set_var("RUST_LOG", "info");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
pub mod helper;
|
pub mod helper;
|
||||||
use crate::helper::{TestOp::*, *};
|
use crate::helper::{TestOp::*, *};
|
||||||
use flowy_ot::core::{Delta, Interval, OpBuilder};
|
use flowy_ot::core::{Builder, Delta, Interval};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_invert_no_attribute_delta() {
|
fn delta_invert_no_attribute_delta() {
|
||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
delta.add(OpBuilder::insert("123").build());
|
delta.add(Builder::insert("123").build());
|
||||||
|
|
||||||
let mut change = Delta::default();
|
let mut change = Delta::default();
|
||||||
change.add(OpBuilder::retain(3).build());
|
change.add(Builder::retain(3).build());
|
||||||
change.add(OpBuilder::insert("456").build());
|
change.add(Builder::insert("456").build());
|
||||||
let undo = change.invert(&delta);
|
let undo = change.invert(&delta);
|
||||||
|
|
||||||
let new_delta = delta.compose(&change).unwrap();
|
let new_delta = delta.compose(&change).unwrap();
|
||||||
@ -48,21 +48,30 @@ fn delta_invert_attribute_delta_with_no_attribute_delta2() {
|
|||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
Insert(0, "456", 3),
|
Insert(0, "456", 3),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[
|
||||||
|
{"insert":"123456","attributes":{"bold":"true"}}]
|
||||||
|
"#,
|
||||||
|
),
|
||||||
Italic(0, Interval::new(2, 4), true),
|
Italic(0, Interval::new(2, 4), true),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
0,
|
0,
|
||||||
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":
|
r#"[
|
||||||
{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"
|
{"insert":"12","attributes":{"bold":"true"}},
|
||||||
}}]"#,
|
{"insert":"34","attributes":{"bold":"true","italic":"true"}},
|
||||||
|
{"insert":"56","attributes":{"bold":"true"}}
|
||||||
|
]"#,
|
||||||
),
|
),
|
||||||
Insert(1, "abc", 0),
|
Insert(1, "abc", 0),
|
||||||
Invert(0, 1),
|
Invert(0, 1),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
0,
|
0,
|
||||||
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":
|
r#"[
|
||||||
{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"
|
{"insert":"12","attributes":{"bold":"true"}},
|
||||||
}}]"#,
|
{"insert":"34","attributes":{"bold":"true","italic":"true"}},
|
||||||
|
{"insert":"56","attributes":{"bold":"true"}}
|
||||||
|
]"#,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
@ -115,9 +124,11 @@ fn delta_invert_attribute_delta_with_attribute_delta() {
|
|||||||
Italic(0, Interval::new(2, 4), true),
|
Italic(0, Interval::new(2, 4), true),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
0,
|
0,
|
||||||
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":
|
r#"[
|
||||||
{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"
|
{"insert":"12","attributes":{"bold":"true"}},
|
||||||
}}]"#,
|
{"insert":"34","attributes":{"bold":"true","italic":"true"}},
|
||||||
|
{"insert":"56","attributes":{"bold":"true"}}
|
||||||
|
]"#,
|
||||||
),
|
),
|
||||||
Insert(1, "abc", 0),
|
Insert(1, "abc", 0),
|
||||||
Bold(1, Interval::new(0, 3), true),
|
Bold(1, Interval::new(0, 3), true),
|
||||||
@ -125,16 +136,20 @@ fn delta_invert_attribute_delta_with_attribute_delta() {
|
|||||||
Italic(1, Interval::new(1, 3), true),
|
Italic(1, Interval::new(1, 3), true),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
1,
|
1,
|
||||||
r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":
|
r#"[
|
||||||
{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"
|
{"insert":"a","attributes":{"bold":"true"}},
|
||||||
}}]"#,
|
{"insert":"bc","attributes":{"bold":"true","italic":"true"}},
|
||||||
|
{"insert":"d","attributes":{"bold":"true"}}
|
||||||
|
]"#,
|
||||||
),
|
),
|
||||||
Invert(0, 1),
|
Invert(0, 1),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
0,
|
0,
|
||||||
r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34","attributes":
|
r#"[
|
||||||
{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"
|
{"insert":"12","attributes":{"bold":"true"}},
|
||||||
}}]"#,
|
{"insert":"34","attributes":{"bold":"true","italic":"true"}},
|
||||||
|
{"insert":"56","attributes":{"bold":"true"}}
|
||||||
|
]"#,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
@ -143,8 +158,8 @@ fn delta_invert_attribute_delta_with_attribute_delta() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn delta_get_ops_in_interval_1() {
|
fn delta_get_ops_in_interval_1() {
|
||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
let insert_a = OpBuilder::insert("123").build();
|
let insert_a = Builder::insert("123").build();
|
||||||
let insert_b = OpBuilder::insert("4").build();
|
let insert_b = Builder::insert("4").build();
|
||||||
|
|
||||||
delta.add(insert_a.clone());
|
delta.add(insert_a.clone());
|
||||||
delta.add(insert_b.clone());
|
delta.add(insert_b.clone());
|
||||||
@ -158,16 +173,21 @@ fn delta_get_ops_in_interval_1() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn delta_get_ops_in_interval_2() {
|
fn delta_get_ops_in_interval_2() {
|
||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
let insert_a = OpBuilder::insert("123").build();
|
let insert_a = Builder::insert("123").build();
|
||||||
let insert_b = OpBuilder::insert("4").build();
|
let insert_b = Builder::insert("4").build();
|
||||||
let insert_c = OpBuilder::insert("5").build();
|
let insert_c = Builder::insert("5").build();
|
||||||
let retain_a = OpBuilder::retain(3).build();
|
let retain_a = Builder::retain(3).build();
|
||||||
|
|
||||||
delta.add(insert_a.clone());
|
delta.add(insert_a.clone());
|
||||||
delta.add(retain_a.clone());
|
delta.add(retain_a.clone());
|
||||||
delta.add(insert_b.clone());
|
delta.add(insert_b.clone());
|
||||||
delta.add(insert_c.clone());
|
delta.add(insert_c.clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(0, 2)),
|
||||||
|
vec![Builder::insert("12").build()]
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
delta.ops_in_interval(Interval::new(0, 3)),
|
delta.ops_in_interval(Interval::new(0, 3)),
|
||||||
vec![insert_a.clone()]
|
vec![insert_a.clone()]
|
||||||
@ -175,16 +195,48 @@ fn delta_get_ops_in_interval_2() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
delta.ops_in_interval(Interval::new(0, 4)),
|
delta.ops_in_interval(Interval::new(0, 4)),
|
||||||
|
vec![insert_a.clone(), Builder::retain(1).build()]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(0, 6)),
|
||||||
vec![insert_a.clone(), retain_a.clone()]
|
vec![insert_a.clone(), retain_a.clone()]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
delta.ops_in_interval(Interval::new(0, 7)),
|
delta.ops_in_interval(Interval::new(0, 7)),
|
||||||
vec![
|
vec![insert_a.clone(), retain_a.clone(), insert_b.clone()]
|
||||||
insert_a.clone(),
|
);
|
||||||
retain_a.clone(),
|
}
|
||||||
// insert_b and insert_c will be merged into one. insert: "45"
|
|
||||||
delta.ops.last().unwrap().clone()
|
#[test]
|
||||||
]
|
fn delta_get_ops_in_interval_3() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
let insert_a = Builder::insert("123456").build();
|
||||||
|
delta.add(insert_a.clone());
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(3, 6)),
|
||||||
|
vec![Builder::insert("456").build()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_get_ops_in_interval_4() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
let insert_a = Builder::insert("12").build();
|
||||||
|
let insert_b = Builder::insert("34").build();
|
||||||
|
let insert_c = Builder::insert("56").build();
|
||||||
|
|
||||||
|
delta.ops.push(insert_a.clone());
|
||||||
|
delta.ops.push(insert_b.clone());
|
||||||
|
delta.ops.push(insert_c.clone());
|
||||||
|
|
||||||
|
assert_eq!(delta.ops_in_interval(Interval::new(0, 2)), vec![insert_a]);
|
||||||
|
assert_eq!(delta.ops_in_interval(Interval::new(2, 4)), vec![insert_b]);
|
||||||
|
assert_eq!(delta.ops_in_interval(Interval::new(4, 6)), vec![insert_c]);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(2, 5)),
|
||||||
|
vec![Builder::insert("34").build(), Builder::insert("5").build()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -120,22 +120,22 @@ fn ops_merging() {
|
|||||||
assert_eq!(delta.ops.len(), 0);
|
assert_eq!(delta.ops.len(), 0);
|
||||||
delta.retain(2, Attributes::Empty);
|
delta.retain(2, Attributes::Empty);
|
||||||
assert_eq!(delta.ops.len(), 1);
|
assert_eq!(delta.ops.len(), 1);
|
||||||
assert_eq!(delta.ops.last(), Some(&OpBuilder::retain(2).build()));
|
assert_eq!(delta.ops.last(), Some(&Builder::retain(2).build()));
|
||||||
delta.retain(3, Attributes::Empty);
|
delta.retain(3, Attributes::Empty);
|
||||||
assert_eq!(delta.ops.len(), 1);
|
assert_eq!(delta.ops.len(), 1);
|
||||||
assert_eq!(delta.ops.last(), Some(&OpBuilder::retain(5).build()));
|
assert_eq!(delta.ops.last(), Some(&Builder::retain(5).build()));
|
||||||
delta.insert("abc", Attributes::Empty);
|
delta.insert("abc", Attributes::Empty);
|
||||||
assert_eq!(delta.ops.len(), 2);
|
assert_eq!(delta.ops.len(), 2);
|
||||||
assert_eq!(delta.ops.last(), Some(&OpBuilder::insert("abc").build()));
|
assert_eq!(delta.ops.last(), Some(&Builder::insert("abc").build()));
|
||||||
delta.insert("xyz", Attributes::Empty);
|
delta.insert("xyz", Attributes::Empty);
|
||||||
assert_eq!(delta.ops.len(), 2);
|
assert_eq!(delta.ops.len(), 2);
|
||||||
assert_eq!(delta.ops.last(), Some(&OpBuilder::insert("abcxyz").build()));
|
assert_eq!(delta.ops.last(), Some(&Builder::insert("abcxyz").build()));
|
||||||
delta.delete(1);
|
delta.delete(1);
|
||||||
assert_eq!(delta.ops.len(), 3);
|
assert_eq!(delta.ops.len(), 3);
|
||||||
assert_eq!(delta.ops.last(), Some(&OpBuilder::delete(1).build()));
|
assert_eq!(delta.ops.last(), Some(&Builder::delete(1).build()));
|
||||||
delta.delete(1);
|
delta.delete(1);
|
||||||
assert_eq!(delta.ops.len(), 3);
|
assert_eq!(delta.ops.len(), 3);
|
||||||
assert_eq!(delta.ops.last(), Some(&OpBuilder::delete(2).build()));
|
assert_eq!(delta.ops.last(), Some(&Builder::delete(2).build()));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn is_noop() {
|
fn is_noop() {
|
||||||
|
@ -3,7 +3,7 @@ use flowy_ot::core::*;
|
|||||||
#[test]
|
#[test]
|
||||||
fn operation_insert_serialize_test() {
|
fn operation_insert_serialize_test() {
|
||||||
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
|
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
|
||||||
let operation = OpBuilder::insert("123").attributes(attributes).build();
|
let operation = Builder::insert("123").attributes(attributes).build();
|
||||||
let json = serde_json::to_string(&operation).unwrap();
|
let json = serde_json::to_string(&operation).unwrap();
|
||||||
eprintln!("{}", json);
|
eprintln!("{}", json);
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ fn delta_serialize_test() {
|
|||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
|
|
||||||
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
|
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
|
||||||
let retain = OpBuilder::insert("123").attributes(attributes).build();
|
let retain = Builder::insert("123").attributes(attributes).build();
|
||||||
|
|
||||||
delta.add(retain);
|
delta.add(retain);
|
||||||
delta.add(Operation::Retain(5.into()));
|
delta.add(Operation::Retain(5.into()));
|
||||||
|
@ -71,3 +71,20 @@ fn delta_undo_attributes() {
|
|||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_redo_attributes() {
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, "\n", 0),
|
||||||
|
Insert(0, "123", 0),
|
||||||
|
Bold(0, Interval::new(0, 3), true),
|
||||||
|
Undo(0),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
||||||
|
Redo(0),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#" [{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
OpTester::new().run_script(ops);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user