mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add auto exit block test
This commit is contained in:
parent
9bc72d3b9e
commit
4ab4f744ba
@ -73,6 +73,7 @@ class Document {
|
||||
print('insert delta: $delta');
|
||||
compose(delta, ChangeSource.LOCAL);
|
||||
print('compose insert, current document $_delta');
|
||||
print('compose end');
|
||||
return delta;
|
||||
}
|
||||
|
||||
|
@ -13,10 +13,8 @@ impl FormatExt for FormatLinkAtCaretPositionExt {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(interval.start);
|
||||
|
||||
let (before, after) = (iter.next_op_before(interval.size()), iter.next());
|
||||
let mut iter = DeltaIter::from_offset(delta, interval.start);
|
||||
let (before, after) = (iter.last_op_before_index(interval.size()), iter.next_op());
|
||||
let mut start = interval.end;
|
||||
let mut retain = 0;
|
||||
|
||||
|
@ -25,12 +25,11 @@ impl FormatExt for ResolveBlockFormatExt {
|
||||
}
|
||||
|
||||
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(interval.start);
|
||||
let mut iter = DeltaIter::from_offset(delta, interval.start);
|
||||
let mut start = 0;
|
||||
let end = interval.size();
|
||||
while start < end && iter.has_next() {
|
||||
let next_op = iter.next_op_before(end - start).unwrap();
|
||||
let next_op = iter.last_op_before_index(end - start).unwrap();
|
||||
match find_newline(next_op.get_data()) {
|
||||
None => new_delta.retain(next_op.len(), Attributes::empty()),
|
||||
Some(_) => {
|
||||
|
@ -15,14 +15,12 @@ impl FormatExt for ResolveInlineFormatExt {
|
||||
return None;
|
||||
}
|
||||
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(interval.start);
|
||||
|
||||
let mut iter = DeltaIter::from_offset(delta, interval.start);
|
||||
let mut start = 0;
|
||||
let end = interval.size();
|
||||
|
||||
while start < end && iter.has_next() {
|
||||
let next_op = iter.next_op_before(end - start).unwrap();
|
||||
let next_op = iter.last_op_before_index(end - start).unwrap();
|
||||
match find_newline(next_op.get_data()) {
|
||||
None => new_delta.retain(next_op.len(), attribute.clone().into()),
|
||||
Some(_) => {
|
||||
|
@ -1,23 +1,70 @@
|
||||
use crate::{
|
||||
client::{extensions::InsertExt, util::is_newline},
|
||||
core::{Delta, DeltaIter},
|
||||
core::{
|
||||
AttributeKey,
|
||||
AttributeValue,
|
||||
Attributes,
|
||||
CharMetric,
|
||||
Delta,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
Operation,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::core::is_empty_line_at_index;
|
||||
|
||||
pub struct AutoExitBlockExt {}
|
||||
|
||||
impl InsertExt for AutoExitBlockExt {
|
||||
fn ext_name(&self) -> &str { "AutoExitBlockExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, _replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
// Auto exit block will be triggered by enter two new lines
|
||||
if !is_newline(text) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
let _prev = iter.next_op_before(index);
|
||||
let _next = iter.next_op();
|
||||
if !is_empty_line_at_index(delta, index) {
|
||||
return None;
|
||||
}
|
||||
|
||||
None
|
||||
let mut iter = DeltaIter::from_offset(delta, index);
|
||||
let next = iter.next_op()?;
|
||||
let mut attributes = next.get_attributes();
|
||||
|
||||
let block_attributes = attributes_except_header(&next);
|
||||
if block_attributes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if next.len() > 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
match iter.first_op_contains_newline() {
|
||||
None => {},
|
||||
Some((newline_op, _)) => {
|
||||
let newline_attributes = attributes_except_header(&newline_op);
|
||||
if block_attributes == newline_attributes {
|
||||
return None;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
attributes.mark_as_removed_except(&AttributeKey::Header);
|
||||
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.retain_with_attributes(1, attributes)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn attributes_except_header(op: &Operation) -> Attributes {
|
||||
let mut attributes = op.get_attributes();
|
||||
attributes.remove(AttributeKey::Header);
|
||||
attributes
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ impl InsertExt for AutoFormatExt {
|
||||
return None;
|
||||
}
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
if let Some(prev) = iter.next_op_before(index) {
|
||||
if let Some(prev) = iter.last_op_before_index(index) {
|
||||
match AutoFormat::parse(prev.get_data()) {
|
||||
None => {},
|
||||
Some(formatter) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
client::extensions::{InsertExt, NEW_LINE},
|
||||
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter},
|
||||
client::extensions::InsertExt,
|
||||
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter, NEW_LINE},
|
||||
};
|
||||
|
||||
pub struct DefaultInsertExt {}
|
||||
|
@ -1,9 +1,6 @@
|
||||
use crate::{
|
||||
client::{
|
||||
extensions::InsertExt,
|
||||
util::{contain_newline, OpNewline},
|
||||
},
|
||||
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter},
|
||||
client::{extensions::InsertExt, util::contain_newline},
|
||||
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter, OpNewline},
|
||||
};
|
||||
|
||||
pub struct PreserveInlineStylesExt {}
|
||||
@ -16,7 +13,7 @@ impl InsertExt for PreserveInlineStylesExt {
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
let prev = iter.next_op_before(index)?;
|
||||
let prev = iter.last_op_before_index(index)?;
|
||||
if OpNewline::parse(&prev).is_contain() {
|
||||
return None;
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
use crate::{
|
||||
client::{
|
||||
extensions::{InsertExt, NEW_LINE},
|
||||
util::is_newline,
|
||||
},
|
||||
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter},
|
||||
client::{extensions::InsertExt, util::is_newline},
|
||||
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter, NEW_LINE},
|
||||
};
|
||||
|
||||
pub struct ResetLineFormatOnNewLineExt {}
|
||||
@ -16,8 +13,7 @@ impl InsertExt for ResetLineFormatOnNewLineExt {
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(index);
|
||||
let next_op = iter.next()?;
|
||||
let next_op = iter.first_op_after_index(index)?;
|
||||
if !next_op.get_data().starts_with(NEW_LINE) {
|
||||
return None;
|
||||
}
|
||||
|
@ -8,9 +8,6 @@ mod delete;
|
||||
mod format;
|
||||
mod insert;
|
||||
|
||||
pub const NEW_LINE: &'static str = "\n";
|
||||
pub const WHITESPACE: &'static str = " ";
|
||||
|
||||
pub type InsertExtension = Box<dyn InsertExt>;
|
||||
pub type FormatExtension = Box<dyn FormatExt>;
|
||||
pub type DeleteExtension = Box<dyn DeleteExt>;
|
||||
|
@ -1,7 +1,4 @@
|
||||
use crate::{
|
||||
client::extensions::{NEW_LINE, WHITESPACE},
|
||||
core::Operation,
|
||||
};
|
||||
use crate::core::{Operation, NEW_LINE, WHITESPACE};
|
||||
|
||||
#[inline]
|
||||
pub fn find_newline(s: &str) -> Option<usize> {
|
||||
@ -11,51 +8,6 @@ pub fn find_newline(s: &str) -> Option<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum OpNewline {
|
||||
Start,
|
||||
End,
|
||||
Contain,
|
||||
Equal,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl OpNewline {
|
||||
pub fn parse(op: &Operation) -> OpNewline {
|
||||
let s = op.get_data();
|
||||
|
||||
if s == NEW_LINE {
|
||||
return OpNewline::Equal;
|
||||
}
|
||||
|
||||
if s.starts_with(NEW_LINE) {
|
||||
return OpNewline::Start;
|
||||
}
|
||||
|
||||
if s.ends_with(NEW_LINE) {
|
||||
return OpNewline::End;
|
||||
}
|
||||
|
||||
if s.contains(NEW_LINE) {
|
||||
return OpNewline::Contain;
|
||||
}
|
||||
|
||||
OpNewline::NotFound
|
||||
}
|
||||
|
||||
pub fn is_start(&self) -> bool { self == &OpNewline::Start }
|
||||
|
||||
pub fn is_end(&self) -> bool { self == &OpNewline::End }
|
||||
|
||||
pub fn is_not_found(&self) -> bool { self == &OpNewline::NotFound }
|
||||
|
||||
pub fn is_contain(&self) -> bool {
|
||||
self.is_start() || self.is_end() || self.is_equal() || self == &OpNewline::Contain
|
||||
}
|
||||
|
||||
pub fn is_equal(&self) -> bool { self == &OpNewline::Equal }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_op_contains_newline(op: &Operation) -> bool { contain_newline(op.get_data()) }
|
||||
|
||||
|
@ -6,11 +6,19 @@ use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator};
|
||||
lazy_static! {
|
||||
static ref BLOCK_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
|
||||
AttributeKey::Header,
|
||||
AttributeKey::Align,
|
||||
AttributeKey::List,
|
||||
AttributeKey::CodeBlock,
|
||||
AttributeKey::QuoteBlock,
|
||||
AttributeKey::LeftAlignment,
|
||||
AttributeKey::CenterAlignment,
|
||||
AttributeKey::RightAlignment,
|
||||
AttributeKey::JustifyAlignment,
|
||||
AttributeKey::Indent,
|
||||
AttributeKey::Align,
|
||||
AttributeKey::CodeBlock,
|
||||
AttributeKey::List,
|
||||
AttributeKey::Bullet,
|
||||
AttributeKey::Ordered,
|
||||
AttributeKey::Checked,
|
||||
AttributeKey::UnChecked,
|
||||
AttributeKey::QuoteBlock,
|
||||
]);
|
||||
static ref INLINE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
|
||||
AttributeKey::Bold,
|
||||
@ -19,8 +27,15 @@ lazy_static! {
|
||||
AttributeKey::StrikeThrough,
|
||||
AttributeKey::Link,
|
||||
AttributeKey::Color,
|
||||
AttributeKey::Font,
|
||||
AttributeKey::Size,
|
||||
AttributeKey::Background,
|
||||
]);
|
||||
static ref INGORE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
|
||||
AttributeKey::Width,
|
||||
AttributeKey::Height,
|
||||
AttributeKey::Style,
|
||||
]);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
@ -116,45 +131,26 @@ impl AttributeKey {
|
||||
pub fn value<T: Into<AttributeValue>>(&self, value: T) -> Attribute {
|
||||
let key = self.clone();
|
||||
let value: AttributeValue = value.into();
|
||||
match self {
|
||||
AttributeKey::Bold
|
||||
| AttributeKey::Italic
|
||||
| AttributeKey::Underline
|
||||
| AttributeKey::StrikeThrough
|
||||
| AttributeKey::Link
|
||||
| AttributeKey::Color
|
||||
| AttributeKey::Background
|
||||
| AttributeKey::Font
|
||||
| AttributeKey::Size => Attribute {
|
||||
if INLINE_KEYS.contains(self) {
|
||||
return Attribute {
|
||||
key,
|
||||
value,
|
||||
scope: AttributeScope::Inline,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
AttributeKey::Header
|
||||
| AttributeKey::LeftAlignment
|
||||
| AttributeKey::CenterAlignment
|
||||
| AttributeKey::RightAlignment
|
||||
| AttributeKey::JustifyAlignment
|
||||
| AttributeKey::Indent
|
||||
| AttributeKey::Align
|
||||
| AttributeKey::CodeBlock
|
||||
| AttributeKey::List
|
||||
| AttributeKey::Bullet
|
||||
| AttributeKey::Ordered
|
||||
| AttributeKey::Checked
|
||||
| AttributeKey::UnChecked
|
||||
| AttributeKey::QuoteBlock => Attribute {
|
||||
if BLOCK_KEYS.contains(self) {
|
||||
return Attribute {
|
||||
key,
|
||||
value,
|
||||
scope: AttributeScope::Block,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
AttributeKey::Width | AttributeKey::Height | AttributeKey::Style => Attribute {
|
||||
key,
|
||||
value,
|
||||
scope: AttributeScope::Ignore,
|
||||
},
|
||||
Attribute {
|
||||
key,
|
||||
value,
|
||||
scope: AttributeScope::Ignore,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,3 +179,10 @@ impl std::convert::From<bool> for AttributeValue {
|
||||
AttributeValue(val.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_block_except_header(k: &AttributeKey) -> bool {
|
||||
if k == &AttributeKey::Header {
|
||||
return false;
|
||||
}
|
||||
BLOCK_KEYS.contains(k)
|
||||
}
|
||||
|
@ -37,17 +37,39 @@ impl Attributes {
|
||||
self.inner.insert(key, value);
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &AttributeKey) {
|
||||
pub fn mark_as_removed(&mut self, key: &AttributeKey) {
|
||||
let value: AttributeValue = REMOVE_FLAG.into();
|
||||
self.inner.insert(key.clone(), value);
|
||||
}
|
||||
|
||||
// Remove the key if its value is empty. e.g. { bold: "" }
|
||||
pub fn remove_empty_value(&mut self) { self.inner.retain(|_, v| !should_remove(v)); }
|
||||
pub fn mark_as_removed_except(&mut self, attribute: &AttributeKey) {
|
||||
self.inner.iter_mut().for_each(|(k, v)| {
|
||||
if k != attribute {
|
||||
v.0 = REMOVE_FLAG.into();
|
||||
}
|
||||
v.0 = REMOVE_FLAG.into();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: AttributeKey) { self.inner.retain(|k, _| k != &key); }
|
||||
|
||||
// pub fn block_attributes_except_header(attributes: &Attributes) -> Attributes
|
||||
// { let mut new_attributes = Attributes::new();
|
||||
// attributes.iter().for_each(|(k, v)| {
|
||||
// if k != &AttributeKey::Header {
|
||||
// new_attributes.insert(k.clone(), v.clone());
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// new_attributes
|
||||
// }
|
||||
|
||||
// Remove the empty attribute which value is empty. e.g. {bold: ""}.
|
||||
pub fn remove_empty(&mut self) { self.inner.retain(|_, v| !should_remove(v)); }
|
||||
|
||||
pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); }
|
||||
|
||||
// Update self attributes by constructing new attributes from the other if it's
|
||||
// Update inner by constructing new attributes from the other if it's
|
||||
// not None and replace the key/value with self key/value.
|
||||
pub fn merge(&mut self, other: Option<Attributes>) {
|
||||
if other.is_none() {
|
||||
@ -144,7 +166,7 @@ pub fn invert_attributes(attr: Attributes, base: Attributes) -> 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.mark_as_removed(k);
|
||||
}
|
||||
attributes
|
||||
});
|
||||
@ -156,15 +178,3 @@ pub fn merge_attributes(mut attributes: Attributes, other: Attributes) -> Attrib
|
||||
attributes.extend(other);
|
||||
attributes
|
||||
}
|
||||
|
||||
pub trait AttributesRule {
|
||||
// Remove the empty attribute that its value is empty. e.g. {bold: ""}.
|
||||
fn remove_empty(self) -> Attributes;
|
||||
}
|
||||
|
||||
impl AttributesRule for Attributes {
|
||||
fn remove_empty(mut self) -> Attributes {
|
||||
self.remove_empty_value();
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
@ -34,10 +34,10 @@ impl<'a> Cursor<'a> {
|
||||
// get the next operation interval
|
||||
pub fn next_iv(&self) -> Interval { self.next_iv_before(None) }
|
||||
|
||||
pub fn next_op(&mut self) -> Option<Operation> { self.next_op_before(None) }
|
||||
pub fn next_op(&mut self) -> Option<Operation> { self.last_op_before_index(None) }
|
||||
|
||||
// get the last operation before the index
|
||||
pub fn next_op_before(&mut self, index: Option<usize>) -> Option<Operation> {
|
||||
pub fn last_op_before_index(&mut self, index: Option<usize>) -> Option<Operation> {
|
||||
let mut find_op = None;
|
||||
let next_op = self.next_op.take();
|
||||
let mut next_op = next_op.as_ref();
|
||||
@ -74,7 +74,7 @@ impl<'a> Cursor<'a> {
|
||||
let pos = self.cur_char_count - pre_char_count;
|
||||
let end = index.unwrap();
|
||||
if end > pos {
|
||||
return self.next_op_before(Some(end - pos));
|
||||
return self.last_op_before_index(Some(end - pos));
|
||||
}
|
||||
}
|
||||
return find_op;
|
||||
@ -173,7 +173,7 @@ pub struct CharMetric {}
|
||||
impl Metric for CharMetric {
|
||||
fn seek(cursor: &mut Cursor, index: usize) -> SeekResult {
|
||||
let _ = check_bound(cursor.cur_char_count, index)?;
|
||||
let _ = cursor.next_op_before(Some(index));
|
||||
let _ = cursor.last_op_before_index(Some(index));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -168,11 +168,11 @@ impl Delta {
|
||||
);
|
||||
|
||||
let op = iter
|
||||
.next_op_before(length)
|
||||
.last_op_before_index(length)
|
||||
.unwrap_or(OpBuilder::retain(length).build());
|
||||
|
||||
let other_op = other_iter
|
||||
.next_op_before(length)
|
||||
.last_op_before_index(length)
|
||||
.unwrap_or(OpBuilder::retain(length).build());
|
||||
|
||||
debug_assert_eq!(op.len(), other_op.len());
|
||||
@ -194,7 +194,7 @@ impl Delta {
|
||||
insert.attributes.clone(),
|
||||
other_retain.attributes.clone(),
|
||||
);
|
||||
composed_attrs = composed_attrs.remove_empty();
|
||||
composed_attrs.remove_empty();
|
||||
new_delta.add(
|
||||
OpBuilder::insert(op.get_data())
|
||||
.attributes(composed_attrs)
|
||||
@ -300,7 +300,7 @@ impl Delta {
|
||||
},
|
||||
(Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => {
|
||||
let mut composed_attrs = compose_operation(&next_op1, &next_op2);
|
||||
composed_attrs = composed_attrs.remove_empty();
|
||||
composed_attrs.remove_empty();
|
||||
|
||||
log::debug!(
|
||||
"compose: [{} - {}], composed_attrs: {}",
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::cursor::*;
|
||||
use crate::core::{Attributes, Delta, Interval, Operation};
|
||||
use crate::core::{Attributes, Delta, Interval, Operation, NEW_LINE};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize;
|
||||
@ -14,6 +14,13 @@ impl<'a> DeltaIter<'a> {
|
||||
Self::from_interval(delta, interval)
|
||||
}
|
||||
|
||||
pub fn from_offset(delta: &'a Delta, offset: usize) -> Self {
|
||||
let interval = Interval::new(0, MAX_IV_LEN);
|
||||
let mut iter = Self::from_interval(delta, interval);
|
||||
iter.seek::<CharMetric>(offset);
|
||||
iter
|
||||
}
|
||||
|
||||
pub fn from_interval(delta: &'a Delta, interval: Interval) -> Self {
|
||||
let cursor = Cursor::new(delta, interval);
|
||||
Self { cursor }
|
||||
@ -21,8 +28,6 @@ impl<'a> DeltaIter<'a> {
|
||||
|
||||
pub fn ops(&mut self) -> Vec<Operation> { self.collect::<Vec<_>>() }
|
||||
|
||||
pub fn next_op(&mut self) -> Option<Operation> { self.cursor.next_op() }
|
||||
|
||||
pub fn next_op_len(&self) -> Option<usize> {
|
||||
let interval = self.cursor.next_iv();
|
||||
if interval.is_empty() {
|
||||
@ -32,8 +37,30 @@ impl<'a> DeltaIter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_op_before(&mut self, index: usize) -> Option<Operation> {
|
||||
self.cursor.next_op_before(Some(index))
|
||||
// find next op contains NEW_LINE
|
||||
pub fn first_op_contains_newline(&mut self) -> Option<(Operation, usize)> {
|
||||
let mut offset = 0;
|
||||
while self.has_next() {
|
||||
if let Some(op) = self.next_op() {
|
||||
if OpNewline::parse(&op).is_contain() {
|
||||
return Some((op, offset));
|
||||
}
|
||||
offset += op.len();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn next_op(&mut self) -> Option<Operation> { self.cursor.next_op() }
|
||||
|
||||
pub fn last_op_before_index(&mut self, index: usize) -> Option<Operation> {
|
||||
self.cursor.last_op_before_index(Some(index))
|
||||
}
|
||||
|
||||
pub fn first_op_after_index(&mut self, index: usize) -> Option<Operation> {
|
||||
self.seek::<CharMetric>(index);
|
||||
self.next_op()
|
||||
}
|
||||
|
||||
pub fn seek<M: Metric>(&mut self, index: usize) {
|
||||
@ -72,6 +99,22 @@ impl<'a> Iterator for DeltaIter<'a> {
|
||||
fn next(&mut self) -> Option<Self::Item> { self.next_op() }
|
||||
}
|
||||
|
||||
pub fn is_empty_line_at_index(delta: &Delta, index: usize) -> bool {
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
let (prev, next) = (iter.last_op_before_index(index), iter.next_op());
|
||||
if prev.is_none() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if next.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let prev = prev.unwrap();
|
||||
let next = next.unwrap();
|
||||
OpNewline::parse(&prev).is_end() && OpNewline::parse(&next).is_start()
|
||||
}
|
||||
|
||||
pub struct AttributesIter<'a> {
|
||||
delta_iter: DeltaIter<'a>,
|
||||
}
|
||||
@ -133,3 +176,48 @@ impl<'a> Iterator for AttributesIter<'a> {
|
||||
Some((length, attributes))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum OpNewline {
|
||||
Start,
|
||||
End,
|
||||
Contain,
|
||||
Equal,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl OpNewline {
|
||||
pub fn parse(op: &Operation) -> OpNewline {
|
||||
let s = op.get_data();
|
||||
|
||||
if s == NEW_LINE {
|
||||
return OpNewline::Equal;
|
||||
}
|
||||
|
||||
if s.starts_with(NEW_LINE) {
|
||||
return OpNewline::Start;
|
||||
}
|
||||
|
||||
if s.ends_with(NEW_LINE) {
|
||||
return OpNewline::End;
|
||||
}
|
||||
|
||||
if s.contains(NEW_LINE) {
|
||||
return OpNewline::Contain;
|
||||
}
|
||||
|
||||
OpNewline::NotFound
|
||||
}
|
||||
|
||||
pub fn is_start(&self) -> bool { self == &OpNewline::Start || self.is_equal() }
|
||||
|
||||
pub fn is_end(&self) -> bool { self == &OpNewline::End || self.is_equal() }
|
||||
|
||||
pub fn is_not_found(&self) -> bool { self == &OpNewline::NotFound }
|
||||
|
||||
pub fn is_contain(&self) -> bool {
|
||||
self.is_start() || self.is_end() || self.is_equal() || self == &OpNewline::Contain
|
||||
}
|
||||
|
||||
pub fn is_equal(&self) -> bool { self == &OpNewline::Equal }
|
||||
}
|
||||
|
@ -8,3 +8,6 @@ pub use builder::*;
|
||||
pub use cursor::*;
|
||||
pub use delta::*;
|
||||
pub use iterator::*;
|
||||
|
||||
pub const NEW_LINE: &'static str = "\n";
|
||||
pub const WHITESPACE: &'static str = " ";
|
||||
|
@ -3,7 +3,7 @@ pub mod helper;
|
||||
use crate::helper::{TestOp::*, *};
|
||||
use flowy_ot::core::Interval;
|
||||
|
||||
use flowy_ot::client::extensions::{NEW_LINE, WHITESPACE};
|
||||
use flowy_ot::core::{NEW_LINE, WHITESPACE};
|
||||
|
||||
#[test]
|
||||
fn attributes_insert_text() {
|
||||
@ -646,6 +646,10 @@ fn attributes_add_bullet() {
|
||||
r#"[{"insert":"1"},{"insert":"\n","attributes":{"bullet":"true"}}]"#,
|
||||
),
|
||||
Insert(0, NEW_LINE, 1),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"1"},{"insert":"\n\n","attributes":{"bullet":"true"}}]"#,
|
||||
),
|
||||
Insert(0, "2", 2),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
@ -672,3 +676,19 @@ fn attributes_un_bullet_one() {
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_auto_exit_block() {
|
||||
let ops = vec![
|
||||
Insert(0, "1", 0),
|
||||
Bullet(0, Interval::new(0, 1), true),
|
||||
Insert(0, NEW_LINE, 1),
|
||||
Insert(0, NEW_LINE, 2),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"1"},{"insert":"\n","attributes":{"bullet":"true"}},{"insert":"\n"}]"#,
|
||||
),
|
||||
];
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use flowy_ot::{client::Document, core::*};
|
||||
use rand::{prelude::*, Rng as WrappedRng};
|
||||
use std::{sync::Once, time::Duration};
|
||||
|
||||
const LEVEL: &'static str = "info";
|
||||
const LEVEL: &'static str = "debug";
|
||||
|
||||
#[derive(Clone, Debug, Display)]
|
||||
pub enum TestOp {
|
||||
|
@ -147,14 +147,13 @@ fn delta_get_ops_in_interval_7() {
|
||||
delta.add(insert_a.clone());
|
||||
delta.add(retain_a.clone());
|
||||
|
||||
let mut iter_1 = DeltaIter::new(&delta);
|
||||
iter_1.seek::<CharMetric>(2);
|
||||
let mut iter_1 = DeltaIter::from_offset(&delta, 2);
|
||||
assert_eq!(iter_1.next_op().unwrap(), OpBuilder::insert("345").build());
|
||||
assert_eq!(iter_1.next_op().unwrap(), OpBuilder::retain(3).build());
|
||||
|
||||
let mut iter_2 = DeltaIter::new(&delta);
|
||||
assert_eq!(
|
||||
iter_2.next_op_before(2).unwrap(),
|
||||
iter_2.last_op_before_index(2).unwrap(),
|
||||
OpBuilder::insert("12").build()
|
||||
);
|
||||
assert_eq!(iter_2.next_op().unwrap(), OpBuilder::insert("345").build());
|
||||
@ -181,7 +180,7 @@ fn delta_seek_2() {
|
||||
|
||||
let mut iter = DeltaIter::new(&delta);
|
||||
assert_eq!(
|
||||
iter.next_op_before(1).unwrap(),
|
||||
iter.last_op_before_index(1).unwrap(),
|
||||
OpBuilder::insert("1").build()
|
||||
);
|
||||
}
|
||||
@ -193,21 +192,21 @@ fn delta_seek_3() {
|
||||
|
||||
let mut iter = DeltaIter::new(&delta);
|
||||
assert_eq!(
|
||||
iter.next_op_before(2).unwrap(),
|
||||
iter.last_op_before_index(2).unwrap(),
|
||||
OpBuilder::insert("12").build()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
iter.next_op_before(2).unwrap(),
|
||||
iter.last_op_before_index(2).unwrap(),
|
||||
OpBuilder::insert("34").build()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
iter.next_op_before(2).unwrap(),
|
||||
iter.last_op_before_index(2).unwrap(),
|
||||
OpBuilder::insert("5").build()
|
||||
);
|
||||
|
||||
assert_eq!(iter.next_op_before(1), None);
|
||||
assert_eq!(iter.last_op_before_index(1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -218,7 +217,7 @@ fn delta_seek_4() {
|
||||
let mut iter = DeltaIter::new(&delta);
|
||||
iter.seek::<CharMetric>(3);
|
||||
assert_eq!(
|
||||
iter.next_op_before(2).unwrap(),
|
||||
iter.last_op_before_index(2).unwrap(),
|
||||
OpBuilder::insert("45").build()
|
||||
);
|
||||
}
|
||||
@ -238,7 +237,7 @@ fn delta_seek_5() {
|
||||
iter.seek::<CharMetric>(0);
|
||||
|
||||
assert_eq!(
|
||||
iter.next_op_before(4).unwrap(),
|
||||
iter.last_op_before_index(4).unwrap(),
|
||||
OpBuilder::insert("1234").attributes(attributes).build(),
|
||||
);
|
||||
}
|
||||
@ -252,7 +251,7 @@ fn delta_next_op_len_test() {
|
||||
iter.seek::<CharMetric>(3);
|
||||
assert_eq!(iter.next_op_len().unwrap(), 2);
|
||||
assert_eq!(
|
||||
iter.next_op_before(1).unwrap(),
|
||||
iter.last_op_before_index(1).unwrap(),
|
||||
OpBuilder::insert("4").build()
|
||||
);
|
||||
assert_eq!(iter.next_op_len().unwrap(), 1);
|
||||
@ -267,12 +266,22 @@ fn delta_next_op_len_test2() {
|
||||
|
||||
assert_eq!(iter.next_op_len().unwrap(), 5);
|
||||
assert_eq!(
|
||||
iter.next_op_before(5).unwrap(),
|
||||
iter.last_op_before_index(5).unwrap(),
|
||||
OpBuilder::insert("12345").build()
|
||||
);
|
||||
assert_eq!(iter.next_op_len(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_next_op_len_test3() {
|
||||
let mut delta = Delta::default();
|
||||
delta.add(OpBuilder::insert("12345").build());
|
||||
let mut iter = DeltaIter::new(&delta);
|
||||
|
||||
assert_eq!(iter.last_op_before_index(0), None,);
|
||||
assert_eq!(iter.next_op_len().unwrap(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lengths() {
|
||||
let mut delta = Delta::default();
|
||||
|
Loading…
Reference in New Issue
Block a user