config header attribute & add test

This commit is contained in:
appflowy 2021-08-15 21:11:48 +08:00
parent c8502151ed
commit aef5e54c3f
24 changed files with 483 additions and 172 deletions

View File

@ -52,6 +52,7 @@ class Document {
bool get hasRedo => _history.hasRedo;
Delta insert(int index, Object? data, {int replaceLength = 0}) {
print('insert $data at $index');
assert(index >= 0);
assert(data is String || data is Embeddable);
if (data is Embeddable) {
@ -67,7 +68,10 @@ class Document {
data: data,
length: replaceLength,
);
print('insert delta: $delta');
compose(delta, ChangeSource.LOCAL);
print('compose insert, current document $_delta');
return delta;
}
@ -76,6 +80,7 @@ class Document {
final delta = _rules.apply(RuleType.DELETE, this, index, length: length);
if (delta.isNotEmpty) {
compose(delta, ChangeSource.LOCAL);
print('compose delete, current document $_delta');
}
return delta;
}
@ -92,14 +97,17 @@ class Document {
// We have to insert before applying delete rules
// Otherwise delete would be operating on stale document snapshot.
if (dataIsNotEmpty) {
print('insert $data at $index, replace len: $length');
delta = insert(index, data, replaceLength: length);
}
if (length > 0) {
print('delete $length at $index, len: $length');
final deleteDelta = delete(index, length);
delta = delta.compose(deleteDelta);
}
print('replace result $delta');
return delta;
}
@ -117,6 +125,7 @@ class Document {
);
if (formatDelta.isNotEmpty) {
compose(formatDelta, ChangeSource.LOCAL);
print('compose format, current document $_delta');
delta = delta.compose(formatDelta);
}

View File

@ -44,7 +44,9 @@ class ResolveLineFormatRule extends FormatRule {
for (var lineBreak = text.indexOf('\n');
lineBreak >= 0;
lineBreak = text.indexOf('\n', offset)) {
tmp..retain(lineBreak - offset)..retain(1, attribute.toJson());
tmp
..retain(lineBreak - offset)
..retain(1, attribute.toJson());
offset = lineBreak + 1;
}
tmp.retain(text.length - offset);
@ -59,7 +61,9 @@ class ResolveLineFormatRule extends FormatRule {
delta.retain(op.length!);
continue;
}
delta..retain(lineBreak)..retain(1, attribute.toJson());
delta
..retain(lineBreak)
..retain(1, attribute.toJson());
break;
}
return delta;
@ -91,7 +95,9 @@ class FormatLinkAtCaretPositionRule extends FormatRule {
return null;
}
delta..retain(beg)..retain(retain!, attribute.toJson());
delta
..retain(beg)
..retain(retain!, attribute.toJson());
return delta;
}
}
@ -120,7 +126,9 @@ class ResolveInlineFormatRule extends FormatRule {
}
var pos = 0;
while (lineBreak >= 0) {
delta..retain(lineBreak - pos, attribute.toJson())..retain(1);
delta
..retain(lineBreak - pos, attribute.toJson())
..retain(1);
pos = lineBreak + 1;
lineBreak = text.indexOf('\n', pos);
}

View File

@ -159,7 +159,9 @@ class AutoExitBlockRule extends InsertRule {
.firstWhere((k) => Attribute.blockKeysExceptHeader.contains(k));
attributes[k] = null;
// retain(1) should be '\n', set it with no attribute
return Delta()..retain(index + (length ?? 0))..retain(1, attributes);
return Delta()
..retain(index + (length ?? 0))
..retain(1, attributes);
}
}
@ -261,10 +263,14 @@ class ForceNewlineForInsertsAroundEmbedRule extends InsertRule {
}
final delta = Delta()..retain(index + (length ?? 0));
if (cursorBeforeEmbed && !text.endsWith('\n')) {
return delta..insert(text)..insert('\n');
return delta
..insert(text)
..insert('\n');
}
if (cursorAfterEmbed && !text.startsWith('\n')) {
return delta..insert('\n')..insert(text);
return delta
..insert('\n')
..insert(text);
}
return delta..insert(text);
}

View File

@ -41,7 +41,7 @@ class Rules {
static final Rules _instance = Rules([
const FormatLinkAtCaretPositionRule(),
// const ResolveLineFormatRule(),
const ResolveLineFormatRule(),
const ResolveInlineFormatRule(),
// const InsertEmbedsRule(),
// const ForceNewlineForInsertsAroundEmbedRule(),
@ -70,6 +70,7 @@ class Rules {
final result = rule.apply(delta, index,
length: length, data: data, attribute: attribute);
if (result != null) {
print('apply rule: $rule, result: $result');
return result..trim();
}
} catch (e) {

View File

@ -34,6 +34,7 @@ impl Document {
let interval = Interval::new(index, index);
let _ = validate_interval(&self.delta, &interval)?;
let delta = self.view.insert(&self.delta, text, interval)?;
log::debug!("👉 receive change: {}", delta);
self.add_delta(&delta)?;
Ok(delta)
}
@ -43,6 +44,7 @@ impl Document {
debug_assert_eq!(interval.is_empty(), false);
let delete = self.view.delete(&self.delta, interval)?;
if !delete.is_empty() {
log::debug!("👉 receive change: {}", delete);
let _ = self.add_delta(&delete)?;
}
Ok(delete)
@ -56,6 +58,7 @@ impl Document {
.format(&self.delta, attribute.clone(), interval)
.unwrap();
log::debug!("👉 receive change: {}", format_delta);
self.add_delta(&format_delta)?;
Ok(())
}
@ -65,6 +68,7 @@ impl Document {
let mut delta = Delta::default();
if !text.is_empty() {
delta = self.view.insert(&self.delta, text, interval)?;
log::debug!("👉 receive change: {}", delta);
self.add_delta(&delta)?;
}
@ -121,7 +125,6 @@ impl Document {
impl Document {
fn add_delta(&mut self, delta: &Delta) -> Result<(), OTError> {
log::debug!("👉invert change {}", delta);
let composed_delta = self.delta.compose(delta)?;
let mut undo_delta = delta.invert(&self.delta);
self.rev_id_counter += 1;
@ -138,7 +141,7 @@ impl Document {
self.last_edit_time = now;
}
log::debug!("compose previous result: {}", undo_delta);
log::debug!("👉 receive change undo: {}", undo_delta);
if !undo_delta.is_empty() {
self.history.record(undo_delta);
}

View File

@ -5,6 +5,8 @@ use crate::{
pub struct DefaultDeleteExt {}
impl DeleteExt for DefaultDeleteExt {
fn ext_name(&self) -> &str { "DeleteExt" }
fn apply(&self, _delta: &Delta, interval: Interval) -> Option<Delta> {
Some(
DeltaBuilder::new()

View File

@ -5,13 +5,16 @@ pub type FormatExtension = Box<dyn FormatExt>;
pub type DeleteExtension = Box<dyn DeleteExt>;
pub trait InsertExt {
fn ext_name(&self) -> &str;
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta>;
}
pub trait FormatExt {
fn ext_name(&self) -> &str;
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta>;
}
pub trait DeleteExt {
fn ext_name(&self) -> &str;
fn apply(&self, delta: &Delta, interval: Interval) -> Option<Delta>;
}

View File

@ -1,5 +1,5 @@
use crate::{
client::view::{FormatExt, NEW_LINE},
client::view::{util::find_newline, FormatExt, NEW_LINE},
core::{
Attribute,
AttributeKey,
@ -17,6 +17,8 @@ use crate::{
pub struct FormatLinkAtCaretPositionExt {}
impl FormatExt for FormatLinkAtCaretPositionExt {
fn ext_name(&self) -> &str { "FormatLinkAtCaretPositionExt" }
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
if attribute.key != AttributeKey::Link || interval.size() != 0 {
return None;
@ -57,26 +59,56 @@ impl FormatExt for FormatLinkAtCaretPositionExt {
}
}
pub struct ResolveLineFormatExt {}
impl FormatExt for ResolveLineFormatExt {
pub struct ResolveBlockFormatExt {}
impl FormatExt for ResolveBlockFormatExt {
fn ext_name(&self) -> &str { "ResolveBlockFormatExt" }
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
if attribute.scope != AttributeScope::Block {
return None;
}
let mut new_delta = Delta::new();
new_delta.retain(interval.start, Attributes::default());
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(interval.start);
let mut start = 0;
let end = interval.size();
while start < end && iter.has_next() {
let next_op = iter.next_op_with_len(end - start).unwrap();
match find_newline(next_op.get_data()) {
None => new_delta.retain(next_op.length(), Attributes::empty()),
Some(_) => {
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Block);
new_delta.extend(tmp_delta);
},
}
None
start += next_op.length();
}
while iter.has_next() {
let op = iter
.next_op()
.expect("Unexpected None, iter.has_next() must return op");
match find_newline(op.get_data()) {
None => new_delta.retain(op.length(), Attributes::empty()),
Some(line_break) => {
debug_assert_eq!(line_break, 0);
new_delta.retain(1, attribute.clone().into());
break;
},
}
}
Some(new_delta)
}
}
pub struct ResolveInlineFormatExt {}
impl FormatExt for ResolveInlineFormatExt {
fn ext_name(&self) -> &str { "ResolveInlineFormatExt" }
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
if attribute.scope != AttributeScope::Inline {
return None;
@ -85,44 +117,57 @@ impl FormatExt for ResolveInlineFormatExt {
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(interval.start);
let mut cur = 0;
let len = interval.size();
let mut start = 0;
let end = interval.size();
while cur < len && iter.has_next() {
let some_op = iter.next_op_with_len(len - cur);
if some_op.is_none() {
return Some(new_delta);
}
let op = some_op.unwrap();
if let Operation::Insert(insert) = &op {
let mut s = insert.s.as_str();
match s.find(NEW_LINE) {
None => {
new_delta.retain(op.length(), attribute.clone().into());
},
Some(line_break) => {
let mut pos = 0;
let mut some_line_break = Some(line_break);
while some_line_break.is_some() {
let line_break = some_line_break.unwrap();
new_delta.retain(line_break - pos, attribute.clone().into());
new_delta.retain(1, Attributes::default());
pos = line_break + 1;
s = &s[pos..s.len()];
some_line_break = s.find(NEW_LINE);
}
if pos < op.length() {
new_delta.retain(op.length() - pos, attribute.clone().into());
}
},
}
while start < end && iter.has_next() {
let next_op = iter.next_op_with_len(end - start).unwrap();
match find_newline(next_op.get_data()) {
None => new_delta.retain(next_op.length(), attribute.clone().into()),
Some(_) => {
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Inline);
new_delta.extend(tmp_delta);
},
}
cur += op.length();
start += next_op.length();
}
Some(new_delta)
}
}
fn line_break(op: &Operation, attribute: &Attribute, scope: AttributeScope) -> Delta {
let mut new_delta = Delta::new();
let mut start = 0;
let end = op.length();
let mut s = op.get_data();
while let Some(line_break) = find_newline(s) {
match scope {
AttributeScope::Inline => {
new_delta.retain(line_break - start, attribute.clone().into());
new_delta.retain(1, Attributes::empty());
},
AttributeScope::Block => {
new_delta.retain(line_break - start, Attributes::empty());
new_delta.retain(1, attribute.clone().into());
},
_ => {
log::error!("Unsupported parser line break for {:?}", scope);
},
}
start = line_break + 1;
s = &s[start..s.len()];
}
if start < end {
match scope {
AttributeScope::Inline => new_delta.retain(end - start, attribute.clone().into()),
AttributeScope::Block => new_delta.retain(end - start, Attributes::empty()),
_ => log::error!("Unsupported parser line break for {:?}", scope),
}
}
new_delta
}

View File

@ -1,12 +1,14 @@
use crate::{
client::view::InsertExt,
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter},
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter, Operation},
};
pub const NEW_LINE: &'static str = "\n";
pub struct PreserveBlockStyleOnInsertExt {}
impl InsertExt for PreserveBlockStyleOnInsertExt {
fn ext_name(&self) -> &str { "PreserveBlockStyleOnInsertExt" }
fn apply(
&self,
_delta: &Delta,
@ -20,6 +22,8 @@ impl InsertExt for PreserveBlockStyleOnInsertExt {
pub struct PreserveLineStyleOnSplitExt {}
impl InsertExt for PreserveLineStyleOnSplitExt {
fn ext_name(&self) -> &str { "PreserveLineStyleOnSplitExt" }
fn apply(
&self,
_delta: &Delta,
@ -34,6 +38,8 @@ impl InsertExt for PreserveLineStyleOnSplitExt {
pub struct AutoExitBlockExt {}
impl InsertExt for AutoExitBlockExt {
fn ext_name(&self) -> &str { "AutoExitBlockExt" }
fn apply(
&self,
_delta: &Delta,
@ -47,6 +53,8 @@ impl InsertExt for AutoExitBlockExt {
pub struct InsertEmbedsExt {}
impl InsertExt for InsertEmbedsExt {
fn ext_name(&self) -> &str { "InsertEmbedsExt" }
fn apply(
&self,
_delta: &Delta,
@ -60,6 +68,8 @@ impl InsertExt for InsertEmbedsExt {
pub struct ForceNewlineForInsertsAroundEmbedExt {}
impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
fn ext_name(&self) -> &str { "ForceNewlineForInsertsAroundEmbedExt" }
fn apply(
&self,
_delta: &Delta,
@ -73,6 +83,8 @@ impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
pub struct AutoFormatLinksExt {}
impl InsertExt for AutoFormatLinksExt {
fn ext_name(&self) -> &str { "AutoFormatLinksExt" }
fn apply(
&self,
_delta: &Delta,
@ -86,8 +98,10 @@ impl InsertExt for AutoFormatLinksExt {
pub struct PreserveInlineStylesExt {}
impl InsertExt for PreserveInlineStylesExt {
fn ext_name(&self) -> &str { "PreserveInlineStylesExt" }
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
if text.ends_with(NEW_LINE) {
if text.contains(NEW_LINE) {
return None;
}
@ -119,6 +133,8 @@ impl InsertExt for PreserveInlineStylesExt {
pub struct ResetLineFormatOnNewLineExt {}
impl InsertExt for ResetLineFormatOnNewLineExt {
fn ext_name(&self) -> &str { "ResetLineFormatOnNewLineExt" }
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
if text != NEW_LINE {
return None;
@ -133,7 +149,7 @@ impl InsertExt for ResetLineFormatOnNewLineExt {
let mut reset_attribute = Attributes::new();
if next_op.get_attributes().contains_key(&AttributeKey::Header) {
reset_attribute.add(AttributeKey::Header.with_value(""));
reset_attribute.add(AttributeKey::Header.value(""));
}
let len = index + replace_len;
@ -150,11 +166,27 @@ impl InsertExt for ResetLineFormatOnNewLineExt {
pub struct DefaultInsertExt {}
impl InsertExt for DefaultInsertExt {
fn apply(&self, _delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
fn ext_name(&self) -> &str { "DefaultInsertExt" }
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
let mut iter = DeltaIter::new(delta);
let mut attributes = Attributes::new();
if text.ends_with(NEW_LINE) {
match iter.last() {
None => {},
Some(op) => {
if op.get_attributes().contains_key(&AttributeKey::Header) {
attributes.extend(op.get_attributes());
}
},
}
}
Some(
DeltaBuilder::new()
.retain(index + replace_len)
.insert(text)
.insert_with_attributes(text, attributes)
.build(),
)
}

View File

@ -2,6 +2,7 @@ mod delete_ext;
mod extension;
mod format_ext;
mod insert_ext;
mod util;
mod view;
pub use delete_ext::*;

View File

@ -0,0 +1,8 @@
use crate::client::view::NEW_LINE;
pub fn find_newline(s: &str) -> Option<usize> {
match s.find(NEW_LINE) {
None => None,
Some(line_break) => Some(line_break),
}
}

View File

@ -1,6 +1,6 @@
use crate::{
client::view::*,
core::{Attribute, Delta, Interval},
core::{Attribute, Delta, Interval, Operation},
errors::{ErrorBuilder, OTError, OTErrorCode},
};
@ -28,6 +28,7 @@ impl View {
let mut new_delta = None;
for ext in &self.insert_exts {
if let Some(delta) = ext.apply(delta, interval.size(), text, interval.start) {
log::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
new_delta = Some(delta);
break;
}
@ -43,6 +44,7 @@ impl View {
let mut new_delta = None;
for ext in &self.delete_exts {
if let Some(delta) = ext.apply(delta, interval) {
log::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
new_delta = Some(delta);
break;
}
@ -63,6 +65,7 @@ impl View {
let mut new_delta = None;
for ext in &self.format_exts {
if let Some(delta) = ext.apply(delta, interval, &attribute) {
log::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
new_delta = Some(delta);
break;
}
@ -92,7 +95,7 @@ fn construct_insert_exts() -> Vec<InsertExtension> {
fn construct_format_exts() -> Vec<FormatExtension> {
vec![
Box::new(FormatLinkAtCaretPositionExt {}),
Box::new(ResolveLineFormatExt {}),
Box::new(ResolveBlockFormatExt {}),
Box::new(ResolveInlineFormatExt {}),
]
}

View File

@ -1,14 +1,14 @@
use crate::core::{Attribute, AttributeKey, Operation};
use crate::core::{Attribute, AttributeKey, AttributeValue, Operation};
use std::{collections::HashMap, fmt};
pub const REMOVE_FLAG: &'static str = "";
pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG }
pub(crate) fn should_remove(val: &AttributeValue) -> bool { val.0 == REMOVE_FLAG }
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Attributes {
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(flatten)]
pub(crate) inner: HashMap<AttributeKey, String>,
pub(crate) inner: HashMap<AttributeKey, AttributeValue>,
}
impl fmt::Display for Attributes {
@ -38,7 +38,8 @@ impl Attributes {
}
pub fn remove(&mut self, key: &AttributeKey) {
self.inner.insert(key.clone(), REMOVE_FLAG.to_owned());
let value: AttributeValue = REMOVE_FLAG.into();
self.inner.insert(key.clone(), value);
}
// Remove the key if its value is empty. e.g. { bold: "" }
@ -62,7 +63,7 @@ impl Attributes {
}
impl std::ops::Deref for Attributes {
type Target = HashMap<AttributeKey, String>;
type Target = HashMap<AttributeKey, AttributeValue>;
fn deref(&self) -> &Self::Target { &self.inner }
}

View File

@ -0,0 +1,39 @@
use crate::core::AttributeValue;
use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
impl Serialize for AttributeValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for AttributeValue {
fn deserialize<D>(deserializer: D) -> Result<AttributeValue, D::Error>
where
D: Deserializer<'de>,
{
struct OperationSeqVisitor;
impl<'de> Visitor<'de> for OperationSeqVisitor {
type Value = AttributeValue;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let attribute_value = AttributeValue(s.to_owned());
Ok(attribute_value)
}
}
deserializer.deserialize_str(OperationSeqVisitor)
}
}

View File

@ -2,6 +2,13 @@ use crate::core::{Attributes, REMOVE_FLAG};
use derive_more::Display;
use std::{fmt, fmt::Formatter};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AttributeValue(pub(crate) String);
impl AsRef<str> for AttributeValue {
fn as_ref(&self) -> &str { &self.0 }
}
#[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AttributeKey {
@ -23,8 +30,6 @@ pub enum AttributeKey {
Color,
#[display(fmt = "background")]
Background,
#[display(fmt = "header")]
Header,
#[display(fmt = "ident")]
Ident,
#[display(fmt = "align")]
@ -41,18 +46,8 @@ pub enum AttributeKey {
Height,
#[display(fmt = "style")]
Style,
#[display(fmt = "h1")]
H1,
#[display(fmt = "h2")]
H2,
#[display(fmt = "h3")]
H3,
#[display(fmt = "h4")]
H4,
#[display(fmt = "h5")]
H5,
#[display(fmt = "h6")]
H6,
#[display(fmt = "header")]
Header,
#[display(fmt = "left")]
LeftAlignment,
#[display(fmt = "center")]
@ -82,13 +77,13 @@ pub enum AttributeScope {
#[derive(Debug, Clone)]
pub struct Attribute {
pub key: AttributeKey,
pub value: String,
pub value: AttributeValue,
pub scope: AttributeScope,
}
impl fmt::Display for Attribute {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let s = format!("{:?}:{} {:?}", self.key, self.value, self.scope);
let s = format!("{:?}:{} {:?}", self.key, self.value.as_ref(), self.scope);
f.write_str(&s)
}
}
@ -129,13 +124,13 @@ impl AttrsBuilder {
self
}
pub fn insert<T: Into<String>>(mut self, key: AttributeKey, value: T) -> Self {
self.inner.add(key.with_value(value));
pub fn insert<T: Into<AttributeValue>>(mut self, key: AttributeKey, value: T) -> Self {
self.inner.add(key.value(value));
self
}
pub fn remove<T: Into<String>>(mut self, key: AttributeKey) -> Self {
self.inner.add(key.with_value(REMOVE_FLAG));
self.inner.add(key.value(REMOVE_FLAG));
self
}
@ -148,9 +143,11 @@ impl AttrsBuilder {
}
impl AttributeKey {
pub fn with_value<T: Into<String>>(&self, value: T) -> Attribute {
pub fn remove(&self) -> Attribute { self.value(REMOVE_FLAG) }
pub fn value<T: Into<AttributeValue>>(&self, value: T) -> Attribute {
let key = self.clone();
let value: String = value.into();
let value: AttributeValue = value.into();
match self {
AttributeKey::Bold
| AttributeKey::Italic
@ -167,12 +164,6 @@ impl AttributeKey {
},
AttributeKey::Header
| AttributeKey::H1
| AttributeKey::H2
| AttributeKey::H3
| AttributeKey::H4
| AttributeKey::H5
| AttributeKey::H6
| AttributeKey::LeftAlignment
| AttributeKey::CenterAlignment
| AttributeKey::RightAlignment
@ -199,3 +190,21 @@ impl AttributeKey {
}
}
}
impl std::convert::From<&usize> for AttributeValue {
fn from(val: &usize) -> Self { AttributeValue(format!("{}", val)) }
}
impl std::convert::From<&str> for AttributeValue {
fn from(val: &str) -> Self { AttributeValue(val.to_owned()) }
}
impl std::convert::From<bool> for AttributeValue {
fn from(val: bool) -> Self {
let val = match val {
true => "true",
false => "",
};
AttributeValue(val.to_owned())
}
}

View File

@ -1,4 +1,5 @@
mod attributes;
mod attributes_serde;
mod builder;
pub use attributes::*;

View File

@ -42,11 +42,16 @@ impl<'a> Cursor<'a> {
next_op = find_next_op(self);
}
let old_c_index = self.c_index;
while find_op.is_none() && next_op.is_some() {
let op = next_op.take().unwrap();
let interval = self.next_op_interval_with_constraint(force_len);
find_op = op.shrink(interval);
if interval.is_empty() {
self.next_op = Some(op.clone());
break;
}
find_op = op.shrink(interval);
let suffix = Interval::new(0, op.length()).suffix(interval);
if !suffix.is_empty() {
self.next_op = op.shrink(suffix);
@ -60,7 +65,15 @@ impl<'a> Cursor<'a> {
}
}
find_op
if find_op.is_some() {
let last = self.c_index - old_c_index;
let force_len = force_len.unwrap_or(0);
if force_len > last {
let len = force_len - last;
return self.next_op_with_len(Some(len));
}
}
return find_op;
}
pub fn next_op(&mut self) -> Option<Operation> { self.next_op_with_len(None) }
@ -159,6 +172,7 @@ impl Metric for CharMetric {
fn seek(cursor: &mut Cursor, index: usize) -> SeekResult {
let _ = check_bound(cursor.c_index, index)?;
let _ = cursor.next_op_with_len(Some(index));
Ok(())
}
}

View File

@ -599,6 +599,8 @@ impl Delta {
pub fn is_empty(&self) -> bool { self.ops.is_empty() }
pub fn to_json(&self) -> String { serde_json::to_string(self).unwrap_or("".to_owned()) }
pub fn extend(&mut self, other: Self) { other.ops.into_iter().for_each(|op| self.add(op)); }
}
fn invert_from_other(

View File

@ -0,0 +1,53 @@
use crate::core::Delta;
use serde::{
de::{SeqAccess, Visitor},
ser::SerializeSeq,
Deserialize,
Deserializer,
Serialize,
Serializer,
};
use std::fmt;
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)
}
}

View File

@ -1,6 +1,7 @@
mod builder;
mod cursor;
mod delta;
mod delta_serde;
mod iterator;
pub use builder::*;

View File

@ -92,46 +92,3 @@ impl<'de> Deserialize<'de> for 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)
}
}

View File

@ -4,7 +4,7 @@ use crate::helper::{TestOp::*, *};
use flowy_ot::core::Interval;
#[test]
fn delta_insert_text() {
fn attributes_insert_text() {
let ops = vec![
Insert(0, "123", 0),
Insert(0, "456", 3),
@ -14,7 +14,7 @@ fn delta_insert_text() {
}
#[test]
fn delta_insert_text_at_head() {
fn attributes_insert_text_at_head() {
let ops = vec![
Insert(0, "123", 0),
Insert(0, "456", 0),
@ -24,7 +24,7 @@ fn delta_insert_text_at_head() {
}
#[test]
fn delta_insert_text_at_middle() {
fn attributes_insert_text_at_middle() {
let ops = vec![
Insert(0, "123", 0),
Insert(0, "456", 1),
@ -34,7 +34,7 @@ fn delta_insert_text_at_middle() {
}
#[test]
fn delta_insert_text_with_attr() {
fn attributes_insert_text_with_attr() {
let ops = vec![
Insert(0, "145", 0),
Insert(0, "23", 1),
@ -53,7 +53,7 @@ fn delta_insert_text_with_attr() {
}
#[test]
fn delta_add_bold() {
fn attributes_add_bold() {
let ops = vec![
Insert(0, "123456", 0),
Bold(0, Interval::new(3, 5), true),
@ -70,7 +70,7 @@ fn delta_add_bold() {
}
#[test]
fn delta_add_bold_and_invert_all() {
fn attributes_add_bold_and_invert_all() {
let ops = vec![
Insert(0, "123", 0),
Bold(0, Interval::new(0, 3), true),
@ -82,7 +82,7 @@ fn delta_add_bold_and_invert_all() {
}
#[test]
fn delta_add_bold_and_invert_partial_suffix() {
fn attributes_add_bold_and_invert_partial_suffix() {
let ops = vec![
Insert(0, "1234", 0),
Bold(0, Interval::new(0, 4), true),
@ -97,7 +97,7 @@ fn delta_add_bold_and_invert_partial_suffix() {
}
#[test]
fn delta_add_bold_and_invert_partial_suffix2() {
fn attributes_add_bold_and_invert_partial_suffix2() {
let ops = vec![
Insert(0, "1234", 0),
Bold(0, Interval::new(0, 4), true),
@ -114,7 +114,35 @@ fn delta_add_bold_and_invert_partial_suffix2() {
}
#[test]
fn delta_add_bold_and_invert_partial_prefix() {
fn attributes_add_bold_with_new_line() {
let ops = vec![
Insert(0, "123456", 0),
Bold(0, Interval::new(0, 6), true),
AssertOpsJson(
0,
r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
),
Insert(0, "\n", 3),
AssertOpsJson(
0,
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"},{"insert":"456","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
),
Insert(0, "\n", 4),
AssertOpsJson(
0,
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n\n"},{"insert":"456","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
),
Insert(0, "a", 4),
AssertOpsJson(
0,
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\na\n"},{"insert":"456","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn attributes_add_bold_and_invert_partial_prefix() {
let ops = vec![
Insert(0, "1234", 0),
Bold(0, Interval::new(0, 4), true),
@ -129,7 +157,7 @@ fn delta_add_bold_and_invert_partial_prefix() {
}
#[test]
fn delta_add_bold_consecutive() {
fn attributes_add_bold_consecutive() {
let ops = vec![
Insert(0, "1234", 0),
Bold(0, Interval::new(0, 1), true),
@ -147,31 +175,26 @@ fn delta_add_bold_consecutive() {
}
#[test]
fn delta_add_bold_italic() {
fn attributes_add_bold_italic() {
let ops = vec![
Insert(0, "1234", 0),
Bold(0, Interval::new(0, 4), true),
Italic(0, Interval::new(0, 4), true),
AssertOpsJson(
0,
r#"[{"insert":"1234","attributes":{"italic":"true","bold":"true"}}]"#,
r#"[{"insert":"1234","attributes":{"italic":"true","bold":"true"}},{"insert":"\n"}]"#,
),
Insert(0, "5678", 4),
AssertOpsJson(
0,
r#"[{"insert":"12345678","attributes":{"italic":"true","bold":"true"}}]"#,
),
Italic(0, Interval::new(4, 6), false),
AssertOpsJson(
0,
r#"[{"insert":"1234","attributes":{"bold":"true","italic":"true"}},{"insert":"56","attributes":{"bold":"true"}},{"insert":"78","attributes":{"bold":"true","italic":"true"}}]"#,
r#"[{"insert":"12345678","attributes":{"bold":"true","italic":"true"}},{"insert":"\n"}]"#,
),
];
OpTester::new().run_script(ops);
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn delta_add_bold_italic2() {
fn attributes_add_bold_italic2() {
let ops = vec![
Insert(0, "123456", 0),
Bold(0, Interval::new(0, 6), true),
@ -199,7 +222,7 @@ fn delta_add_bold_italic2() {
}
#[test]
fn delta_add_bold_italic3() {
fn attributes_add_bold_italic3() {
let ops = vec![
Insert(0, "123456789", 0),
Bold(0, Interval::new(0, 5), true),
@ -236,7 +259,7 @@ fn delta_add_bold_italic3() {
}
#[test]
fn delta_add_bold_italic_delete() {
fn attributes_add_bold_italic_delete() {
let ops = vec![
Insert(0, "123456789", 0),
Bold(0, Interval::new(0, 5), true),
@ -275,7 +298,7 @@ fn delta_add_bold_italic_delete() {
}
#[test]
fn delta_merge_inserted_text_with_same_attribute() {
fn attributes_merge_inserted_text_with_same_attribute() {
let ops = vec![
InsertBold(0, "123", Interval::new(0, 3)),
AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
@ -286,7 +309,7 @@ fn delta_merge_inserted_text_with_same_attribute() {
}
#[test]
fn delta_compose_attr_delta_with_attr_delta_test() {
fn attributes_compose_attr_attributes_with_attr_attributes_test() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@ -301,7 +324,7 @@ fn delta_compose_attr_delta_with_attr_delta_test() {
}
#[test]
fn delta_compose_attr_delta_with_attr_delta_test2() {
fn attributes_compose_attr_attributes_with_attr_attributes_test2() {
let ops = vec![
Insert(0, "123456", 0),
Bold(0, Interval::new(0, 6), true),
@ -342,7 +365,7 @@ fn delta_compose_attr_delta_with_attr_delta_test2() {
}
#[test]
fn delta_compose_attr_delta_with_no_attr_delta_test() {
fn attributes_compose_attr_attributes_with_no_attr_attributes_test() {
let expected = r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"7"}]"#;
let ops = vec![
@ -358,7 +381,7 @@ fn delta_compose_attr_delta_with_no_attr_delta_test() {
}
#[test]
fn delta_replace_heading() {
fn attributes_replace_heading() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@ -370,7 +393,7 @@ fn delta_replace_heading() {
}
#[test]
fn delta_replace_trailing() {
fn attributes_replace_trailing() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@ -382,7 +405,7 @@ fn delta_replace_trailing() {
}
#[test]
fn delta_replace_middle() {
fn attributes_replace_middle() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@ -396,7 +419,7 @@ fn delta_replace_middle() {
}
#[test]
fn delta_replace_all() {
fn attributes_replace_all() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@ -408,7 +431,7 @@ fn delta_replace_all() {
}
#[test]
fn delta_replace_with_text() {
fn attributes_replace_with_text() {
let ops = vec![
InsertBold(0, "123456", Interval::new(0, 6)),
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
@ -421,3 +444,62 @@ fn delta_replace_with_text() {
OpTester::new().run_script(ops);
}
#[test]
fn attributes_add_header() {
let ops = vec![
Insert(0, "123456", 0),
Header(0, Interval::new(0, 6), 1, true),
AssertOpsJson(
0,
r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":"1"}}]"#,
),
Insert(0, "\n", 3),
AssertOpsJson(
0,
r#"[{"insert":"123"},{"insert":"\n","attributes":{"header":"1"}},{"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn attributes_header_add_newline() {
let ops = vec![
Insert(0, "123456", 0),
Header(0, Interval::new(0, 6), 1, true),
Insert(0, "\n", 6),
AssertOpsJson(
0,
r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":"1"}},{"insert":"\n"}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn attributes_header_add_newline_2() {
let ops = vec![
Insert(0, "123456", 0),
Header(0, Interval::new(0, 6), 1, true),
Insert(0, "\n", 3),
AssertOpsJson(
0,
r#"[{"insert":"123"},{"insert":"\n","attributes":{"header":"1"}},{"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]"#,
),
Insert(0, "\n", 4),
AssertOpsJson(
0,
r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":"1"}},{"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]"#,
),
Insert(0, "\n", 4),
AssertOpsJson(
0,
r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":"1"}},{"insert":"\n456"},{"insert":"\n","attributes":{"header":"1"}}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}

View File

@ -28,6 +28,9 @@ pub enum TestOp {
#[display(fmt = "Italic")]
Italic(usize, Interval, bool),
#[display(fmt = "Header")]
Header(usize, Interval, usize, bool),
#[display(fmt = "Transform")]
Transform(usize, usize),
@ -60,7 +63,7 @@ impl OpTester {
static INIT: Once = Once::new();
INIT.call_once(|| {
color_eyre::install().unwrap();
std::env::set_var("RUST_LOG", "info");
std::env::set_var("RUST_LOG", "debug");
env_logger::init();
});
@ -86,22 +89,30 @@ impl OpTester {
let document = &mut self.documents[*delta_i];
document.insert(interval.start, s).unwrap();
document
.format(*interval, AttributeKey::Bold.with_value("true".to_owned()))
.format(*interval, AttributeKey::Bold.value(true))
.unwrap();
},
TestOp::Bold(delta_i, interval, enable) => {
let document = &mut self.documents[*delta_i];
let attribute = match *enable {
true => AttributeKey::Bold.with_value("true".to_owned()),
false => AttributeKey::Bold.with_value("".to_owned()),
true => AttributeKey::Bold.value(true),
false => AttributeKey::Bold.remove(),
};
document.format(*interval, attribute).unwrap();
},
TestOp::Italic(delta_i, interval, enable) => {
let document = &mut self.documents[*delta_i];
let attribute = match *enable {
true => AttributeKey::Italic.with_value("true"),
false => AttributeKey::Italic.with_value(REMOVE_FLAG),
true => AttributeKey::Italic.value("true"),
false => AttributeKey::Italic.remove(),
};
document.format(*interval, attribute).unwrap();
},
TestOp::Header(delta_i, interval, level, enable) => {
let document = &mut self.documents[*delta_i];
let attribute = match *enable {
true => AttributeKey::Header.value(level),
false => AttributeKey::Header.remove(),
};
document.format(*interval, attribute).unwrap();
},

View File

@ -223,6 +223,26 @@ fn delta_seek_4() {
);
}
#[test]
fn delta_seek_5() {
let mut delta = Delta::default();
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
delta.add(
OpBuilder::insert("1234")
.attributes(attributes.clone())
.build(),
);
delta.add(OpBuilder::insert("\n").build());
let mut iter = DeltaIter::new(&delta);
iter.seek::<CharMetric>(0);
assert_eq!(
iter.next_op_with_len(4).unwrap(),
OpBuilder::insert("1234").attributes(attributes).build(),
);
}
#[test]
fn delta_next_op_len_test() {
let mut delta = Delta::default();