mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix undo redo bugs, merge last undo if need
This commit is contained in:
parent
251b065dd8
commit
ca81c990bf
@ -51,10 +51,12 @@ class History {
|
|||||||
var undoDelta = change.invert(before);
|
var undoDelta = change.invert(before);
|
||||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
if (timestamp - lastRecorded < minRecordThreshold &&
|
if (stack.undo.isNotEmpty) {
|
||||||
stack.undo.isNotEmpty) {
|
|
||||||
final lastDelta = stack.undo.removeLast();
|
final lastDelta = stack.undo.removeLast();
|
||||||
|
print("undoDelta: $undoDelta");
|
||||||
|
print("lastDelta: $lastDelta");
|
||||||
undoDelta = undoDelta.compose(lastDelta);
|
undoDelta = undoDelta.compose(lastDelta);
|
||||||
|
print("compose result: $undoDelta");
|
||||||
} else {
|
} else {
|
||||||
lastRecorded = timestamp;
|
lastRecorded = timestamp;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ 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 }
|
color-eyre = { version = "0.5", default-features = false }
|
||||||
|
chrono = "0.4.19"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
client::{History, RevId, UndoResult},
|
client::{History, RevId, UndoResult},
|
||||||
core::{
|
core::*,
|
||||||
Attribute,
|
|
||||||
Attributes,
|
|
||||||
AttributesDataRule,
|
|
||||||
AttrsBuilder,
|
|
||||||
Builder,
|
|
||||||
Delta,
|
|
||||||
Interval,
|
|
||||||
Operation,
|
|
||||||
},
|
|
||||||
errors::{ErrorBuilder, OTError, OTErrorCode::*},
|
errors::{ErrorBuilder, OTError, OTErrorCode::*},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const RECORD_THRESHOLD: usize = 400; // in milliseconds
|
||||||
|
|
||||||
pub struct Document {
|
pub struct Document {
|
||||||
data: Delta,
|
data: Delta,
|
||||||
history: History,
|
history: History,
|
||||||
rev_id_counter: usize,
|
rev_id_counter: usize,
|
||||||
|
last_edit_time: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
@ -26,6 +20,7 @@ impl Document {
|
|||||||
data: delta,
|
data: delta,
|
||||||
history: History::new(),
|
history: History::new(),
|
||||||
rev_id_counter: 1,
|
rev_id_counter: 1,
|
||||||
|
last_edit_time: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,10 +37,12 @@ impl Document {
|
|||||||
if attributes == Attributes::Empty {
|
if attributes == Attributes::Empty {
|
||||||
attributes = Attributes::Follow;
|
attributes = Attributes::Follow;
|
||||||
}
|
}
|
||||||
|
let mut delta = Delta::new();
|
||||||
let insert = Builder::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);
|
||||||
|
delta.add(insert);
|
||||||
|
|
||||||
self.update_with_op(insert, interval)
|
self.update_with_op(&delta, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(
|
pub fn format(
|
||||||
@ -68,15 +65,14 @@ impl Document {
|
|||||||
|
|
||||||
pub fn undo(&mut self) -> Result<UndoResult, OTError> {
|
pub fn undo(&mut self) -> Result<UndoResult, OTError> {
|
||||||
match self.history.undo() {
|
match self.history.undo() {
|
||||||
None => Err(ErrorBuilder::new(UndoFail).build()),
|
None => Err(ErrorBuilder::new(UndoFail)
|
||||||
|
.msg("Undo stack is empty")
|
||||||
|
.build()),
|
||||||
Some(undo_delta) => {
|
Some(undo_delta) => {
|
||||||
log::debug!("undo: {:?}", undo_delta);
|
let (new_delta, inverted_delta) = self.invert_change(&undo_delta)?;
|
||||||
let composed_delta = self.data.compose(&undo_delta)?;
|
let result = UndoResult::success(new_delta.target_len as usize);
|
||||||
let redo_delta = undo_delta.invert(&self.data);
|
self.data = new_delta;
|
||||||
log::debug!("computed redo: {:?}", redo_delta);
|
self.history.add_redo(inverted_delta);
|
||||||
let result = UndoResult::success(composed_delta.target_len as usize);
|
|
||||||
self.data = composed_delta;
|
|
||||||
self.history.add_redo(redo_delta);
|
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
},
|
},
|
||||||
@ -87,22 +83,29 @@ 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, inverted_delta) = self.invert_change(&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);
|
|
||||||
log::debug!("computed undo: {:?}", undo_delta);
|
|
||||||
self.data = new_delta;
|
self.data = new_delta;
|
||||||
|
|
||||||
self.history.add_undo(undo_delta);
|
self.history.add_undo(inverted_delta);
|
||||||
Ok(result)
|
Ok(result)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, interval: Interval) -> Result<(), OTError> {
|
pub fn replace(&mut self, interval: Interval, s: &str) -> Result<(), OTError> {
|
||||||
let delete = Builder::delete(interval.size()).build();
|
let mut delta = Delta::default();
|
||||||
self.update_with_op(delete, interval)
|
if !s.is_empty() {
|
||||||
|
let insert = Builder::insert(s).attributes(Attributes::Follow).build();
|
||||||
|
delta.add(insert);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !interval.is_empty() {
|
||||||
|
let delete = Builder::delete(interval.size()).build();
|
||||||
|
delta.add(delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_with_op(&delta, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> String { self.data.to_json() }
|
pub fn to_json(&self) -> String { self.data.to_json() }
|
||||||
@ -113,7 +116,7 @@ impl Document {
|
|||||||
|
|
||||||
pub fn set_data(&mut self, data: Delta) { self.data = data; }
|
pub fn set_data(&mut self, data: Delta) { self.data = data; }
|
||||||
|
|
||||||
fn update_with_op(&mut self, op: Operation, interval: Interval) -> Result<(), OTError> {
|
fn update_with_op(&mut self, delta: &Delta, interval: Interval) -> Result<(), OTError> {
|
||||||
let mut new_delta = Delta::default();
|
let mut new_delta = Delta::default();
|
||||||
let (prefix, interval, suffix) = split_length_with_interval(self.data.target_len, interval);
|
let (prefix, interval, suffix) = split_length_with_interval(self.data.target_len, interval);
|
||||||
|
|
||||||
@ -122,33 +125,26 @@ impl Document {
|
|||||||
let intervals = split_interval_with_delta(&self.data, &prefix);
|
let intervals = split_interval_with_delta(&self.data, &prefix);
|
||||||
intervals.into_iter().for_each(|i| {
|
intervals.into_iter().for_each(|i| {
|
||||||
let attributes = self.data.get_attributes(i);
|
let attributes = self.data.get_attributes(i);
|
||||||
log::debug!("prefix attribute: {:?}, interval: {:?}", attributes, i);
|
log::trace!("prefix attribute: {:?}, interval: {:?}", attributes, i);
|
||||||
new_delta.retain(i.size() as usize, attributes);
|
new_delta.retain(i.size() as usize, attributes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("add new op: {:?}", op);
|
delta.ops.iter().for_each(|op| {
|
||||||
new_delta.add(op);
|
new_delta.add(op.clone());
|
||||||
|
});
|
||||||
|
|
||||||
// suffix
|
// suffix
|
||||||
if suffix.is_empty() == false {
|
if suffix.is_empty() == false {
|
||||||
let intervals = split_interval_with_delta(&self.data, &suffix);
|
let intervals = split_interval_with_delta(&self.data, &suffix);
|
||||||
intervals.into_iter().for_each(|i| {
|
intervals.into_iter().for_each(|i| {
|
||||||
let attributes = self.data.get_attributes(i);
|
let attributes = self.data.get_attributes(i);
|
||||||
log::debug!("suffix attribute: {:?}, interval: {:?}", attributes, i);
|
log::trace!("suffix attribute: {:?}, interval: {:?}", attributes, i);
|
||||||
new_delta.retain(i.size() as usize, attributes);
|
new_delta.retain(i.size() as usize, attributes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// c = a.compose(b)
|
self.data = self.record_change(&new_delta)?;
|
||||||
// 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 = composed_delta;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +155,7 @@ impl Document {
|
|||||||
) -> Result<(), OTError> {
|
) -> Result<(), OTError> {
|
||||||
let old_attributes = self.data.get_attributes(interval);
|
let old_attributes = self.data.get_attributes(interval);
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"merge attributes: {:?}, with old: {:?}",
|
"combine attributes: {:?} : {:?}",
|
||||||
attributes,
|
attributes,
|
||||||
old_attributes
|
old_attributes
|
||||||
);
|
);
|
||||||
@ -172,21 +168,54 @@ impl Document {
|
|||||||
Attributes::Empty => Attributes::Empty,
|
Attributes::Empty => Attributes::Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::debug!("new attributes: {:?}", new_attributes);
|
log::debug!("combined result: {:?}", new_attributes);
|
||||||
let retain = Builder::retain(interval.size())
|
let retain = Builder::retain(interval.size())
|
||||||
.attributes(new_attributes)
|
.attributes(new_attributes)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
log::debug!(
|
let mut delta = Delta::new();
|
||||||
"Update delta with new attributes: {:?} at: {:?}",
|
delta.add(retain);
|
||||||
retain,
|
|
||||||
interval
|
|
||||||
);
|
|
||||||
|
|
||||||
self.update_with_op(retain, interval)
|
self.update_with_op(&delta, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_rev_id(&self) -> RevId { RevId(self.rev_id_counter) }
|
fn next_rev_id(&self) -> RevId { RevId(self.rev_id_counter) }
|
||||||
|
|
||||||
|
fn record_change(&mut self, delta: &Delta) -> Result<Delta, OTError> {
|
||||||
|
let (composed_delta, mut undo_delta) = self.invert_change(&delta)?;
|
||||||
|
self.rev_id_counter += 1;
|
||||||
|
|
||||||
|
let now = chrono::Utc::now().timestamp_millis() as usize;
|
||||||
|
if now - self.last_edit_time < RECORD_THRESHOLD {
|
||||||
|
if let Some(last_delta) = self.history.undo() {
|
||||||
|
log::debug!("compose previous change");
|
||||||
|
log::debug!("current = {}", undo_delta);
|
||||||
|
log::debug!("previous = {}", last_delta);
|
||||||
|
undo_delta = undo_delta.compose(&last_delta)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.last_edit_time = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !undo_delta.is_empty() {
|
||||||
|
log::debug!("record change: {}", undo_delta);
|
||||||
|
self.history.record(undo_delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(composed_delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invert_change(&self, change: &Delta) -> Result<(Delta, Delta), OTError> {
|
||||||
|
// c = a.compose(b)
|
||||||
|
// d = b.invert(a)
|
||||||
|
// a = c.compose(d)
|
||||||
|
log::debug!("👉invert change {}", change);
|
||||||
|
let new_delta = self.data.compose(change)?;
|
||||||
|
let mut inverted_delta = change.invert(&self.data);
|
||||||
|
// trim(&mut inverted_delta);
|
||||||
|
|
||||||
|
Ok((new_delta, inverted_delta))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) {
|
fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) {
|
||||||
@ -216,3 +245,17 @@ fn split_interval_with_delta(delta: &Delta, interval: &Interval) -> Vec<Interval
|
|||||||
});
|
});
|
||||||
new_intervals
|
new_intervals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn trim(delta: &mut Delta) {
|
||||||
|
let remove_last = match delta.ops.last() {
|
||||||
|
None => false,
|
||||||
|
Some(op) => match op {
|
||||||
|
Operation::Delete(_) => false,
|
||||||
|
Operation::Retain(retain) => retain.is_plain(),
|
||||||
|
Operation::Insert(_) => false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if remove_last {
|
||||||
|
delta.ops.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,7 +36,7 @@ impl UndoResult {
|
|||||||
pub struct History {
|
pub struct History {
|
||||||
cur_undo: usize,
|
cur_undo: usize,
|
||||||
undos: Vec<Delta>,
|
undos: Vec<Delta>,
|
||||||
redos: Vec<Delta>,
|
redoes: Vec<Delta>,
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,25 +45,25 @@ impl History {
|
|||||||
History {
|
History {
|
||||||
cur_undo: 1,
|
cur_undo: 1,
|
||||||
undos: Vec::new(),
|
undos: Vec::new(),
|
||||||
redos: Vec::new(),
|
redoes: Vec::new(),
|
||||||
capacity: 20,
|
capacity: 20,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_undo(&self) -> bool { !self.undos.is_empty() }
|
pub fn can_undo(&self) -> bool { !self.undos.is_empty() }
|
||||||
|
|
||||||
pub fn can_redo(&self) -> bool { !self.redos.is_empty() }
|
pub fn can_redo(&self) -> bool { !self.redoes.is_empty() }
|
||||||
|
|
||||||
pub fn add_undo(&mut self, delta: Delta) { self.undos.push(delta); }
|
pub fn add_undo(&mut self, delta: Delta) { self.undos.push(delta); }
|
||||||
|
|
||||||
pub fn add_redo(&mut self, delta: Delta) { self.redos.push(delta); }
|
pub fn add_redo(&mut self, delta: Delta) { self.redoes.push(delta); }
|
||||||
|
|
||||||
pub fn record(&mut self, delta: Delta) {
|
pub fn record(&mut self, delta: Delta) {
|
||||||
if delta.ops.is_empty() {
|
if delta.ops.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.redos.clear();
|
self.redoes.clear();
|
||||||
self.add_undo(delta);
|
self.add_undo(delta);
|
||||||
|
|
||||||
if self.undos.len() > self.capacity {
|
if self.undos.len() > self.capacity {
|
||||||
@ -84,7 +84,7 @@ impl History {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let delta = self.redos.pop().unwrap();
|
let delta = self.redoes.pop().unwrap();
|
||||||
Some(delta)
|
Some(delta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::core::{Attribute, AttributesData, AttributesRule, Operation};
|
use crate::core::{Attribute, AttributesData, AttributesRule, Operation};
|
||||||
use std::{collections::HashMap, fmt};
|
use std::{collections::HashMap, fmt, fmt::Formatter};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
@ -11,6 +11,16 @@ pub enum Attributes {
|
|||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Attributes {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Attributes::Follow => f.write_str("follow"),
|
||||||
|
Attributes::Custom(data) => f.write_fmt(format_args!("{:?}", data.inner)),
|
||||||
|
Attributes::Empty => f.write_str("empty"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Attributes {
|
impl Attributes {
|
||||||
pub fn data(&self) -> Option<AttributesData> {
|
pub fn data(&self) -> Option<AttributesData> {
|
||||||
match self {
|
match self {
|
||||||
@ -25,23 +35,6 @@ impl std::default::Default for Attributes {
|
|||||||
fn default() -> Self { Attributes::Empty }
|
fn default() -> Self { Attributes::Empty }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Attributes {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Attributes::Follow => {
|
|
||||||
f.write_str("")?;
|
|
||||||
},
|
|
||||||
Attributes::Custom(data) => {
|
|
||||||
f.write_fmt(format_args!("{:?}", data.inner))?;
|
|
||||||
},
|
|
||||||
Attributes::Empty => {
|
|
||||||
f.write_str("")?;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
|
pub(crate) fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
|
||||||
match operation {
|
match operation {
|
||||||
None => None,
|
None => None,
|
||||||
@ -139,7 +132,7 @@ fn compose_attributes(left: Attributes, right: Attributes) -> Attributes {
|
|||||||
};
|
};
|
||||||
|
|
||||||
log::trace!("composed_attributes: a: {:?}", attr);
|
log::trace!("composed_attributes: a: {:?}", attr);
|
||||||
attr.apply_rule()
|
attr
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_attributes(left: Attributes, right: Attributes) -> Attributes {
|
fn transform_attributes(left: Attributes, right: Attributes) -> Attributes {
|
||||||
|
@ -18,9 +18,7 @@ impl AttributesData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_plain(&self) -> bool {
|
pub fn is_plain(&self) -> bool { self.inner.is_empty() }
|
||||||
self.inner.values().filter(|v| !should_remove(v)).count() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, attribute: &Attribute) {
|
pub fn remove(&mut self, attribute: &Attribute) {
|
||||||
self.inner.insert(attribute.clone(), REMOVE_FLAG.to_owned());
|
self.inner.insert(attribute.clone(), REMOVE_FLAG.to_owned());
|
||||||
|
@ -43,10 +43,12 @@ impl<T: AsRef<str>> From<T> for Delta {
|
|||||||
|
|
||||||
impl fmt::Display for Delta {
|
impl fmt::Display for Delta {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?;
|
// f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?;
|
||||||
// for op in &self.ops {
|
f.write_str("[ ")?;
|
||||||
// f.write_fmt(format_args!("{}", op));
|
for op in &self.ops {
|
||||||
// }
|
f.write_fmt(format_args!("{} ", op));
|
||||||
|
}
|
||||||
|
f.write_str("]")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,7 +186,11 @@ 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(Builder::retain(o_retain.n - retain.n).build());
|
next_op2 = Some(
|
||||||
|
Builder::retain(o_retain.n - retain.n)
|
||||||
|
.attributes(o_retain.attributes.clone())
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
next_op1 = ops1.next();
|
next_op1 = ops1.next();
|
||||||
},
|
},
|
||||||
std::cmp::Ordering::Equal => {
|
std::cmp::Ordering::Equal => {
|
||||||
@ -225,11 +231,13 @@ impl Delta {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
(Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => {
|
(Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => {
|
||||||
let composed_attrs = compose_operation(&next_op1, &next_op2);
|
let mut composed_attrs = compose_operation(&next_op1, &next_op2);
|
||||||
|
composed_attrs = composed_attrs.apply_rule();
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"[insert:{} - retain:{}]: {:?}",
|
"compose: [{} - {}], composed_attrs: {}",
|
||||||
insert.s,
|
insert,
|
||||||
o_retain.n,
|
o_retain,
|
||||||
composed_attrs
|
composed_attrs
|
||||||
);
|
);
|
||||||
match (insert.num_chars()).cmp(o_retain) {
|
match (insert.num_chars()).cmp(o_retain) {
|
||||||
@ -481,7 +489,9 @@ impl Delta {
|
|||||||
if other.is_empty() {
|
if other.is_empty() {
|
||||||
return inverted;
|
return inverted;
|
||||||
}
|
}
|
||||||
|
log::debug!("🌜Calculate invert delta");
|
||||||
|
log::debug!("current: {}", self);
|
||||||
|
log::debug!("other: {}", other);
|
||||||
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;
|
||||||
@ -494,19 +504,25 @@ impl Delta {
|
|||||||
match op.has_attribute() {
|
match op.has_attribute() {
|
||||||
true => invert_from_other(&mut inverted, other, op, index, index + len),
|
true => invert_from_other(&mut inverted, other, op, index, index + len),
|
||||||
false => {
|
false => {
|
||||||
log::debug!("invert retain op: {:?}", op);
|
log::debug!(
|
||||||
|
"invert retain: {} by retain {} {}",
|
||||||
|
op,
|
||||||
|
len,
|
||||||
|
op.get_attributes()
|
||||||
|
);
|
||||||
inverted.retain(len as usize, op.get_attributes())
|
inverted.retain(len as usize, op.get_attributes())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
index += len;
|
index += len;
|
||||||
},
|
},
|
||||||
Operation::Insert(_) => {
|
Operation::Insert(_) => {
|
||||||
log::debug!("invert insert op: {:?}", op);
|
log::debug!("invert insert: {} by delete {}", op, len);
|
||||||
inverted.delete(len as usize);
|
inverted.delete(len as usize);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::debug!("🌛invert result: {}", inverted);
|
||||||
inverted
|
inverted
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,7 +539,8 @@ 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, mut interval: Interval) -> Vec<Operation> {
|
pub fn ops_in_interval(&self, mut interval: Interval) -> Vec<Operation> {
|
||||||
log::debug!("ops in delta: {:?}, at {:?}", self, interval);
|
log::debug!("try get ops in delta: {} at {}", self, interval);
|
||||||
|
|
||||||
let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
|
let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
|
||||||
let mut ops_iter = self.ops.iter();
|
let mut ops_iter = self.ops.iter();
|
||||||
let mut maybe_next_op = ops_iter.next();
|
let mut maybe_next_op = ops_iter.next();
|
||||||
@ -534,32 +551,32 @@ impl Delta {
|
|||||||
if offset < interval.start {
|
if offset < interval.start {
|
||||||
let next_op_i = Interval::new(offset, offset + next_op.length());
|
let next_op_i = Interval::new(offset, offset + next_op.length());
|
||||||
let intersect = next_op_i.intersect(interval);
|
let intersect = next_op_i.intersect(interval);
|
||||||
if !intersect.is_empty() {
|
if intersect.is_empty() {
|
||||||
// if the op's length larger than the interval size, just shrink the op to that
|
offset += next_op_i.size();
|
||||||
// interval Checkout the delta_get_ops_in_interval_3 test for more details.
|
} else {
|
||||||
// ┌──────────────┐
|
if let Some(new_op) = next_op.shrink(intersect.translate_neg(offset)) {
|
||||||
// │ 1 2 3 4 5 6 │
|
// shrink the op to fit the intersect range
|
||||||
// └───────▲───▲──┘
|
// ┌──────────────┐
|
||||||
// │ │
|
// │ 1 2 3 4 5 6 │
|
||||||
// [3, 5)
|
// └───────▲───▲──┘
|
||||||
// op = "45"
|
// │ │
|
||||||
if let Some(new_op) = next_op.shrink(intersect) {
|
// [3, 5)
|
||||||
offset += min(intersect.end, next_op.length());
|
// op = "45"
|
||||||
interval = Interval::new(offset, interval.end);
|
|
||||||
ops.push(new_op);
|
ops.push(new_op);
|
||||||
}
|
}
|
||||||
} else {
|
offset = intersect.end;
|
||||||
offset += next_op_i.size();
|
interval = Interval::new(offset, interval.end);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// the interval passed in the shrink function is base on the op not the delta.
|
// 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)) {
|
if let Some(new_op) = next_op.shrink(interval.translate_neg(offset)) {
|
||||||
ops.push(new_op);
|
ops.push(new_op);
|
||||||
}
|
}
|
||||||
// take the small value between interval.size() and next_op.length().
|
|
||||||
// for example: extract the ops from three insert ops with interval [2,5). the
|
// for example: extract the ops from three insert ops with interval [2,5). the
|
||||||
// interval size is larger than the op. Run the
|
// interval size is larger than the op. Moving the offset to extract each part.
|
||||||
// delta_get_ops_in_interval_4 for more details.
|
// Each step would be the small value between interval.size() and
|
||||||
|
// next_op.length(). Checkout the delta_get_ops_in_interval_4 for more details.
|
||||||
|
//
|
||||||
// ┌──────┐ ┌──────┐ ┌──────┐
|
// ┌──────┐ ┌──────┐ ┌──────┐
|
||||||
// │ 1 2 │ │ 3 4 │ │ 5 6 │
|
// │ 1 2 │ │ 3 4 │ │ 5 6 │
|
||||||
// └──────┘ └─▲────┘ └───▲──┘
|
// └──────┘ └─▲────┘ └───▲──┘
|
||||||
@ -570,6 +587,7 @@ impl Delta {
|
|||||||
}
|
}
|
||||||
maybe_next_op = ops_iter.next();
|
maybe_next_op = ops_iter.next();
|
||||||
}
|
}
|
||||||
|
log::debug!("did get ops : {:?}", ops);
|
||||||
ops
|
ops
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,36 +632,38 @@ impl Delta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn invert_from_other(
|
fn invert_from_other(
|
||||||
inverted: &mut Delta,
|
base: &mut Delta,
|
||||||
other: &Delta,
|
other: &Delta,
|
||||||
operation: &Operation,
|
operation: &Operation,
|
||||||
start: usize,
|
start: usize,
|
||||||
end: usize,
|
end: usize,
|
||||||
) {
|
) {
|
||||||
log::debug!("invert op: {:?} [{}:{}]", operation, start, end);
|
log::debug!("invert op: {} [{}:{}]", operation, start, end);
|
||||||
let ops = other.ops_in_interval(Interval::new(start, end));
|
let other_ops = other.ops_in_interval(Interval::new(start, end));
|
||||||
ops.into_iter().for_each(|other_op| {
|
other_ops.into_iter().for_each(|other_op| match operation {
|
||||||
match operation {
|
Operation::Delete(n) => {
|
||||||
Operation::Delete(_) => {
|
log::debug!("invert delete: {} by add {}", n, other_op);
|
||||||
log::debug!("add: {}", other_op);
|
base.add(other_op);
|
||||||
inverted.add(other_op);
|
},
|
||||||
},
|
Operation::Retain(retain) => {
|
||||||
Operation::Retain(_) => {
|
log::debug!(
|
||||||
log::debug!(
|
"invert attributes: {:?}, {:?}",
|
||||||
"Start invert attributes: {:?}, {:?}",
|
operation.get_attributes(),
|
||||||
operation.get_attributes(),
|
other_op.get_attributes()
|
||||||
other_op.get_attributes()
|
);
|
||||||
);
|
let inverted_attrs =
|
||||||
let inverted_attrs =
|
invert_attributes(operation.get_attributes(), other_op.get_attributes());
|
||||||
invert_attributes(operation.get_attributes(), other_op.get_attributes());
|
log::debug!("invert result: {:?}", inverted_attrs);
|
||||||
log::debug!("End invert attributes: {:?}", inverted_attrs);
|
log::debug!(
|
||||||
log::debug!("invert retain: {}, {}", other_op.length(), inverted_attrs);
|
"invert retain: {} by retain len: {}, {}",
|
||||||
inverted.retain(other_op.length(), inverted_attrs);
|
retain,
|
||||||
},
|
other_op.length(),
|
||||||
Operation::Insert(_) => {
|
inverted_attrs
|
||||||
// Impossible to here
|
);
|
||||||
panic!()
|
base.retain(other_op.length(), inverted_attrs);
|
||||||
},
|
},
|
||||||
}
|
Operation::Insert(_) => {
|
||||||
|
log::error!("Impossible to here. Insert operation should be treated as delete")
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::core::{Attributes, Builder, Interval};
|
use crate::core::{Attributes, Builder, Interval};
|
||||||
use bytecount::num_chars;
|
use bytecount::num_chars;
|
||||||
|
use serde::__private::Formatter;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::min,
|
cmp::min,
|
||||||
fmt,
|
fmt,
|
||||||
@ -60,14 +61,6 @@ impl Operation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@ -105,23 +98,19 @@ impl Operation {
|
|||||||
|
|
||||||
impl fmt::Display for Operation {
|
impl fmt::Display for Operation {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("{");
|
||||||
match self {
|
match self {
|
||||||
Operation::Delete(n) => {
|
Operation::Delete(n) => {
|
||||||
f.write_fmt(format_args!("delete: {}", n))?;
|
f.write_fmt(format_args!("delete: {}", n))?;
|
||||||
},
|
},
|
||||||
Operation::Retain(r) => {
|
Operation::Retain(r) => {
|
||||||
f.write_fmt(format_args!(
|
f.write_fmt(format_args!("{}", r))?;
|
||||||
"retain: {}, attributes: {}",
|
|
||||||
r.n, r.attributes
|
|
||||||
))?;
|
|
||||||
},
|
},
|
||||||
Operation::Insert(i) => {
|
Operation::Insert(i) => {
|
||||||
f.write_fmt(format_args!(
|
f.write_fmt(format_args!("{}", i))?;
|
||||||
"insert: {}, attributes: {}",
|
|
||||||
i.s, i.attributes
|
|
||||||
))?;
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
f.write_str("}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,9 +123,23 @@ pub struct Retain {
|
|||||||
pub attributes: Attributes,
|
pub attributes: Attributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Retain {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"retain: {}, attributes: {}",
|
||||||
|
self.n, self.attributes
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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!("merge_retain_or_new_op: {:?}, {:?}", n, attributes);
|
log::debug!(
|
||||||
|
"merge_retain_or_new_op: len: {:?}, l: {} - r: {}",
|
||||||
|
n,
|
||||||
|
self.attributes,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
|
||||||
match &attributes {
|
match &attributes {
|
||||||
Attributes::Follow => {
|
Attributes::Follow => {
|
||||||
@ -146,16 +149,22 @@ impl Retain {
|
|||||||
},
|
},
|
||||||
Attributes::Custom(_) | Attributes::Empty => {
|
Attributes::Custom(_) | Attributes::Empty => {
|
||||||
if self.attributes == attributes {
|
if self.attributes == attributes {
|
||||||
log::debug!("Attribute equal");
|
|
||||||
self.n += n;
|
self.n += n;
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
log::debug!("New retain op");
|
|
||||||
Some(Builder::retain(n).attributes(attributes).build())
|
Some(Builder::retain(n).attributes(attributes).build())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_plain(&self) -> bool {
|
||||||
|
match &self.attributes {
|
||||||
|
Attributes::Follow => true,
|
||||||
|
Attributes::Custom(data) => data.is_plain(),
|
||||||
|
Attributes::Empty => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<usize> for Retain {
|
impl std::convert::From<usize> for Retain {
|
||||||
@ -186,6 +195,23 @@ pub struct Insert {
|
|||||||
pub attributes: Attributes,
|
pub attributes: Attributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Insert {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut s = self.s.clone();
|
||||||
|
if s.ends_with("\n") {
|
||||||
|
s.pop();
|
||||||
|
if s.is_empty() {
|
||||||
|
s = "new_line".to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"insert: {}, attributes: {}",
|
||||||
|
s, self.attributes
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Insert {
|
impl Insert {
|
||||||
pub fn as_bytes(&self) -> &[u8] { self.s.as_bytes() }
|
pub fn as_bytes(&self) -> &[u8] { self.s.as_bytes() }
|
||||||
|
|
||||||
|
@ -352,7 +352,7 @@ fn delta_compose_attr_delta_with_no_attr_delta_test() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_delete_heading() {
|
fn delta_replace_heading() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -364,7 +364,7 @@ fn delta_delete_heading() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_delete_trailing() {
|
fn delta_replace_trailing() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -376,7 +376,7 @@ fn delta_delete_trailing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_delete_middle() {
|
fn delta_replace_middle() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -390,7 +390,7 @@ fn delta_delete_middle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_delete_all() {
|
fn delta_replace_all() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -400,3 +400,21 @@ fn delta_delete_all() {
|
|||||||
|
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_replace_with_text() {
|
||||||
|
let ops = vec![
|
||||||
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
|
Replace(0, Interval::new(0, 3), "ab"),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[
|
||||||
|
{"insert":"ab"},
|
||||||
|
{"insert":"456","attributes":{"bold":"true"}}]
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
OpTester::new().run_script(ops);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use flowy_ot::{client::Document, core::*};
|
use flowy_ot::{client::Document, core::*};
|
||||||
use rand::{prelude::*, Rng as WrappedRng};
|
use rand::{prelude::*, Rng as WrappedRng};
|
||||||
use std::sync::Once;
|
use std::{sync::Once, thread::sleep_ms, time::Duration};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Display)]
|
#[derive(Clone, Debug, Display)]
|
||||||
pub enum TestOp {
|
pub enum TestOp {
|
||||||
@ -19,6 +19,9 @@ pub enum TestOp {
|
|||||||
#[display(fmt = "Delete")]
|
#[display(fmt = "Delete")]
|
||||||
Delete(usize, Interval),
|
Delete(usize, Interval),
|
||||||
|
|
||||||
|
#[display(fmt = "Replace")]
|
||||||
|
Replace(usize, Interval, &'static str),
|
||||||
|
|
||||||
#[display(fmt = "Italic")]
|
#[display(fmt = "Italic")]
|
||||||
Italic(usize, Interval, bool),
|
Italic(usize, Interval, bool),
|
||||||
|
|
||||||
@ -35,6 +38,9 @@ pub enum TestOp {
|
|||||||
#[display(fmt = "Redo")]
|
#[display(fmt = "Redo")]
|
||||||
Redo(usize),
|
Redo(usize),
|
||||||
|
|
||||||
|
#[display(fmt = "Wait")]
|
||||||
|
Wait(usize),
|
||||||
|
|
||||||
#[display(fmt = "AssertStr")]
|
#[display(fmt = "AssertStr")]
|
||||||
AssertStr(usize, &'static str),
|
AssertStr(usize, &'static str),
|
||||||
|
|
||||||
@ -51,7 +57,7 @@ impl OpTester {
|
|||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
color_eyre::install().unwrap();
|
color_eyre::install().unwrap();
|
||||||
std::env::set_var("RUST_LOG", "info");
|
std::env::set_var("RUST_LOG", "debug");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,7 +77,11 @@ impl OpTester {
|
|||||||
},
|
},
|
||||||
TestOp::Delete(delta_i, interval) => {
|
TestOp::Delete(delta_i, interval) => {
|
||||||
let document = &mut self.documents[*delta_i];
|
let document = &mut self.documents[*delta_i];
|
||||||
document.delete(*interval).unwrap();
|
document.replace(*interval, "").unwrap();
|
||||||
|
},
|
||||||
|
TestOp::Replace(delta_i, interval, s) => {
|
||||||
|
let document = &mut self.documents[*delta_i];
|
||||||
|
document.replace(*interval, s).unwrap();
|
||||||
},
|
},
|
||||||
TestOp::InsertBold(delta_i, s, interval) => {
|
TestOp::InsertBold(delta_i, s, interval) => {
|
||||||
let document = &mut self.documents[*delta_i];
|
let document = &mut self.documents[*delta_i];
|
||||||
@ -131,6 +141,9 @@ impl OpTester {
|
|||||||
TestOp::Redo(delta_i) => {
|
TestOp::Redo(delta_i) => {
|
||||||
self.documents[*delta_i].redo().unwrap();
|
self.documents[*delta_i].redo().unwrap();
|
||||||
},
|
},
|
||||||
|
TestOp::Wait(mills_sec) => {
|
||||||
|
std::thread::sleep(Duration::from_millis(*mills_sec as u64));
|
||||||
|
},
|
||||||
TestOp::AssertStr(delta_i, expected) => {
|
TestOp::AssertStr(delta_i, expected) => {
|
||||||
assert_eq!(&self.documents[*delta_i].to_string(), expected);
|
assert_eq!(&self.documents[*delta_i].to_string(), expected);
|
||||||
},
|
},
|
||||||
|
@ -154,113 +154,3 @@ fn delta_invert_attribute_delta_with_attribute_delta() {
|
|||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn delta_get_ops_in_interval_1() {
|
|
||||||
let mut delta = Delta::default();
|
|
||||||
let insert_a = Builder::insert("123").build();
|
|
||||||
let insert_b = Builder::insert("4").build();
|
|
||||||
|
|
||||||
delta.add(insert_a.clone());
|
|
||||||
delta.add(insert_b.clone());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
delta.ops_in_interval(Interval::new(0, 4)),
|
|
||||||
vec![delta.ops.last().unwrap().clone()]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn delta_get_ops_in_interval_2() {
|
|
||||||
let mut delta = Delta::default();
|
|
||||||
let insert_a = Builder::insert("123").build();
|
|
||||||
let insert_b = Builder::insert("4").build();
|
|
||||||
let insert_c = Builder::insert("5").build();
|
|
||||||
let retain_a = Builder::retain(3).build();
|
|
||||||
|
|
||||||
delta.add(insert_a.clone());
|
|
||||||
delta.add(retain_a.clone());
|
|
||||||
delta.add(insert_b.clone());
|
|
||||||
delta.add(insert_c.clone());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
delta.ops_in_interval(Interval::new(0, 2)),
|
|
||||||
vec![Builder::insert("12").build()]
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
delta.ops_in_interval(Interval::new(0, 3)),
|
|
||||||
vec![insert_a.clone()]
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
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()]
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
delta.ops_in_interval(Interval::new(0, 7)),
|
|
||||||
vec![insert_a.clone(), retain_a.clone(), insert_b.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, 5)),
|
|
||||||
vec![Builder::insert("45").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()]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn delta_get_ops_in_interval_5() {
|
|
||||||
let mut delta = Delta::default();
|
|
||||||
let insert_a = Builder::insert("123456").build();
|
|
||||||
let insert_b = Builder::insert("789").build();
|
|
||||||
delta.ops.push(insert_a.clone());
|
|
||||||
delta.ops.push(insert_b.clone());
|
|
||||||
assert_eq!(
|
|
||||||
delta.ops_in_interval(Interval::new(4, 8)),
|
|
||||||
vec![Builder::insert("56").build(), Builder::insert("78").build()]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn delta_get_ops_in_interval_6() {
|
|
||||||
let mut delta = Delta::default();
|
|
||||||
let insert_a = Builder::insert("12345678").build();
|
|
||||||
delta.add(insert_a.clone());
|
|
||||||
assert_eq!(
|
|
||||||
delta.ops_in_interval(Interval::new(4, 6)),
|
|
||||||
vec![Builder::insert("56").build()]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -5,6 +5,121 @@ use bytecount::num_chars;
|
|||||||
use flowy_ot::core::*;
|
use flowy_ot::core::*;
|
||||||
use helper::*;
|
use helper::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_get_ops_in_interval_1() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
let insert_a = Builder::insert("123").build();
|
||||||
|
let insert_b = Builder::insert("4").build();
|
||||||
|
|
||||||
|
delta.add(insert_a.clone());
|
||||||
|
delta.add(insert_b.clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(0, 4)),
|
||||||
|
vec![delta.ops.last().unwrap().clone()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_get_ops_in_interval_2() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
let insert_a = Builder::insert("123").build();
|
||||||
|
let insert_b = Builder::insert("4").build();
|
||||||
|
let insert_c = Builder::insert("5").build();
|
||||||
|
let retain_a = Builder::retain(3).build();
|
||||||
|
|
||||||
|
delta.add(insert_a.clone());
|
||||||
|
delta.add(retain_a.clone());
|
||||||
|
delta.add(insert_b.clone());
|
||||||
|
delta.add(insert_c.clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(0, 2)),
|
||||||
|
vec![Builder::insert("12").build()]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(0, 3)),
|
||||||
|
vec![insert_a.clone()]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
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()]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(0, 7)),
|
||||||
|
vec![insert_a.clone(), retain_a.clone(), insert_b.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, 5)),
|
||||||
|
vec![Builder::insert("45").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()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_get_ops_in_interval_5() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
let insert_a = Builder::insert("123456").build();
|
||||||
|
let insert_b = Builder::insert("789").build();
|
||||||
|
delta.ops.push(insert_a.clone());
|
||||||
|
delta.ops.push(insert_b.clone());
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(4, 8)),
|
||||||
|
vec![Builder::insert("56").build(), Builder::insert("78").build()]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(8, 9)),
|
||||||
|
vec![Builder::insert("9").build()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_get_ops_in_interval_6() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
let insert_a = Builder::insert("12345678").build();
|
||||||
|
delta.add(insert_a.clone());
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops_in_interval(Interval::new(4, 6)),
|
||||||
|
vec![Builder::insert("56").build()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lengths() {
|
fn lengths() {
|
||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
pub mod helper;
|
pub mod helper;
|
||||||
|
|
||||||
use crate::helper::{TestOp::*, *};
|
use crate::helper::{TestOp::*, *};
|
||||||
use flowy_ot::core::Interval;
|
use flowy_ot::{client::RECORD_THRESHOLD, core::Interval};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_insert() {
|
fn delta_undo_insert() {
|
||||||
@ -19,6 +19,7 @@ fn delta_undo_insert2() {
|
|||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
Insert(0, "\n", 0),
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
|
Wait(RECORD_THRESHOLD),
|
||||||
Insert(0, "456", 0),
|
Insert(0, "456", 0),
|
||||||
Undo(0),
|
Undo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
||||||
@ -43,11 +44,13 @@ fn delta_redo_insert() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_redo_insert2() {
|
fn delta_redo_insert_with_lagging() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
Insert(0, "\n", 0),
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
|
Wait(RECORD_THRESHOLD),
|
||||||
Insert(0, "456", 3),
|
Insert(0, "456", 3),
|
||||||
|
Wait(RECORD_THRESHOLD),
|
||||||
AssertStr(0, "123456\n"),
|
AssertStr(0, "123456\n"),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#),
|
||||||
Undo(0),
|
Undo(0),
|
||||||
@ -67,6 +70,19 @@ fn delta_undo_attributes() {
|
|||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
Undo(0),
|
Undo(0),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
|
];
|
||||||
|
OpTester::new().run_script(ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_undo_attributes_with_lagging() {
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, "\n", 0),
|
||||||
|
Insert(0, "123", 0),
|
||||||
|
Wait(RECORD_THRESHOLD),
|
||||||
|
Bold(0, Interval::new(0, 3), true),
|
||||||
|
Undo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
@ -79,7 +95,7 @@ fn delta_redo_attributes() {
|
|||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
Undo(0),
|
Undo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
Redo(0),
|
Redo(0),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
0,
|
0,
|
||||||
@ -90,25 +106,36 @@ fn delta_redo_attributes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_delete() {
|
fn delta_redo_attributes_with_lagging() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
Insert(0, "\n", 0),
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
|
Wait(RECORD_THRESHOLD),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
Delete(0, Interval::new(0, 3)),
|
|
||||||
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
|
||||||
Undo(0),
|
Undo(0),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
||||||
|
Redo(0),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
0,
|
0,
|
||||||
r#"[
|
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
|
||||||
{"insert":"123","attributes":{"bold":"true"}},
|
|
||||||
{"insert":"\n"}]
|
|
||||||
"#,
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_undo_delete() {
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, "123", 0),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"123"}]"#),
|
||||||
|
Delete(0, Interval::new(0, 3)),
|
||||||
|
AssertOpsJson(0, r#"[]"#),
|
||||||
|
Undo(0),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"123"}]"#),
|
||||||
|
];
|
||||||
|
OpTester::new().run_script(ops);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_delete2() {
|
fn delta_undo_delete2() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
@ -124,6 +151,28 @@ fn delta_undo_delete2() {
|
|||||||
"#,
|
"#,
|
||||||
),
|
),
|
||||||
Undo(0),
|
Undo(0),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
|
];
|
||||||
|
OpTester::new().run_script(ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_undo_delete2_with_lagging() {
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, "\n", 0),
|
||||||
|
Insert(0, "123", 0),
|
||||||
|
Wait(RECORD_THRESHOLD),
|
||||||
|
Bold(0, Interval::new(0, 3), true),
|
||||||
|
Wait(RECORD_THRESHOLD),
|
||||||
|
Delete(0, Interval::new(0, 1)),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[
|
||||||
|
{"insert":"23","attributes":{"bold":"true"}},
|
||||||
|
{"insert":"\n"}]
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
Undo(0),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
0,
|
0,
|
||||||
r#"[
|
r#"[
|
||||||
@ -148,3 +197,68 @@ fn delta_redo_delete() {
|
|||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_undo_replace() {
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, "\n", 0),
|
||||||
|
Insert(0, "123", 0),
|
||||||
|
Bold(0, Interval::new(0, 3), true),
|
||||||
|
Replace(0, Interval::new(0, 2), "ab"),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[
|
||||||
|
{"insert":"ab"},
|
||||||
|
{"insert":"3","attributes":{"bold":"true"}},{"insert":"\n"}]
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
Undo(0),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
|
];
|
||||||
|
OpTester::new().run_script(ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_undo_replace_with_lagging() {
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, "\n", 0),
|
||||||
|
Insert(0, "123", 0),
|
||||||
|
Wait(RECORD_THRESHOLD),
|
||||||
|
Bold(0, Interval::new(0, 3), true),
|
||||||
|
Wait(RECORD_THRESHOLD),
|
||||||
|
Replace(0, Interval::new(0, 2), "ab"),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[
|
||||||
|
{"insert":"ab"},
|
||||||
|
{"insert":"3","attributes":{"bold":"true"}},{"insert":"\n"}]
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
Undo(0),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
OpTester::new().run_script(ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_redo_replace() {
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, "\n", 0),
|
||||||
|
Insert(0, "123", 0),
|
||||||
|
Bold(0, Interval::new(0, 3), true),
|
||||||
|
Replace(0, Interval::new(0, 2), "ab"),
|
||||||
|
Undo(0),
|
||||||
|
Redo(0),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[
|
||||||
|
{"insert":"ab"},
|
||||||
|
{"insert":"3","attributes":{"bold":"true"}},{"insert":"\n"}]
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
OpTester::new().run_script(ops);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user