|
@@ -1,22 +1,16 @@
|
|
|
use crate::{
|
|
|
client::{History, RevId, UndoResult},
|
|
|
- core::{
|
|
|
- Attribute,
|
|
|
- Attributes,
|
|
|
- AttributesDataRule,
|
|
|
- AttrsBuilder,
|
|
|
- Builder,
|
|
|
- Delta,
|
|
|
- Interval,
|
|
|
- Operation,
|
|
|
- },
|
|
|
+ core::*,
|
|
|
errors::{ErrorBuilder, OTError, OTErrorCode::*},
|
|
|
};
|
|
|
|
|
|
+pub const RECORD_THRESHOLD: usize = 400; // in milliseconds
|
|
|
+
|
|
|
pub struct Document {
|
|
|
data: Delta,
|
|
|
history: History,
|
|
|
rev_id_counter: usize,
|
|
|
+ last_edit_time: usize,
|
|
|
}
|
|
|
|
|
|
impl Document {
|
|
@@ -26,6 +20,7 @@ impl Document {
|
|
|
data: delta,
|
|
|
history: History::new(),
|
|
|
rev_id_counter: 1,
|
|
|
+ last_edit_time: 0,
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -42,10 +37,12 @@ impl Document {
|
|
|
if attributes == Attributes::Empty {
|
|
|
attributes = Attributes::Follow;
|
|
|
}
|
|
|
+ let mut delta = Delta::new();
|
|
|
let insert = Builder::insert(text).attributes(attributes).build();
|
|
|
let interval = Interval::new(index, index);
|
|
|
+ delta.add(insert);
|
|
|
|
|
|
- self.update_with_op(insert, interval)
|
|
|
+ self.update_with_op(&delta, interval)
|
|
|
}
|
|
|
|
|
|
pub fn format(
|
|
@@ -68,15 +65,14 @@ impl Document {
|
|
|
|
|
|
pub fn undo(&mut self) -> Result<UndoResult, OTError> {
|
|
|
match self.history.undo() {
|
|
|
- None => Err(ErrorBuilder::new(UndoFail).build()),
|
|
|
+ None => Err(ErrorBuilder::new(UndoFail)
|
|
|
+ .msg("Undo stack is empty")
|
|
|
+ .build()),
|
|
|
Some(undo_delta) => {
|
|
|
- log::debug!("undo: {:?}", undo_delta);
|
|
|
- let composed_delta = self.data.compose(&undo_delta)?;
|
|
|
- let redo_delta = undo_delta.invert(&self.data);
|
|
|
- log::debug!("computed redo: {:?}", redo_delta);
|
|
|
- let result = UndoResult::success(composed_delta.target_len as usize);
|
|
|
- self.data = composed_delta;
|
|
|
- self.history.add_redo(redo_delta);
|
|
|
+ let (new_delta, inverted_delta) = self.invert_change(&undo_delta)?;
|
|
|
+ let result = UndoResult::success(new_delta.target_len as usize);
|
|
|
+ self.data = new_delta;
|
|
|
+ self.history.add_redo(inverted_delta);
|
|
|
|
|
|
Ok(result)
|
|
|
},
|
|
@@ -87,22 +83,29 @@ impl Document {
|
|
|
match self.history.redo() {
|
|
|
None => Err(ErrorBuilder::new(RedoFail).build()),
|
|
|
Some(redo_delta) => {
|
|
|
- log::debug!("redo: {:?}", redo_delta);
|
|
|
- let new_delta = self.data.compose(&redo_delta)?;
|
|
|
+ let (new_delta, inverted_delta) = self.invert_change(&redo_delta)?;
|
|
|
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.history.add_undo(undo_delta);
|
|
|
+ self.history.add_undo(inverted_delta);
|
|
|
Ok(result)
|
|
|
},
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub fn delete(&mut self, interval: Interval) -> Result<(), OTError> {
|
|
|
- let delete = Builder::delete(interval.size()).build();
|
|
|
- self.update_with_op(delete, interval)
|
|
|
+ pub fn replace(&mut self, interval: Interval, s: &str) -> Result<(), OTError> {
|
|
|
+ let mut delta = Delta::default();
|
|
|
+ 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() }
|
|
@@ -113,7 +116,7 @@ impl Document {
|
|
|
|
|
|
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 (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);
|
|
|
intervals.into_iter().for_each(|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);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- log::debug!("add new op: {:?}", op);
|
|
|
- new_delta.add(op);
|
|
|
+ delta.ops.iter().for_each(|op| {
|
|
|
+ new_delta.add(op.clone());
|
|
|
+ });
|
|
|
|
|
|
// suffix
|
|
|
if suffix.is_empty() == false {
|
|
|
let intervals = split_interval_with_delta(&self.data, &suffix);
|
|
|
intervals.into_iter().for_each(|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);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // c = a.compose(b)
|
|
|
- // 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;
|
|
|
+ self.data = self.record_change(&new_delta)?;
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
@@ -159,7 +155,7 @@ impl Document {
|
|
|
) -> Result<(), OTError> {
|
|
|
let old_attributes = self.data.get_attributes(interval);
|
|
|
log::debug!(
|
|
|
- "merge attributes: {:?}, with old: {:?}",
|
|
|
+ "combine attributes: {:?} : {:?}",
|
|
|
attributes,
|
|
|
old_attributes
|
|
|
);
|
|
@@ -172,21 +168,54 @@ impl Document {
|
|
|
Attributes::Empty => Attributes::Empty,
|
|
|
};
|
|
|
|
|
|
- log::debug!("new attributes: {:?}", new_attributes);
|
|
|
+ log::debug!("combined result: {:?}", new_attributes);
|
|
|
let retain = Builder::retain(interval.size())
|
|
|
.attributes(new_attributes)
|
|
|
.build();
|
|
|
|
|
|
- log::debug!(
|
|
|
- "Update delta with new attributes: {:?} at: {:?}",
|
|
|
- retain,
|
|
|
- interval
|
|
|
- );
|
|
|
+ let mut delta = Delta::new();
|
|
|
+ delta.add(retain);
|
|
|
|
|
|
- self.update_with_op(retain, interval)
|
|
|
+ self.update_with_op(&delta, interval)
|
|
|
}
|
|
|
|
|
|
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) {
|
|
@@ -216,3 +245,17 @@ fn split_interval_with_delta(delta: &Delta, interval: &Interval) -> Vec<Interval
|
|
|
});
|
|
|
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();
|
|
|
+ }
|
|
|
+}
|