mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add flowy-ot document
This commit is contained in:
parent
97bba6e286
commit
e2b8738b65
177
rust-lib/flowy-ot/src/client/document.rs
Normal file
177
rust-lib/flowy-ot/src/client/document.rs
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
use crate::{
|
||||||
|
client::{History, UndoResult},
|
||||||
|
core::{
|
||||||
|
Attribute,
|
||||||
|
Attributes,
|
||||||
|
AttributesDataRule,
|
||||||
|
AttrsBuilder,
|
||||||
|
Delta,
|
||||||
|
Interval,
|
||||||
|
OpBuilder,
|
||||||
|
Operation,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Document {
|
||||||
|
data: Delta,
|
||||||
|
history: History,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Document {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Document {
|
||||||
|
data: Delta::new(),
|
||||||
|
history: History::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn edit(&mut self, index: usize, text: &str) {
|
||||||
|
if self.data.target_len < index {
|
||||||
|
log::error!(
|
||||||
|
"{} out of bounds. should 0..{}",
|
||||||
|
index,
|
||||||
|
self.data.target_len
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let probe = Interval::new(index, index + 1);
|
||||||
|
let mut attributes = self.data.get_attributes(probe);
|
||||||
|
if attributes == Attributes::Empty {
|
||||||
|
attributes = Attributes::Follow;
|
||||||
|
}
|
||||||
|
let insert = OpBuilder::insert(text).attributes(attributes).build();
|
||||||
|
let interval = Interval::new(index, index);
|
||||||
|
|
||||||
|
self.update_with_op(insert, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(&mut self, interval: Interval, attribute: Attribute, enable: bool) {
|
||||||
|
let attributes = match enable {
|
||||||
|
true => AttrsBuilder::new().add_attribute(attribute).build(),
|
||||||
|
false => AttrsBuilder::new().remove_attribute(attribute).build(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.update_with_attribute(attributes, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn undo(&mut self) -> UndoResult { unimplemented!() }
|
||||||
|
|
||||||
|
pub fn redo(&mut self) -> UndoResult { unimplemented!() }
|
||||||
|
|
||||||
|
pub fn delete(&mut self, interval: Interval) {
|
||||||
|
let delete = OpBuilder::delete(interval.size() as u64).build();
|
||||||
|
self.update_with_op(delete, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_json(&self) -> String { self.data.to_json() }
|
||||||
|
|
||||||
|
pub fn data(&self) -> &Delta { &self.data }
|
||||||
|
|
||||||
|
pub fn set_data(&mut self, data: Delta) { self.data = data; }
|
||||||
|
|
||||||
|
fn update_with_op(&mut self, op: Operation, interval: Interval) {
|
||||||
|
let mut new_delta = Delta::default();
|
||||||
|
let (prefix, interval, suffix) = split_length_with_interval(self.data.target_len, interval);
|
||||||
|
|
||||||
|
// prefix
|
||||||
|
if prefix.is_empty() == false && prefix != interval {
|
||||||
|
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);
|
||||||
|
new_delta.retain(i.size() as u64, attributes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!("add new op: {:?}", op);
|
||||||
|
new_delta.add(op);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
new_delta.retain(i.size() as u64, attributes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_data = self.data.compose(&new_delta).unwrap();
|
||||||
|
self.data = new_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_with_attribute(&mut self, mut attributes: Attributes, interval: Interval) {
|
||||||
|
let old_attributes = self.data.get_attributes(interval);
|
||||||
|
log::debug!(
|
||||||
|
"merge attributes: {:?}, with old: {:?}",
|
||||||
|
attributes,
|
||||||
|
old_attributes
|
||||||
|
);
|
||||||
|
let new_attributes = match &mut attributes {
|
||||||
|
Attributes::Follow => old_attributes,
|
||||||
|
Attributes::Custom(attr_data) => {
|
||||||
|
attr_data.merge(old_attributes.data());
|
||||||
|
attr_data.clone().into_attributes()
|
||||||
|
},
|
||||||
|
Attributes::Empty => Attributes::Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!("new attributes: {:?}", new_attributes);
|
||||||
|
let retain = OpBuilder::retain(interval.size() as u64)
|
||||||
|
.attributes(new_attributes)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"Update delta with new attributes: {:?} at: {:?}",
|
||||||
|
retain,
|
||||||
|
interval
|
||||||
|
);
|
||||||
|
|
||||||
|
self.update_with_op(retain, interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform(left: &Document, right: &Document) -> (Document, Document) {
|
||||||
|
let (a_prime, b_prime) = left.data.transform(&right.data).unwrap();
|
||||||
|
log::trace!("a:{:?},b:{:?}", a_prime, b_prime);
|
||||||
|
|
||||||
|
let data_left = left.data.compose(&b_prime).unwrap();
|
||||||
|
let data_right = right.data.compose(&a_prime).unwrap();
|
||||||
|
(
|
||||||
|
Document {
|
||||||
|
data: data_left,
|
||||||
|
history: left.history.clone(),
|
||||||
|
},
|
||||||
|
Document {
|
||||||
|
data: data_right,
|
||||||
|
history: right.history.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) {
|
||||||
|
let original_interval = Interval::new(0, length);
|
||||||
|
let prefix = original_interval.prefix(interval);
|
||||||
|
let suffix = original_interval.suffix(interval);
|
||||||
|
(prefix, interval, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_interval_with_delta(delta: &Delta, interval: &Interval) -> Vec<Interval> {
|
||||||
|
let mut start = 0;
|
||||||
|
let mut new_intervals = vec![];
|
||||||
|
delta.ops.iter().for_each(|op| match op {
|
||||||
|
Operation::Delete(_) => {},
|
||||||
|
Operation::Retain(_) => {},
|
||||||
|
Operation::Insert(insert) => {
|
||||||
|
let len = insert.num_chars() as usize;
|
||||||
|
let end = start + len;
|
||||||
|
let insert_interval = Interval::new(start, end);
|
||||||
|
let new_interval = interval.intersect(insert_interval);
|
||||||
|
|
||||||
|
if !new_interval.is_empty() {
|
||||||
|
new_intervals.push(new_interval)
|
||||||
|
}
|
||||||
|
start += len;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
new_intervals
|
||||||
|
}
|
50
rust-lib/flowy-ot/src/client/history.rs
Normal file
50
rust-lib/flowy-ot/src/client/history.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
use crate::core::{Delta, Interval, OpBuilder, Operation};
|
||||||
|
|
||||||
|
const MAX_UNDOS: usize = 20;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Revision {
|
||||||
|
rev_id: u64,
|
||||||
|
delta: Delta,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct UndoResult {
|
||||||
|
success: bool,
|
||||||
|
len: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct History {
|
||||||
|
cur_undo: usize,
|
||||||
|
undos: Vec<Revision>,
|
||||||
|
redos: Vec<Revision>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl History {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
History {
|
||||||
|
cur_undo: 1,
|
||||||
|
undos: Vec::new(),
|
||||||
|
redos: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_undo(&self) -> bool { !self.undos.is_empty() }
|
||||||
|
|
||||||
|
pub fn can_redo(&self) -> bool { !self.redos.is_empty() }
|
||||||
|
|
||||||
|
pub fn record(&mut self, _change: Delta) {}
|
||||||
|
|
||||||
|
pub fn undo(&mut self) -> Option<Revision> {
|
||||||
|
if !self.can_undo() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let revision = self.undos.pop().unwrap();
|
||||||
|
|
||||||
|
Some(revision)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redo(&mut self) -> Option<Revision> { None }
|
||||||
|
}
|
254
rust-lib/flowy-ot/src/core/attributes/attributes.rs
Normal file
254
rust-lib/flowy-ot/src/core/attributes/attributes.rs
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
use crate::core::{should_remove, Operation};
|
||||||
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Attributes {
|
||||||
|
#[serde(skip)]
|
||||||
|
Follow,
|
||||||
|
Custom(AttributesData),
|
||||||
|
#[serde(skip)]
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Attributes {
|
||||||
|
pub fn data(&self) -> Option<AttributesData> {
|
||||||
|
match self {
|
||||||
|
Attributes::Follow => None,
|
||||||
|
Attributes::Custom(data) => Some(data.clone()),
|
||||||
|
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 {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct AttributesData {
|
||||||
|
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
#[serde(flatten)]
|
||||||
|
inner: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttributesData {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
AttributesData {
|
||||||
|
inner: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.inner.values().filter(|v| !should_remove(v)).count() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_empty(&mut self) { self.inner.retain(|_, v| !should_remove(v)); }
|
||||||
|
|
||||||
|
pub fn extend(&mut self, other: AttributesData) { self.inner.extend(other.inner); }
|
||||||
|
|
||||||
|
pub fn merge(&mut self, other: Option<AttributesData>) {
|
||||||
|
if other.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_attributes = other.unwrap().inner;
|
||||||
|
self.inner.iter().for_each(|(k, v)| {
|
||||||
|
if should_remove(v) {
|
||||||
|
new_attributes.remove(k);
|
||||||
|
} else {
|
||||||
|
new_attributes.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.inner = new_attributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AttributesDataRule {
|
||||||
|
fn apply_rule(&mut self);
|
||||||
|
|
||||||
|
fn into_attributes(self) -> Attributes;
|
||||||
|
}
|
||||||
|
impl AttributesDataRule for AttributesData {
|
||||||
|
fn apply_rule(&mut self) { self.remove_empty(); }
|
||||||
|
|
||||||
|
fn into_attributes(mut self) -> Attributes {
|
||||||
|
self.apply_rule();
|
||||||
|
|
||||||
|
if self.is_empty() {
|
||||||
|
Attributes::Empty
|
||||||
|
} else {
|
||||||
|
Attributes::Custom(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AttributesRule {
|
||||||
|
fn apply_rule(self) -> Attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttributesRule for Attributes {
|
||||||
|
fn apply_rule(self) -> Attributes {
|
||||||
|
match self {
|
||||||
|
Attributes::Follow => self,
|
||||||
|
Attributes::Custom(data) => data.into_attributes(),
|
||||||
|
Attributes::Empty => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<HashMap<String, String>> for AttributesData {
|
||||||
|
fn from(attributes: HashMap<String, String>) -> Self { AttributesData { inner: attributes } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for AttributesData {
|
||||||
|
type Target = HashMap<String, String>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for AttributesData {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
|
||||||
|
match operation {
|
||||||
|
None => None,
|
||||||
|
Some(operation) => Some(operation.get_attributes()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compose_operation(left: &Option<Operation>, right: &Option<Operation>) -> Attributes {
|
||||||
|
if left.is_none() && right.is_none() {
|
||||||
|
return Attributes::Empty;
|
||||||
|
}
|
||||||
|
let attr_l = attributes_from(left);
|
||||||
|
let attr_r = attributes_from(right);
|
||||||
|
|
||||||
|
if attr_l.is_none() {
|
||||||
|
return attr_r.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr_r.is_none() {
|
||||||
|
return attr_l.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
compose_attributes(attr_l.unwrap(), attr_r.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_operation(left: &Option<Operation>, right: &Option<Operation>) -> Attributes {
|
||||||
|
let attr_l = attributes_from(left);
|
||||||
|
let attr_r = attributes_from(right);
|
||||||
|
|
||||||
|
if attr_l.is_none() {
|
||||||
|
if attr_r.is_none() {
|
||||||
|
return Attributes::Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match attr_r.as_ref().unwrap() {
|
||||||
|
Attributes::Follow => Attributes::Follow,
|
||||||
|
Attributes::Custom(_) => attr_r.unwrap(),
|
||||||
|
Attributes::Empty => Attributes::Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
transform_attributes(attr_l.unwrap(), attr_r.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes {
|
||||||
|
let attr = attr.data();
|
||||||
|
let base = base.data();
|
||||||
|
|
||||||
|
if attr.is_none() && base.is_none() {
|
||||||
|
return Attributes::Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
let attr = attr.unwrap_or(AttributesData::new());
|
||||||
|
let base = base.unwrap_or(AttributesData::new());
|
||||||
|
|
||||||
|
let base_inverted = base
|
||||||
|
.iter()
|
||||||
|
.fold(AttributesData::new(), |mut attributes, (k, v)| {
|
||||||
|
if base.get(k) != attr.get(k) && attr.contains_key(k) {
|
||||||
|
attributes.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
attributes
|
||||||
|
});
|
||||||
|
|
||||||
|
let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, _)| {
|
||||||
|
if base.get(k) != attr.get(k) && !base.contains_key(k) {
|
||||||
|
attributes.remove(k);
|
||||||
|
}
|
||||||
|
attributes
|
||||||
|
});
|
||||||
|
|
||||||
|
return Attributes::Custom(inverted);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge_attributes(attributes: Attributes, other: Attributes) -> Attributes {
|
||||||
|
match (&attributes, &other) {
|
||||||
|
(Attributes::Custom(data), Attributes::Custom(o_data)) => {
|
||||||
|
let mut data = data.clone();
|
||||||
|
data.extend(o_data.clone());
|
||||||
|
Attributes::Custom(data)
|
||||||
|
},
|
||||||
|
(Attributes::Custom(data), _) => Attributes::Custom(data.clone()),
|
||||||
|
_ => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compose_attributes(left: Attributes, right: Attributes) -> Attributes {
|
||||||
|
log::trace!("compose_attributes: a: {:?}, b: {:?}", left, right);
|
||||||
|
|
||||||
|
let attr = match (&left, &right) {
|
||||||
|
(_, Attributes::Empty) => Attributes::Empty,
|
||||||
|
(_, Attributes::Custom(_)) => merge_attributes(left, right),
|
||||||
|
(Attributes::Custom(_), _) => merge_attributes(left, right),
|
||||||
|
_ => Attributes::Follow,
|
||||||
|
};
|
||||||
|
|
||||||
|
log::trace!("composed_attributes: a: {:?}", attr);
|
||||||
|
attr.apply_rule()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_attributes(left: Attributes, right: Attributes) -> Attributes {
|
||||||
|
match (left, right) {
|
||||||
|
(Attributes::Custom(data_l), Attributes::Custom(data_r)) => {
|
||||||
|
let result = data_r
|
||||||
|
.iter()
|
||||||
|
.fold(AttributesData::new(), |mut new_attr_data, (k, v)| {
|
||||||
|
if !data_l.contains_key(k) {
|
||||||
|
new_attr_data.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
new_attr_data
|
||||||
|
});
|
||||||
|
|
||||||
|
Attributes::Custom(result)
|
||||||
|
},
|
||||||
|
_ => Attributes::Empty,
|
||||||
|
}
|
||||||
|
}
|
52
rust-lib/flowy-ot/src/core/attributes/builder.rs
Normal file
52
rust-lib/flowy-ot/src/core/attributes/builder.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use crate::core::{Attributes, AttributesData};
|
||||||
|
use derive_more::Display;
|
||||||
|
const REMOVE_FLAG: &'static str = "";
|
||||||
|
pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG }
|
||||||
|
|
||||||
|
#[derive(Clone, Display)]
|
||||||
|
pub enum Attribute {
|
||||||
|
#[display(fmt = "bold")]
|
||||||
|
Bold,
|
||||||
|
#[display(fmt = "italic")]
|
||||||
|
Italic,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AttrsBuilder {
|
||||||
|
inner: AttributesData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttrsBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: AttributesData::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_attribute(mut self, attribute: Attribute) -> Self {
|
||||||
|
self.inner
|
||||||
|
.insert(format!("{}", attribute), "true".to_owned());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_attribute(mut self, attribute: Attribute) -> Self {
|
||||||
|
self.inner
|
||||||
|
.insert(format!("{}", attribute), REMOVE_FLAG.to_owned());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bold(self, bold: bool) -> Self {
|
||||||
|
match bold {
|
||||||
|
true => self.add_attribute(Attribute::Bold),
|
||||||
|
false => self.remove_attribute(Attribute::Bold),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn italic(self, italic: bool) -> Self {
|
||||||
|
match italic {
|
||||||
|
true => self.add_attribute(Attribute::Italic),
|
||||||
|
false => self.remove_attribute(Attribute::Italic),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Attributes { Attributes::Custom(self.inner) }
|
||||||
|
}
|
5
rust-lib/flowy-ot/src/core/attributes/mod.rs
Normal file
5
rust-lib/flowy-ot/src/core/attributes/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
mod attributes;
|
||||||
|
mod builder;
|
||||||
|
|
||||||
|
pub use attributes::*;
|
||||||
|
pub use builder::*;
|
36
rust-lib/flowy-ot/src/core/operation/builder.rs
Normal file
36
rust-lib/flowy-ot/src/core/operation/builder.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use crate::core::{Attributes, Operation};
|
||||||
|
|
||||||
|
pub struct OpBuilder {
|
||||||
|
ty: Operation,
|
||||||
|
attrs: Attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpBuilder {
|
||||||
|
pub fn new(ty: Operation) -> OpBuilder {
|
||||||
|
OpBuilder {
|
||||||
|
ty,
|
||||||
|
attrs: Attributes::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retain(n: u64) -> OpBuilder { OpBuilder::new(Operation::Retain(n.into())) }
|
||||||
|
|
||||||
|
pub fn delete(n: u64) -> OpBuilder { OpBuilder::new(Operation::Delete(n)) }
|
||||||
|
|
||||||
|
pub fn insert(s: &str) -> OpBuilder { OpBuilder::new(Operation::Insert(s.into())) }
|
||||||
|
|
||||||
|
pub fn attributes(mut self, attrs: Attributes) -> OpBuilder {
|
||||||
|
self.attrs = attrs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Operation {
|
||||||
|
let mut operation = self.ty;
|
||||||
|
match &mut operation {
|
||||||
|
Operation::Delete(_) => {},
|
||||||
|
Operation::Retain(retain) => retain.attributes = self.attrs,
|
||||||
|
Operation::Insert(insert) => insert.attributes = self.attrs,
|
||||||
|
}
|
||||||
|
operation
|
||||||
|
}
|
||||||
|
}
|
7
rust-lib/flowy-ot/src/core/operation/mod.rs
Normal file
7
rust-lib/flowy-ot/src/core/operation/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
mod builder;
|
||||||
|
mod operation;
|
||||||
|
mod operation_serde;
|
||||||
|
|
||||||
|
pub use builder::*;
|
||||||
|
pub use operation::*;
|
||||||
|
pub use operation_serde::*;
|
197
rust-lib/flowy-ot/src/core/operation/operation.rs
Normal file
197
rust-lib/flowy-ot/src/core/operation/operation.rs
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
use crate::core::{Attributes, OpBuilder};
|
||||||
|
use bytecount::num_chars;
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
str::Chars,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Operation {
|
||||||
|
Delete(u64),
|
||||||
|
Retain(Retain),
|
||||||
|
Insert(Insert),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operation {
|
||||||
|
pub fn is_delete(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Operation::Delete(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_noop(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Operation::Retain(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_attributes(&self) -> Attributes {
|
||||||
|
match self {
|
||||||
|
Operation::Delete(_) => Attributes::Empty,
|
||||||
|
Operation::Retain(retain) => retain.attributes.clone(),
|
||||||
|
Operation::Insert(insert) => insert.attributes.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attributes(&mut self, attributes: Attributes) {
|
||||||
|
match self {
|
||||||
|
Operation::Delete(_) => {
|
||||||
|
log::error!("Delete should not contains attributes");
|
||||||
|
},
|
||||||
|
Operation::Retain(retain) => {
|
||||||
|
retain.attributes = attributes;
|
||||||
|
},
|
||||||
|
Operation::Insert(insert) => {
|
||||||
|
insert.attributes = attributes;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_attribute(&self) -> bool {
|
||||||
|
match self.get_attributes() {
|
||||||
|
Attributes::Follow => true,
|
||||||
|
Attributes::Custom(_) => false,
|
||||||
|
Attributes::Empty => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Operation::Delete(n) => *n,
|
||||||
|
Operation::Retain(r) => r.n,
|
||||||
|
Operation::Insert(i) => i.num_chars(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool { self.length() == 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Operation {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Operation::Delete(n) => {
|
||||||
|
f.write_fmt(format_args!("delete: {}", n))?;
|
||||||
|
},
|
||||||
|
Operation::Retain(r) => {
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"retain: {}, attributes: {}",
|
||||||
|
r.n, r.attributes
|
||||||
|
))?;
|
||||||
|
},
|
||||||
|
Operation::Insert(i) => {
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"insert: {}, attributes: {}",
|
||||||
|
i.s, i.attributes
|
||||||
|
))?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Retain {
|
||||||
|
#[serde(rename(serialize = "retain", deserialize = "retain"))]
|
||||||
|
pub n: u64,
|
||||||
|
#[serde(skip_serializing_if = "is_empty")]
|
||||||
|
pub attributes: Attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Retain {
|
||||||
|
pub fn merge_or_new_op(&mut self, n: u64, attributes: Attributes) -> Option<Operation> {
|
||||||
|
log::debug!(
|
||||||
|
"merge_retain_or_new_op: {:?}, {:?}",
|
||||||
|
self.attributes,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
|
||||||
|
match &attributes {
|
||||||
|
Attributes::Follow => {
|
||||||
|
log::debug!("Follow attribute: {:?}", self.attributes);
|
||||||
|
self.n += n;
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Attributes::Custom(_) | Attributes::Empty => {
|
||||||
|
if self.attributes == attributes {
|
||||||
|
log::debug!("Attribute equal");
|
||||||
|
self.n += n;
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
log::debug!("New retain op");
|
||||||
|
Some(OpBuilder::retain(n).attributes(attributes).build())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<u64> for Retain {
|
||||||
|
fn from(n: u64) -> Self {
|
||||||
|
Retain {
|
||||||
|
n,
|
||||||
|
attributes: Attributes::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Retain {
|
||||||
|
type Target = u64;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.n }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Retain {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.n }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Insert {
|
||||||
|
#[serde(rename(serialize = "insert", deserialize = "insert"))]
|
||||||
|
pub s: String,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "is_empty")]
|
||||||
|
pub attributes: Attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Insert {
|
||||||
|
pub fn as_bytes(&self) -> &[u8] { self.s.as_bytes() }
|
||||||
|
|
||||||
|
pub fn chars(&self) -> Chars<'_> { self.s.chars() }
|
||||||
|
|
||||||
|
pub fn num_chars(&self) -> u64 { num_chars(self.s.as_bytes()) as _ }
|
||||||
|
|
||||||
|
pub fn merge_or_new_op(&mut self, s: &str, attributes: Attributes) -> Option<Operation> {
|
||||||
|
match &attributes {
|
||||||
|
Attributes::Follow => {
|
||||||
|
self.s += s;
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
Attributes::Custom(_) | Attributes::Empty => {
|
||||||
|
if self.attributes == attributes {
|
||||||
|
self.s += s;
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(OpBuilder::insert(s).attributes(attributes).build())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<String> for Insert {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Insert {
|
||||||
|
s,
|
||||||
|
attributes: Attributes::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<&str> for Insert {
|
||||||
|
fn from(s: &str) -> Self { Insert::from(s.to_owned()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(attributes: &Attributes) -> bool { attributes.is_empty() }
|
137
rust-lib/flowy-ot/src/core/operation/operation_serde.rs
Normal file
137
rust-lib/flowy-ot/src/core/operation/operation_serde.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
use crate::core::{Attributes, Delta, Operation};
|
||||||
|
use serde::{
|
||||||
|
de,
|
||||||
|
de::{MapAccess, SeqAccess, Visitor},
|
||||||
|
ser::{SerializeMap, SerializeSeq},
|
||||||
|
Deserialize,
|
||||||
|
Deserializer,
|
||||||
|
Serialize,
|
||||||
|
Serializer,
|
||||||
|
};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
impl Serialize for Operation {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Operation::Retain(retain) => retain.serialize(serializer),
|
||||||
|
Operation::Delete(i) => {
|
||||||
|
let mut map = serializer.serialize_map(Some(1))?;
|
||||||
|
map.serialize_entry("delete", i)?;
|
||||||
|
map.end()
|
||||||
|
},
|
||||||
|
Operation::Insert(insert) => insert.serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Operation {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Operation, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct OperationVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for OperationVisitor {
|
||||||
|
type Value = Operation;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("an integer between -2^64 and 2^63 or a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
|
||||||
|
where
|
||||||
|
V: MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut operation = None;
|
||||||
|
let mut attributes = None;
|
||||||
|
while let Some(key) = map.next_key()? {
|
||||||
|
match key {
|
||||||
|
"delete" => {
|
||||||
|
if operation.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("operation"));
|
||||||
|
}
|
||||||
|
operation = Some(Operation::Delete(map.next_value()?));
|
||||||
|
},
|
||||||
|
"retain" => {
|
||||||
|
if operation.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("operation"));
|
||||||
|
}
|
||||||
|
let i: u64 = map.next_value()?;
|
||||||
|
operation = Some(Operation::Retain(i.into()));
|
||||||
|
},
|
||||||
|
"insert" => {
|
||||||
|
if operation.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("operation"));
|
||||||
|
}
|
||||||
|
let i: String = map.next_value()?;
|
||||||
|
operation = Some(Operation::Insert(i.into()));
|
||||||
|
},
|
||||||
|
"attributes" => {
|
||||||
|
if attributes.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("attributes"));
|
||||||
|
}
|
||||||
|
let map: Attributes = map.next_value()?;
|
||||||
|
attributes = Some(map);
|
||||||
|
},
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match operation {
|
||||||
|
None => Err(de::Error::missing_field("operation")),
|
||||||
|
Some(mut operation) => {
|
||||||
|
operation.set_attributes(attributes.unwrap_or(Attributes::Empty));
|
||||||
|
Ok(operation)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(OperationVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Delta {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut seq = serializer.serialize_seq(Some(self.ops.len()))?;
|
||||||
|
for op in self.ops.iter() {
|
||||||
|
seq.serialize_element(op)?;
|
||||||
|
}
|
||||||
|
seq.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Delta {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Delta, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct OperationSeqVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for OperationSeqVisitor {
|
||||||
|
type Value = Delta;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a sequence")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut o = Delta::default();
|
||||||
|
while let Some(op) = seq.next_element()? {
|
||||||
|
o.add(op);
|
||||||
|
}
|
||||||
|
Ok(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_seq(OperationSeqVisitor)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use flowy_ot::core::*;
|
use flowy_ot::{
|
||||||
|
client::{transform, Document},
|
||||||
|
core::*,
|
||||||
|
};
|
||||||
use rand::{prelude::*, Rng as WrappedRng};
|
use rand::{prelude::*, Rng as WrappedRng};
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
|
||||||
@ -29,15 +32,12 @@ pub enum TestOp {
|
|||||||
#[display(fmt = "Undo")]
|
#[display(fmt = "Undo")]
|
||||||
Undo(usize, usize),
|
Undo(usize, usize),
|
||||||
|
|
||||||
#[display(fmt = "AssertStr")]
|
|
||||||
AssertStr(usize, &'static str),
|
|
||||||
|
|
||||||
#[display(fmt = "AssertOpsJson")]
|
#[display(fmt = "AssertOpsJson")]
|
||||||
AssertOpsJson(usize, &'static str),
|
AssertOpsJson(usize, &'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OpTester {
|
pub struct OpTester {
|
||||||
deltas: Vec<Delta>,
|
documents: Vec<Document>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpTester {
|
impl OpTester {
|
||||||
@ -48,52 +48,47 @@ impl OpTester {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut deltas = Vec::with_capacity(2);
|
let mut documents = Vec::with_capacity(2);
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
let delta = Delta::default();
|
documents.push(Document::new());
|
||||||
deltas.push(delta);
|
|
||||||
}
|
}
|
||||||
Self { deltas }
|
Self { documents }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_op(&mut self, op: &TestOp) {
|
pub fn run_op(&mut self, op: &TestOp) {
|
||||||
log::debug!("***************** 😈{} *******************", &op);
|
log::debug!("***************** 😈{} *******************", &op);
|
||||||
match op {
|
match op {
|
||||||
TestOp::Insert(delta_i, s, index) => {
|
TestOp::Insert(delta_i, s, index) => {
|
||||||
self.update_delta_with_insert(*delta_i, s, *index);
|
let document = &mut self.documents[*delta_i];
|
||||||
|
document.edit(*index, s);
|
||||||
},
|
},
|
||||||
TestOp::Delete(delta_i, interval) => {
|
TestOp::Delete(delta_i, interval) => {
|
||||||
//
|
let document = &mut self.documents[*delta_i];
|
||||||
self.update_delta_with_delete(*delta_i, interval);
|
document.delete(*interval);
|
||||||
},
|
},
|
||||||
TestOp::InsertBold(delta_i, s, _interval) => {
|
TestOp::InsertBold(delta_i, s, interval) => {
|
||||||
let attrs = AttrsBuilder::new().bold(true).build();
|
let document = &mut self.documents[*delta_i];
|
||||||
let delta = &mut self.deltas[*delta_i];
|
document.edit(interval.start, s);
|
||||||
delta.insert(s, attrs);
|
document.format(*interval, Attribute::Bold, true);
|
||||||
},
|
},
|
||||||
TestOp::Bold(delta_i, interval, enable) => {
|
TestOp::Bold(delta_i, interval, enable) => {
|
||||||
let attrs = AttrsBuilder::new().bold(*enable).build();
|
let document = &mut self.documents[*delta_i];
|
||||||
self.update_delta_with_attribute(*delta_i, attrs, interval);
|
document.format(*interval, Attribute::Bold, *enable);
|
||||||
},
|
},
|
||||||
TestOp::Italic(delta_i, interval, enable) => {
|
TestOp::Italic(delta_i, interval, enable) => {
|
||||||
let attrs = AttrsBuilder::new().italic(*enable).build();
|
let document = &mut self.documents[*delta_i];
|
||||||
self.update_delta_with_attribute(*delta_i, attrs, interval);
|
document.format(*interval, Attribute::Italic, *enable);
|
||||||
},
|
},
|
||||||
TestOp::Transform(delta_a_i, delta_b_i) => {
|
TestOp::Transform(delta_a_i, delta_b_i) => {
|
||||||
let delta_a = &self.deltas[*delta_a_i];
|
let (document_a, document_b) =
|
||||||
let delta_b = &self.deltas[*delta_b_i];
|
transform(&self.documents[*delta_a_i], &self.documents[*delta_b_i]);
|
||||||
|
|
||||||
let (a_prime, b_prime) = delta_a.transform(delta_b).unwrap();
|
self.documents[*delta_a_i] = document_a;
|
||||||
log::trace!("a:{:?},b:{:?}", a_prime, b_prime);
|
self.documents[*delta_b_i] = document_b;
|
||||||
let new_delta_a = delta_a.compose(&b_prime).unwrap();
|
|
||||||
let new_delta_b = delta_b.compose(&a_prime).unwrap();
|
|
||||||
|
|
||||||
self.deltas[*delta_a_i] = new_delta_a;
|
|
||||||
self.deltas[*delta_b_i] = new_delta_b;
|
|
||||||
},
|
},
|
||||||
TestOp::Undo(delta_a_i, delta_b_i) => {
|
TestOp::Undo(delta_a_i, delta_b_i) => {
|
||||||
let delta_a = &self.deltas[*delta_a_i];
|
let delta_a = &self.documents[*delta_a_i].data();
|
||||||
let delta_b = &self.deltas[*delta_b_i];
|
let delta_b = &self.documents[*delta_b_i].data();
|
||||||
log::debug!("Invert: ");
|
log::debug!("Invert: ");
|
||||||
log::debug!("a: {}", delta_a.to_json());
|
log::debug!("a: {}", delta_a.to_json());
|
||||||
log::debug!("b: {}", delta_b.to_json());
|
log::debug!("b: {}", delta_b.to_json());
|
||||||
@ -109,18 +104,13 @@ impl OpTester {
|
|||||||
|
|
||||||
log::debug!("inverted delta a: {}", new_delta_after_undo.to_string());
|
log::debug!("inverted delta a: {}", new_delta_after_undo.to_string());
|
||||||
|
|
||||||
assert_eq!(delta_a, &new_delta_after_undo);
|
assert_eq!(delta_a, &&new_delta_after_undo);
|
||||||
|
|
||||||
self.deltas[*delta_a_i] = new_delta_after_undo;
|
self.documents[*delta_a_i].set_data(new_delta_after_undo);
|
||||||
},
|
|
||||||
TestOp::AssertStr(delta_i, expected) => {
|
|
||||||
let s = self.deltas[*delta_i].apply("").unwrap();
|
|
||||||
assert_eq!(&s, expected);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
TestOp::AssertOpsJson(delta_i, expected) => {
|
TestOp::AssertOpsJson(delta_i, expected) => {
|
||||||
log::debug!("AssertOpsJson: {:?}", self.deltas[*delta_i]);
|
let delta_i_json = self.documents[*delta_i].to_json();
|
||||||
let delta_i_json = serde_json::to_string(&self.deltas[*delta_i]).unwrap();
|
|
||||||
|
|
||||||
let expected_delta: Delta = serde_json::from_str(expected).unwrap();
|
let expected_delta: Delta = serde_json::from_str(expected).unwrap();
|
||||||
let target_delta: Delta = serde_json::from_str(&delta_i_json).unwrap();
|
let target_delta: Delta = serde_json::from_str(&delta_i_json).unwrap();
|
||||||
@ -139,116 +129,6 @@ impl OpTester {
|
|||||||
self.run_op(op);
|
self.run_op(op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_delta(&mut self, index: usize) -> &mut Delta { &mut self.deltas[index] }
|
|
||||||
|
|
||||||
pub fn update_delta_with_insert(&mut self, delta_index: usize, s: &str, index: usize) {
|
|
||||||
let old_delta = &mut self.deltas[delta_index];
|
|
||||||
let target_interval = Interval::new(0, old_delta.target_len);
|
|
||||||
if old_delta.target_len < index {
|
|
||||||
log::error!("{} out of bounds {}", index, target_interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut attributes = old_delta.get_attributes(Interval::new(index, index + 1));
|
|
||||||
if attributes == Attributes::Empty {
|
|
||||||
attributes = Attributes::Follow;
|
|
||||||
}
|
|
||||||
let insert = OpBuilder::insert(s).attributes(attributes).build();
|
|
||||||
let new_delta = new_delta_with_op(old_delta, insert, Interval::new(index, index));
|
|
||||||
self.deltas[delta_index] = new_delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_delta_with_attribute(
|
|
||||||
&mut self,
|
|
||||||
delta_index: usize,
|
|
||||||
mut attributes: Attributes,
|
|
||||||
interval: &Interval,
|
|
||||||
) {
|
|
||||||
let old_delta = &self.deltas[delta_index];
|
|
||||||
let old_attributes = old_delta.get_attributes(*interval);
|
|
||||||
log::debug!(
|
|
||||||
"merge attributes: {:?}, with old: {:?}",
|
|
||||||
attributes,
|
|
||||||
old_attributes
|
|
||||||
);
|
|
||||||
let new_attributes = match &mut attributes {
|
|
||||||
Attributes::Follow => old_attributes,
|
|
||||||
Attributes::Custom(attr_data) => {
|
|
||||||
attr_data.merge(old_attributes.data());
|
|
||||||
attr_data.clone().into_attributes()
|
|
||||||
},
|
|
||||||
Attributes::Empty => Attributes::Empty,
|
|
||||||
};
|
|
||||||
|
|
||||||
log::debug!("new attributes: {:?}", new_attributes);
|
|
||||||
let retain = OpBuilder::retain(interval.size() as u64)
|
|
||||||
.attributes(new_attributes)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
log::debug!(
|
|
||||||
"Update delta with new attributes: {:?} at: {:?}",
|
|
||||||
retain,
|
|
||||||
interval
|
|
||||||
);
|
|
||||||
|
|
||||||
let new_delta = new_delta_with_op(old_delta, retain, *interval);
|
|
||||||
self.deltas[delta_index] = new_delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_delta_with_delete(&mut self, delta_index: usize, interval: &Interval) {
|
|
||||||
let old_delta = &self.deltas[delta_index];
|
|
||||||
let delete = OpBuilder::delete(interval.size() as u64).build();
|
|
||||||
let new_delta = new_delta_with_op(old_delta, delete, *interval);
|
|
||||||
self.deltas[delta_index] = new_delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_delta_with_op(delta: &Delta, op: Operation, interval: Interval) -> Delta {
|
|
||||||
let mut new_delta = Delta::default();
|
|
||||||
let (prefix, interval, suffix) = target_length_split_with_interval(delta.target_len, interval);
|
|
||||||
|
|
||||||
// prefix
|
|
||||||
if prefix.is_empty() == false && prefix != interval {
|
|
||||||
let intervals = split_interval_with_delta(delta, &prefix);
|
|
||||||
intervals.into_iter().for_each(|p_interval| {
|
|
||||||
let attributes = delta.get_attributes(p_interval);
|
|
||||||
log::debug!(
|
|
||||||
"prefix attribute: {:?}, interval: {:?}",
|
|
||||||
attributes,
|
|
||||||
p_interval
|
|
||||||
);
|
|
||||||
new_delta.retain(p_interval.size() as u64, attributes);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
log::debug!("add new op: {:?}", op);
|
|
||||||
new_delta.add(op);
|
|
||||||
|
|
||||||
// suffix
|
|
||||||
if suffix.is_empty() == false {
|
|
||||||
let intervals = split_interval_with_delta(delta, &suffix);
|
|
||||||
intervals.into_iter().for_each(|s_interval| {
|
|
||||||
let attributes = delta.get_attributes(s_interval);
|
|
||||||
log::debug!(
|
|
||||||
"suffix attribute: {:?}, interval: {:?}",
|
|
||||||
attributes,
|
|
||||||
s_interval
|
|
||||||
);
|
|
||||||
new_delta.retain(s_interval.size() as u64, attributes);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
delta.compose(&new_delta).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn target_length_split_with_interval(
|
|
||||||
length: usize,
|
|
||||||
interval: Interval,
|
|
||||||
) -> (Interval, Interval, Interval) {
|
|
||||||
let original_interval = Interval::new(0, length);
|
|
||||||
let prefix = original_interval.prefix(interval);
|
|
||||||
let suffix = original_interval.suffix(interval);
|
|
||||||
(prefix, interval, suffix)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_print_delta(delta: &Delta) {
|
pub fn debug_print_delta(delta: &Delta) {
|
||||||
|
@ -191,8 +191,6 @@ fn transform2() {
|
|||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Insert(1, "456", 0),
|
Insert(1, "456", 0),
|
||||||
Transform(0, 1),
|
Transform(0, 1),
|
||||||
AssertStr(0, "123456"),
|
|
||||||
AssertStr(1, "123456"),
|
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456"}]"#),
|
||||||
AssertOpsJson(1, r#"[{"insert":"123456"}]"#),
|
AssertOpsJson(1, r#"[{"insert":"123456"}]"#),
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user