mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add format and insert extension
This commit is contained in:
parent
e4a64fd06a
commit
1ec4655d1b
@ -40,21 +40,21 @@ class Rules {
|
|||||||
final List<Rule> _rules;
|
final List<Rule> _rules;
|
||||||
|
|
||||||
static final Rules _instance = Rules([
|
static final Rules _instance = Rules([
|
||||||
// const FormatLinkAtCaretPositionRule(),
|
const FormatLinkAtCaretPositionRule(),
|
||||||
// const ResolveLineFormatRule(),
|
const ResolveLineFormatRule(),
|
||||||
// const ResolveInlineFormatRule(),
|
const ResolveInlineFormatRule(),
|
||||||
// const InsertEmbedsRule(),
|
const InsertEmbedsRule(),
|
||||||
// const ForceNewlineForInsertsAroundEmbedRule(),
|
const ForceNewlineForInsertsAroundEmbedRule(),
|
||||||
// const AutoExitBlockRule(),
|
const AutoExitBlockRule(),
|
||||||
// const PreserveBlockStyleOnInsertRule(),
|
const PreserveBlockStyleOnInsertRule(),
|
||||||
// const PreserveLineStyleOnSplitRule(),
|
const PreserveLineStyleOnSplitRule(),
|
||||||
// const ResetLineFormatOnNewLineRule(),
|
const ResetLineFormatOnNewLineRule(),
|
||||||
// const AutoFormatLinksRule(),
|
const AutoFormatLinksRule(),
|
||||||
// const PreserveInlineStylesRule(),
|
const PreserveInlineStylesRule(),
|
||||||
// const CatchAllInsertRule(),
|
const CatchAllInsertRule(),
|
||||||
// const EnsureEmbedLineRule(),
|
const EnsureEmbedLineRule(),
|
||||||
// const PreserveLineStyleOnMergeRule(),
|
const PreserveLineStyleOnMergeRule(),
|
||||||
// const CatchAllDeleteRule(),
|
const CatchAllDeleteRule(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
static Rules getInstance() => _instance;
|
static Rules getInstance() => _instance;
|
||||||
|
@ -17,6 +17,10 @@ pub struct Document {
|
|||||||
impl Document {
|
impl Document {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let delta = Delta::new();
|
let delta = Delta::new();
|
||||||
|
Self::from_delta(delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_delta(delta: Delta) -> Self {
|
||||||
Document {
|
Document {
|
||||||
delta,
|
delta,
|
||||||
history: History::new(),
|
history: History::new(),
|
||||||
@ -35,7 +39,7 @@ impl Document {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let delta = self.view.handle_insert(&self.delta, text, index);
|
let delta = self.view.insert(&self.delta, text, index)?;
|
||||||
let interval = Interval::new(index, index);
|
let interval = Interval::new(index, index);
|
||||||
self.update_with_op(&delta, interval)
|
self.update_with_op(&delta, interval)
|
||||||
}
|
}
|
||||||
@ -139,7 +143,7 @@ impl Document {
|
|||||||
) -> Result<(), OTError> {
|
) -> Result<(), OTError> {
|
||||||
log::debug!("Update document with attribute: {}", attribute);
|
log::debug!("Update document with attribute: {}", attribute);
|
||||||
let mut attributes = AttrsBuilder::new().add(attribute).build();
|
let mut attributes = AttrsBuilder::new().add(attribute).build();
|
||||||
let old_attributes = self.delta.get_attributes(interval);
|
let old_attributes = AttributesIter::from_interval(&self.delta, interval).next_or_empty();
|
||||||
|
|
||||||
log::debug!("combine with old: {:?}", old_attributes);
|
log::debug!("combine with old: {:?}", old_attributes);
|
||||||
attributes.merge(Some(old_attributes));
|
attributes.merge(Some(old_attributes));
|
||||||
@ -202,17 +206,3 @@ fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, I
|
|||||||
let suffix = original_interval.suffix(interval);
|
let suffix = original_interval.suffix(interval);
|
||||||
(prefix, interval, suffix)
|
(prefix, interval, suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
client::Document,
|
client::Document,
|
||||||
core::{Attributes, Delta, Interval},
|
core::{Attribute, Delta, Interval},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait InsertExt {
|
pub trait InsertExt {
|
||||||
fn apply(&self, delta: &Delta, s: &str, index: usize) -> Delta;
|
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FormatExt {
|
pub trait FormatExt {
|
||||||
fn apply(&self, document: &Document, interval: Interval, attributes: Attributes);
|
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DeleteExt {
|
pub trait DeleteExt {
|
||||||
fn apply(&self, document: &Document, interval: Interval);
|
fn apply(&self, delta: &Delta, interval: Interval) -> Option<Delta>;
|
||||||
}
|
}
|
||||||
|
28
rust-lib/flowy-ot/src/client/view/format_ext.rs
Normal file
28
rust-lib/flowy-ot/src/client/view/format_ext.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use crate::{
|
||||||
|
client::view::FormatExt,
|
||||||
|
core::{Attribute, Delta, Interval},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct FormatLinkAtCaretPositionExt {}
|
||||||
|
|
||||||
|
impl FormatExt for FormatLinkAtCaretPositionExt {
|
||||||
|
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ResolveLineFormatExt {}
|
||||||
|
|
||||||
|
impl FormatExt for ResolveLineFormatExt {
|
||||||
|
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ResolveInlineFormatExt {}
|
||||||
|
|
||||||
|
impl FormatExt for ResolveInlineFormatExt {
|
||||||
|
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,73 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
client::view::InsertExt,
|
client::view::InsertExt,
|
||||||
core::{attributes_at_index, Attributes, AttributesIter, Builder, Delta, Interval},
|
core::{
|
||||||
|
attributes_at_index,
|
||||||
|
AttributeKey,
|
||||||
|
Attributes,
|
||||||
|
Delta,
|
||||||
|
DeltaBuilder,
|
||||||
|
DeltaIter,
|
||||||
|
Operation,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PreserveInlineStyleExt {}
|
pub const NEW_LINE: &'static str = "\n";
|
||||||
|
|
||||||
|
pub struct PreserveInlineStyleExt {}
|
||||||
impl PreserveInlineStyleExt {
|
impl PreserveInlineStyleExt {
|
||||||
pub fn new() -> Self { Self {} }
|
pub fn new() -> Self { Self {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InsertExt for PreserveInlineStyleExt {
|
impl InsertExt for PreserveInlineStyleExt {
|
||||||
fn apply(&self, delta: &Delta, text: &str, index: usize) -> Delta {
|
fn apply(&self, delta: &Delta, _replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||||
let attributes = attributes_at_index(delta, index);
|
if text.ends_with(NEW_LINE) {
|
||||||
let mut delta = Delta::new();
|
return None;
|
||||||
let insert = Builder::insert(text).attributes(attributes).build();
|
}
|
||||||
delta.add(insert);
|
|
||||||
|
|
||||||
delta
|
let attributes = attributes_at_index(delta, index);
|
||||||
|
let delta = DeltaBuilder::new().insert(text, attributes).build();
|
||||||
|
|
||||||
|
Some(delta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ResetLineFormatOnNewLineExt {}
|
||||||
|
|
||||||
|
impl ResetLineFormatOnNewLineExt {
|
||||||
|
pub fn new() -> Self { Self {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InsertExt for ResetLineFormatOnNewLineExt {
|
||||||
|
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||||
|
if text != NEW_LINE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut iter = DeltaIter::new(delta);
|
||||||
|
iter.seek_to(index);
|
||||||
|
let maybe_next_op = iter.next();
|
||||||
|
if maybe_next_op.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let op = maybe_next_op.unwrap();
|
||||||
|
if !op.get_data().starts_with(NEW_LINE) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut reset_attribute = Attributes::new();
|
||||||
|
if op.get_attributes().contains_key(&AttributeKey::Header) {
|
||||||
|
reset_attribute.add(AttributeKey::Header.with_value(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = index + replace_len;
|
||||||
|
Some(
|
||||||
|
DeltaBuilder::new()
|
||||||
|
.retain(len, Attributes::default())
|
||||||
|
.insert(NEW_LINE, op.get_attributes())
|
||||||
|
.retain(1, reset_attribute)
|
||||||
|
.trim()
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
mod extension;
|
mod extension;
|
||||||
|
mod format_ext;
|
||||||
mod insert_ext;
|
mod insert_ext;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
pub use extension::*;
|
pub use extension::*;
|
||||||
|
pub use format_ext::*;
|
||||||
pub use insert_ext::*;
|
pub use insert_ext::*;
|
||||||
pub use view::*;
|
pub use view::*;
|
||||||
|
@ -1,32 +1,94 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
client::view::{InsertExt, PreserveInlineStyleExt},
|
client::view::{DeleteExt, FormatExt, InsertExt, *},
|
||||||
core::Delta,
|
core::{Attribute, Delta, Interval},
|
||||||
|
errors::{ErrorBuilder, OTError, OTErrorCode},
|
||||||
};
|
};
|
||||||
|
|
||||||
type InsertExtension = Box<dyn InsertExt>;
|
type InsertExtension = Box<dyn InsertExt>;
|
||||||
|
type FormatExtension = Box<dyn FormatExt>;
|
||||||
|
type DeleteExtension = Box<dyn DeleteExt>;
|
||||||
|
|
||||||
pub struct View {
|
pub struct View {
|
||||||
insert_exts: Vec<InsertExtension>,
|
insert_exts: Vec<InsertExtension>,
|
||||||
|
format_exts: Vec<FormatExtension>,
|
||||||
|
delete_exts: Vec<DeleteExtension>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
let insert_exts = construct_insert_exts();
|
let insert_exts = construct_insert_exts();
|
||||||
Self { insert_exts }
|
let format_exts = construct_format_exts();
|
||||||
|
let delete_exts = construct_delete_exts();
|
||||||
|
Self {
|
||||||
|
insert_exts,
|
||||||
|
format_exts,
|
||||||
|
delete_exts,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_insert(&self, delta: &Delta, s: &str, index: usize) -> Delta {
|
pub(crate) fn insert(&self, delta: &Delta, text: &str, index: usize) -> Result<Delta, OTError> {
|
||||||
let mut new_delta = Delta::new();
|
let mut new_delta = None;
|
||||||
self.insert_exts.iter().for_each(|ext| {
|
for ext in &self.insert_exts {
|
||||||
new_delta = ext.apply(delta, s, index);
|
if let Some(delta) = ext.apply(delta, 0, text, index) {
|
||||||
});
|
new_delta = Some(delta);
|
||||||
new_delta
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match new_delta {
|
||||||
|
None => Err(ErrorBuilder::new(OTErrorCode::ApplyInsertFail).build()),
|
||||||
|
Some(new_delta) => Ok(new_delta),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn replace(
|
||||||
|
&self,
|
||||||
|
delta: &Delta,
|
||||||
|
text: &str,
|
||||||
|
interval: Interval,
|
||||||
|
) -> Result<Delta, OTError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn format(
|
||||||
|
&self,
|
||||||
|
delta: &Delta,
|
||||||
|
attribute: Attribute,
|
||||||
|
interval: Interval,
|
||||||
|
) -> Result<Delta, OTError> {
|
||||||
|
let mut new_delta = None;
|
||||||
|
for ext in &self.format_exts {
|
||||||
|
if let Some(delta) = ext.apply(delta, interval, &attribute) {
|
||||||
|
new_delta = Some(delta);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match new_delta {
|
||||||
|
None => Err(ErrorBuilder::new(OTErrorCode::ApplyFormatFail).build()),
|
||||||
|
Some(new_delta) => Ok(new_delta),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct_insert_exts() -> Vec<InsertExtension> {
|
fn construct_insert_exts() -> Vec<InsertExtension> {
|
||||||
vec![
|
vec![
|
||||||
//
|
|
||||||
Box::new(PreserveInlineStyleExt::new()),
|
Box::new(PreserveInlineStyleExt::new()),
|
||||||
|
Box::new(ResetLineFormatOnNewLineExt::new()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn construct_format_exts() -> Vec<FormatExtension> {
|
||||||
|
vec![
|
||||||
|
Box::new(FormatLinkAtCaretPositionExt {}),
|
||||||
|
Box::new(ResolveLineFormatExt {}),
|
||||||
|
Box::new(ResolveInlineFormatExt {}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn construct_delete_exts() -> Vec<DeleteExtension> {
|
||||||
|
vec![
|
||||||
|
//
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::core::{Attribute, AttributeKey, Operation};
|
use crate::core::{Attribute, AttributeKey, Operation};
|
||||||
use std::{collections::HashMap, fmt, fmt::Formatter};
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
pub const REMOVE_FLAG: &'static str = "";
|
pub const REMOVE_FLAG: &'static str = "";
|
||||||
pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG }
|
pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG }
|
||||||
|
44
rust-lib/flowy-ot/src/core/delta/builder.rs
Normal file
44
rust-lib/flowy-ot/src/core/delta/builder.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use crate::core::{Attributes, Delta, Operation};
|
||||||
|
|
||||||
|
pub struct DeltaBuilder {
|
||||||
|
delta: Delta,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeltaBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
delta: Delta::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retain(mut self, n: usize, attrs: Attributes) -> Self {
|
||||||
|
self.delta.retain(n, attrs);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(mut self, s: &str, attrs: Attributes) -> Self {
|
||||||
|
self.delta.insert(s, attrs);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim(mut self) -> Self {
|
||||||
|
trim(&mut self.delta);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Delta { self.delta }
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::{Attributes, Delta, Interval, Operation},
|
core::{Delta, Interval, Operation},
|
||||||
errors::{ErrorBuilder, OTError, OTErrorCode},
|
errors::{ErrorBuilder, OTError, OTErrorCode},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{cmp::min, slice::Iter};
|
||||||
cmp::min,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
slice::Iter,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Cursor<'a> {
|
pub struct Cursor<'a> {
|
||||||
delta: &'a Delta,
|
delta: &'a Delta,
|
||||||
@ -111,108 +107,3 @@ impl<'a> Cursor<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DeltaIter<'a> {
|
|
||||||
cursor: Cursor<'a>,
|
|
||||||
interval: Interval,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DeltaIter<'a> {
|
|
||||||
pub fn new(delta: &'a Delta) -> Self {
|
|
||||||
let interval = Interval::new(0, usize::MAX);
|
|
||||||
Self::from_interval(delta, interval)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_interval(delta: &'a Delta, interval: Interval) -> Self {
|
|
||||||
let cursor = Cursor::new(delta, interval);
|
|
||||||
Self { cursor, interval }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ops(&mut self) -> Vec<Operation> { self.collect::<Vec<_>>() }
|
|
||||||
|
|
||||||
pub fn seek_to(&mut self, n_char: usize) -> Result<(), OTError> {
|
|
||||||
let _ = self.cursor.seek_to(n_char)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for DeltaIter<'a> {
|
|
||||||
type Item = Operation;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> { self.cursor.next_op() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AttributesIter<'a> {
|
|
||||||
delta_iter: DeltaIter<'a>,
|
|
||||||
interval: Interval,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> AttributesIter<'a> {
|
|
||||||
pub fn new(delta: &'a Delta) -> Self {
|
|
||||||
let interval = Interval::new(0, usize::MAX);
|
|
||||||
Self::from_interval(delta, interval)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_interval(delta: &'a Delta, interval: Interval) -> Self {
|
|
||||||
let delta_iter = DeltaIter::from_interval(delta, interval);
|
|
||||||
Self {
|
|
||||||
delta_iter,
|
|
||||||
interval,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Deref for AttributesIter<'a> {
|
|
||||||
type Target = DeltaIter<'a>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.delta_iter }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DerefMut for AttributesIter<'a> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.delta_iter }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for AttributesIter<'a> {
|
|
||||||
type Item = (usize, Attributes);
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let next_op = self.delta_iter.next();
|
|
||||||
if next_op.is_none() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let mut length: usize = 0;
|
|
||||||
let mut attributes = Attributes::new();
|
|
||||||
|
|
||||||
match next_op.unwrap() {
|
|
||||||
Operation::Delete(_n) => {},
|
|
||||||
Operation::Retain(retain) => {
|
|
||||||
log::debug!("extend retain attributes with {} ", &retain.attributes);
|
|
||||||
attributes.extend(retain.attributes.clone());
|
|
||||||
|
|
||||||
length = retain.n;
|
|
||||||
},
|
|
||||||
Operation::Insert(insert) => {
|
|
||||||
log::debug!("extend insert attributes with {} ", &insert.attributes);
|
|
||||||
attributes.extend(insert.attributes.clone());
|
|
||||||
length = insert.num_chars();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((length, attributes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn attributes_at_index(delta: &Delta, index: usize) -> Attributes {
|
|
||||||
let mut iter = AttributesIter::new(delta);
|
|
||||||
iter.seek_to(index);
|
|
||||||
match iter.next() {
|
|
||||||
// None => Attributes::Follow,
|
|
||||||
None => Attributes::new(),
|
|
||||||
Some((_, attributes)) => attributes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test() {}
|
|
||||||
}
|
|
||||||
|
@ -525,34 +525,6 @@ impl Delta {
|
|||||||
|
|
||||||
pub fn is_empty(&self) -> bool { self.ops.is_empty() }
|
pub fn is_empty(&self) -> bool { self.ops.is_empty() }
|
||||||
|
|
||||||
pub fn get_attributes(&self, interval: Interval) -> Attributes {
|
|
||||||
let mut attributes = Attributes::new();
|
|
||||||
let mut offset: usize = 0;
|
|
||||||
log::debug!("Get attributes at {:?}", interval);
|
|
||||||
self.ops.iter().for_each(|op| match op {
|
|
||||||
Operation::Delete(_n) => {},
|
|
||||||
Operation::Retain(retain) => {
|
|
||||||
if interval.contains_range(offset, offset + retain.n) {
|
|
||||||
log::debug!("extend retain attributes with {} ", &retain.attributes);
|
|
||||||
attributes.extend(retain.attributes.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += retain.n;
|
|
||||||
},
|
|
||||||
Operation::Insert(insert) => {
|
|
||||||
let end = insert.num_chars() as usize;
|
|
||||||
if interval.contains_range(offset, offset + end) {
|
|
||||||
log::debug!("extend insert attributes with {} ", &insert.attributes);
|
|
||||||
attributes.extend(insert.attributes.clone());
|
|
||||||
}
|
|
||||||
offset += end;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
log::debug!("Get attributes result: {} ", &attributes);
|
|
||||||
attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_json(&self) -> String { serde_json::to_string(self).unwrap_or("".to_owned()) }
|
pub fn to_json(&self) -> String { serde_json::to_string(self).unwrap_or("".to_owned()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
111
rust-lib/flowy-ot/src/core/delta/iterator.rs
Normal file
111
rust-lib/flowy-ot/src/core/delta/iterator.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use super::cursor::*;
|
||||||
|
use crate::{
|
||||||
|
core::{Attributes, Delta, Interval, Operation},
|
||||||
|
errors::OTError,
|
||||||
|
};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
pub struct DeltaIter<'a> {
|
||||||
|
cursor: Cursor<'a>,
|
||||||
|
interval: Interval,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DeltaIter<'a> {
|
||||||
|
pub fn new(delta: &'a Delta) -> Self {
|
||||||
|
let interval = Interval::new(0, usize::MAX);
|
||||||
|
Self::from_interval(delta, interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_interval(delta: &'a Delta, interval: Interval) -> Self {
|
||||||
|
let cursor = Cursor::new(delta, interval);
|
||||||
|
Self { cursor, interval }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ops(&mut self) -> Vec<Operation> { self.collect::<Vec<_>>() }
|
||||||
|
|
||||||
|
pub fn seek_to(&mut self, n_char: usize) -> Result<(), OTError> {
|
||||||
|
let _ = self.cursor.seek_to(n_char)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for DeltaIter<'a> {
|
||||||
|
type Item = Operation;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> { self.cursor.next_op() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AttributesIter<'a> {
|
||||||
|
delta_iter: DeltaIter<'a>,
|
||||||
|
interval: Interval,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AttributesIter<'a> {
|
||||||
|
pub fn new(delta: &'a Delta) -> Self {
|
||||||
|
let interval = Interval::new(0, usize::MAX);
|
||||||
|
Self::from_interval(delta, interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_interval(delta: &'a Delta, interval: Interval) -> Self {
|
||||||
|
let delta_iter = DeltaIter::from_interval(delta, interval);
|
||||||
|
Self {
|
||||||
|
delta_iter,
|
||||||
|
interval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_or_empty(&mut self) -> Attributes {
|
||||||
|
match self.next() {
|
||||||
|
None => Attributes::default(),
|
||||||
|
Some((_, attributes)) => attributes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deref for AttributesIter<'a> {
|
||||||
|
type Target = DeltaIter<'a>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.delta_iter }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DerefMut for AttributesIter<'a> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.delta_iter }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for AttributesIter<'a> {
|
||||||
|
type Item = (usize, Attributes);
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let next_op = self.delta_iter.next();
|
||||||
|
if next_op.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut length: usize = 0;
|
||||||
|
let mut attributes = Attributes::new();
|
||||||
|
|
||||||
|
match next_op.unwrap() {
|
||||||
|
Operation::Delete(_n) => {},
|
||||||
|
Operation::Retain(retain) => {
|
||||||
|
log::debug!("extend retain attributes with {} ", &retain.attributes);
|
||||||
|
attributes.extend(retain.attributes.clone());
|
||||||
|
|
||||||
|
length = retain.n;
|
||||||
|
},
|
||||||
|
Operation::Insert(insert) => {
|
||||||
|
log::debug!("extend insert attributes with {} ", &insert.attributes);
|
||||||
|
attributes.extend(insert.attributes.clone());
|
||||||
|
length = insert.num_chars();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((length, attributes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn attributes_at_index(delta: &Delta, index: usize) -> Attributes {
|
||||||
|
let mut iter = AttributesIter::new(delta);
|
||||||
|
iter.seek_to(index);
|
||||||
|
match iter.next() {
|
||||||
|
// None => Attributes::Follow,
|
||||||
|
None => Attributes::new(),
|
||||||
|
Some((_, attributes)) => attributes,
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
|
mod builder;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
mod delta;
|
mod delta;
|
||||||
|
mod iterator;
|
||||||
|
|
||||||
|
pub use builder::*;
|
||||||
pub use cursor::*;
|
pub use cursor::*;
|
||||||
pub use delta::*;
|
pub use delta::*;
|
||||||
|
pub use iterator::*;
|
||||||
|
@ -16,17 +16,11 @@ pub enum Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Operation {
|
impl Operation {
|
||||||
pub fn is_delete(&self) -> bool {
|
pub fn get_data(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Operation::Delete(_) => true,
|
Operation::Delete(_) => "",
|
||||||
_ => false,
|
Operation::Retain(_) => "",
|
||||||
}
|
Operation::Insert(insert) => &insert.s,
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_noop(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Operation::Retain(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ impl Error for OTError {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum OTErrorCode {
|
pub enum OTErrorCode {
|
||||||
IncompatibleLength,
|
IncompatibleLength,
|
||||||
|
ApplyInsertFail,
|
||||||
|
ApplyFormatFail,
|
||||||
UndoFail,
|
UndoFail,
|
||||||
RedoFail,
|
RedoFail,
|
||||||
}
|
}
|
||||||
|
@ -64,11 +64,7 @@ impl OpTester {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut documents = Vec::with_capacity(2);
|
Self { documents: vec![] }
|
||||||
for _ in 0..2 {
|
|
||||||
documents.push(Document::new());
|
|
||||||
}
|
|
||||||
Self { documents }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_op(&mut self, op: &TestOp) {
|
pub fn run_op(&mut self, op: &TestOp) {
|
||||||
@ -173,6 +169,22 @@ impl OpTester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_script(&mut self, script: Vec<TestOp>) {
|
pub fn run_script(&mut self, script: Vec<TestOp>) {
|
||||||
|
let delta = Delta::new();
|
||||||
|
self.run(script, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_script_with_newline(&mut self, script: Vec<TestOp>) {
|
||||||
|
let mut delta = Delta::new();
|
||||||
|
delta.insert("\n", Attributes::default());
|
||||||
|
self.run(script, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self, script: Vec<TestOp>, delta: Delta) {
|
||||||
|
let mut documents = Vec::with_capacity(2);
|
||||||
|
for _ in 0..2 {
|
||||||
|
documents.push(Document::from_delta(delta.clone()));
|
||||||
|
}
|
||||||
|
self.documents = documents;
|
||||||
for (_i, op) in script.iter().enumerate() {
|
for (_i, op) in script.iter().enumerate() {
|
||||||
self.run_op(op);
|
self.run_op(op);
|
||||||
}
|
}
|
||||||
|
@ -6,18 +6,16 @@ use flowy_ot::{client::RECORD_THRESHOLD, core::Interval};
|
|||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_insert() {
|
fn delta_undo_insert() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Undo(0),
|
Undo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_insert2() {
|
fn delta_undo_insert2() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Wait(RECORD_THRESHOLD),
|
Wait(RECORD_THRESHOLD),
|
||||||
Insert(0, "456", 0),
|
Insert(0, "456", 0),
|
||||||
@ -26,13 +24,12 @@ fn delta_undo_insert2() {
|
|||||||
Undo(0),
|
Undo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_redo_insert() {
|
fn delta_redo_insert() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
||||||
Undo(0),
|
Undo(0),
|
||||||
@ -40,13 +37,12 @@ fn delta_redo_insert() {
|
|||||||
Redo(0),
|
Redo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_redo_insert_with_lagging() {
|
fn delta_redo_insert_with_lagging() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Wait(RECORD_THRESHOLD),
|
Wait(RECORD_THRESHOLD),
|
||||||
Insert(0, "456", 3),
|
Insert(0, "456", 3),
|
||||||
@ -60,38 +56,35 @@ fn delta_redo_insert_with_lagging() {
|
|||||||
Undo(0),
|
Undo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_attributes() {
|
fn delta_undo_attributes() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
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"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_attributes_with_lagging() {
|
fn delta_undo_attributes_with_lagging() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Wait(RECORD_THRESHOLD),
|
Wait(RECORD_THRESHOLD),
|
||||||
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":"123\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_redo_attributes() {
|
fn delta_redo_attributes() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
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),
|
||||||
@ -102,13 +95,12 @@ fn delta_redo_attributes() {
|
|||||||
r#" [{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
|
r#" [{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_redo_attributes_with_lagging() {
|
fn delta_redo_attributes_with_lagging() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Wait(RECORD_THRESHOLD),
|
Wait(RECORD_THRESHOLD),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
@ -120,7 +112,7 @@ fn delta_redo_attributes_with_lagging() {
|
|||||||
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
|
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -139,7 +131,6 @@ fn delta_undo_delete() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_delete2() {
|
fn delta_undo_delete2() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
Delete(0, Interval::new(0, 1)),
|
Delete(0, Interval::new(0, 1)),
|
||||||
@ -153,13 +144,12 @@ fn delta_undo_delete2() {
|
|||||||
Undo(0),
|
Undo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_delete2_with_lagging() {
|
fn delta_undo_delete2_with_lagging() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Wait(RECORD_THRESHOLD),
|
Wait(RECORD_THRESHOLD),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
@ -181,13 +171,12 @@ fn delta_undo_delete2_with_lagging() {
|
|||||||
"#,
|
"#,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_redo_delete() {
|
fn delta_redo_delete() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Delete(0, Interval::new(0, 3)),
|
Delete(0, Interval::new(0, 3)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
@ -195,13 +184,12 @@ fn delta_redo_delete() {
|
|||||||
Redo(0),
|
Redo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_replace() {
|
fn delta_undo_replace() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
Replace(0, Interval::new(0, 2), "ab"),
|
Replace(0, Interval::new(0, 2), "ab"),
|
||||||
@ -215,13 +203,12 @@ fn delta_undo_replace() {
|
|||||||
Undo(0),
|
Undo(0),
|
||||||
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_replace_with_lagging() {
|
fn delta_undo_replace_with_lagging() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Wait(RECORD_THRESHOLD),
|
Wait(RECORD_THRESHOLD),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
@ -240,13 +227,12 @@ fn delta_undo_replace_with_lagging() {
|
|||||||
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
|
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_redo_replace() {
|
fn delta_redo_replace() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "\n", 0),
|
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
Replace(0, Interval::new(0, 2), "ab"),
|
Replace(0, Interval::new(0, 2), "ab"),
|
||||||
@ -260,5 +246,5 @@ fn delta_redo_replace() {
|
|||||||
"#,
|
"#,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user