From aef5e54c3f12a83e18d636fe64492a175d6289ff Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 15 Aug 2021 21:11:48 +0800 Subject: [PATCH] config header attribute & add test --- .../lib/src/model/document/document.dart | 9 ++ .../lib/src/model/heuristic/format.dart | 16 +- .../lib/src/model/heuristic/insert.dart | 12 +- .../lib/src/model/heuristic/rule.dart | 3 +- rust-lib/flowy-ot/src/client/document.rs | 7 +- .../flowy-ot/src/client/view/delete_ext.rs | 2 + .../flowy-ot/src/client/view/extension.rs | 3 + .../flowy-ot/src/client/view/format_ext.rs | 127 ++++++++++----- .../flowy-ot/src/client/view/insert_ext.rs | 42 ++++- rust-lib/flowy-ot/src/client/view/mod.rs | 1 + rust-lib/flowy-ot/src/client/view/util.rs | 8 + rust-lib/flowy-ot/src/client/view/view.rs | 7 +- .../src/core/attributes/attributes.rs | 11 +- .../src/core/attributes/attributes_serde.rs | 39 +++++ .../flowy-ot/src/core/attributes/builder.rs | 63 ++++---- rust-lib/flowy-ot/src/core/attributes/mod.rs | 1 + rust-lib/flowy-ot/src/core/delta/cursor.rs | 18 ++- rust-lib/flowy-ot/src/core/delta/delta.rs | 2 + .../flowy-ot/src/core/delta/delta_serde.rs | 53 +++++++ rust-lib/flowy-ot/src/core/delta/mod.rs | 1 + .../src/core/operation/operation_serde.rs | 43 ------ rust-lib/flowy-ot/tests/attribute_test.rs | 144 ++++++++++++++---- rust-lib/flowy-ot/tests/helper/mod.rs | 23 ++- rust-lib/flowy-ot/tests/op_test.rs | 20 +++ 24 files changed, 483 insertions(+), 172 deletions(-) create mode 100644 rust-lib/flowy-ot/src/client/view/util.rs create mode 100644 rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs create mode 100644 rust-lib/flowy-ot/src/core/delta/delta_serde.rs diff --git a/app_flowy/packages/flowy_editor/lib/src/model/document/document.dart b/app_flowy/packages/flowy_editor/lib/src/model/document/document.dart index 274f3959d4..8eb5f7a964 100644 --- a/app_flowy/packages/flowy_editor/lib/src/model/document/document.dart +++ b/app_flowy/packages/flowy_editor/lib/src/model/document/document.dart @@ -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); } diff --git a/app_flowy/packages/flowy_editor/lib/src/model/heuristic/format.dart b/app_flowy/packages/flowy_editor/lib/src/model/heuristic/format.dart index 8f83328b04..419889bdcb 100644 --- a/app_flowy/packages/flowy_editor/lib/src/model/heuristic/format.dart +++ b/app_flowy/packages/flowy_editor/lib/src/model/heuristic/format.dart @@ -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); } diff --git a/app_flowy/packages/flowy_editor/lib/src/model/heuristic/insert.dart b/app_flowy/packages/flowy_editor/lib/src/model/heuristic/insert.dart index eb30fb6c2e..a70f281bab 100644 --- a/app_flowy/packages/flowy_editor/lib/src/model/heuristic/insert.dart +++ b/app_flowy/packages/flowy_editor/lib/src/model/heuristic/insert.dart @@ -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); } diff --git a/app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart b/app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart index 9a90540b1a..2158b6a53e 100644 --- a/app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart +++ b/app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart @@ -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) { diff --git a/rust-lib/flowy-ot/src/client/document.rs b/rust-lib/flowy-ot/src/client/document.rs index 2f959f8f23..b98d0919b6 100644 --- a/rust-lib/flowy-ot/src/client/document.rs +++ b/rust-lib/flowy-ot/src/client/document.rs @@ -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); } diff --git a/rust-lib/flowy-ot/src/client/view/delete_ext.rs b/rust-lib/flowy-ot/src/client/view/delete_ext.rs index 6557b0e810..9cdb8c5cc5 100644 --- a/rust-lib/flowy-ot/src/client/view/delete_ext.rs +++ b/rust-lib/flowy-ot/src/client/view/delete_ext.rs @@ -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 { Some( DeltaBuilder::new() diff --git a/rust-lib/flowy-ot/src/client/view/extension.rs b/rust-lib/flowy-ot/src/client/view/extension.rs index e541677882..de1ad3dbd0 100644 --- a/rust-lib/flowy-ot/src/client/view/extension.rs +++ b/rust-lib/flowy-ot/src/client/view/extension.rs @@ -5,13 +5,16 @@ pub type FormatExtension = Box; pub type DeleteExtension = Box; pub trait InsertExt { + fn ext_name(&self) -> &str; fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option; } pub trait FormatExt { + fn ext_name(&self) -> &str; fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option; } pub trait DeleteExt { + fn ext_name(&self) -> &str; fn apply(&self, delta: &Delta, interval: Interval) -> Option; } diff --git a/rust-lib/flowy-ot/src/client/view/format_ext.rs b/rust-lib/flowy-ot/src/client/view/format_ext.rs index f3d8acfbcb..6812dc9b99 100644 --- a/rust-lib/flowy-ot/src/client/view/format_ext.rs +++ b/rust-lib/flowy-ot/src/client/view/format_ext.rs @@ -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 { 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 { 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::(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 { if attribute.scope != AttributeScope::Inline { return None; @@ -85,44 +117,57 @@ impl FormatExt for ResolveInlineFormatExt { let mut iter = DeltaIter::new(delta); iter.seek::(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 +} diff --git a/rust-lib/flowy-ot/src/client/view/insert_ext.rs b/rust-lib/flowy-ot/src/client/view/insert_ext.rs index 6160b84d04..3bc57972f9 100644 --- a/rust-lib/flowy-ot/src/client/view/insert_ext.rs +++ b/rust-lib/flowy-ot/src/client/view/insert_ext.rs @@ -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 { - 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 { 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 { + fn ext_name(&self) -> &str { "DefaultInsertExt" } + + fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option { + 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(), ) } diff --git a/rust-lib/flowy-ot/src/client/view/mod.rs b/rust-lib/flowy-ot/src/client/view/mod.rs index 005149a62e..0dbad27ebc 100644 --- a/rust-lib/flowy-ot/src/client/view/mod.rs +++ b/rust-lib/flowy-ot/src/client/view/mod.rs @@ -2,6 +2,7 @@ mod delete_ext; mod extension; mod format_ext; mod insert_ext; +mod util; mod view; pub use delete_ext::*; diff --git a/rust-lib/flowy-ot/src/client/view/util.rs b/rust-lib/flowy-ot/src/client/view/util.rs new file mode 100644 index 0000000000..86f99ec162 --- /dev/null +++ b/rust-lib/flowy-ot/src/client/view/util.rs @@ -0,0 +1,8 @@ +use crate::client::view::NEW_LINE; + +pub fn find_newline(s: &str) -> Option { + match s.find(NEW_LINE) { + None => None, + Some(line_break) => Some(line_break), + } +} diff --git a/rust-lib/flowy-ot/src/client/view/view.rs b/rust-lib/flowy-ot/src/client/view/view.rs index dd96985f8e..090a82249f 100644 --- a/rust-lib/flowy-ot/src/client/view/view.rs +++ b/rust-lib/flowy-ot/src/client/view/view.rs @@ -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 { fn construct_format_exts() -> Vec { vec![ Box::new(FormatLinkAtCaretPositionExt {}), - Box::new(ResolveLineFormatExt {}), + Box::new(ResolveBlockFormatExt {}), Box::new(ResolveInlineFormatExt {}), ] } diff --git a/rust-lib/flowy-ot/src/core/attributes/attributes.rs b/rust-lib/flowy-ot/src/core/attributes/attributes.rs index 86f9c2bf58..e25b6ec910 100644 --- a/rust-lib/flowy-ot/src/core/attributes/attributes.rs +++ b/rust-lib/flowy-ot/src/core/attributes/attributes.rs @@ -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, + pub(crate) inner: HashMap, } 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; + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.inner } } diff --git a/rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs b/rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs new file mode 100644 index 0000000000..55b28f0019 --- /dev/null +++ b/rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs @@ -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(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.0) + } +} + +impl<'de> Deserialize<'de> for AttributeValue { + fn deserialize(deserializer: D) -> Result + 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(self, s: &str) -> Result + where + E: de::Error, + { + let attribute_value = AttributeValue(s.to_owned()); + Ok(attribute_value) + } + } + + deserializer.deserialize_str(OperationSeqVisitor) + } +} diff --git a/rust-lib/flowy-ot/src/core/attributes/builder.rs b/rust-lib/flowy-ot/src/core/attributes/builder.rs index e97328c745..275d4c9d40 100644 --- a/rust-lib/flowy-ot/src/core/attributes/builder.rs +++ b/rust-lib/flowy-ot/src/core/attributes/builder.rs @@ -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 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>(mut self, key: AttributeKey, value: T) -> Self { - self.inner.add(key.with_value(value)); + pub fn insert>(mut self, key: AttributeKey, value: T) -> Self { + self.inner.add(key.value(value)); self } pub fn remove>(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>(&self, value: T) -> Attribute { + pub fn remove(&self) -> Attribute { self.value(REMOVE_FLAG) } + + pub fn value>(&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 for AttributeValue { + fn from(val: bool) -> Self { + let val = match val { + true => "true", + false => "", + }; + AttributeValue(val.to_owned()) + } +} diff --git a/rust-lib/flowy-ot/src/core/attributes/mod.rs b/rust-lib/flowy-ot/src/core/attributes/mod.rs index a87e8d00d4..1855d0e8be 100644 --- a/rust-lib/flowy-ot/src/core/attributes/mod.rs +++ b/rust-lib/flowy-ot/src/core/attributes/mod.rs @@ -1,4 +1,5 @@ mod attributes; +mod attributes_serde; mod builder; pub use attributes::*; diff --git a/rust-lib/flowy-ot/src/core/delta/cursor.rs b/rust-lib/flowy-ot/src/core/delta/cursor.rs index 6e12e61bde..b2e8e76262 100644 --- a/rust-lib/flowy-ot/src/core/delta/cursor.rs +++ b/rust-lib/flowy-ot/src/core/delta/cursor.rs @@ -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 { 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(()) } } diff --git a/rust-lib/flowy-ot/src/core/delta/delta.rs b/rust-lib/flowy-ot/src/core/delta/delta.rs index d5685d9b3b..43b8f66f7f 100644 --- a/rust-lib/flowy-ot/src/core/delta/delta.rs +++ b/rust-lib/flowy-ot/src/core/delta/delta.rs @@ -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( diff --git a/rust-lib/flowy-ot/src/core/delta/delta_serde.rs b/rust-lib/flowy-ot/src/core/delta/delta_serde.rs new file mode 100644 index 0000000000..ea085c2d69 --- /dev/null +++ b/rust-lib/flowy-ot/src/core/delta/delta_serde.rs @@ -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(&self, serializer: S) -> Result + 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(deserializer: D) -> Result + 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(self, mut seq: A) -> Result + 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) + } +} diff --git a/rust-lib/flowy-ot/src/core/delta/mod.rs b/rust-lib/flowy-ot/src/core/delta/mod.rs index 18b2df0264..8cb9d71ee2 100644 --- a/rust-lib/flowy-ot/src/core/delta/mod.rs +++ b/rust-lib/flowy-ot/src/core/delta/mod.rs @@ -1,6 +1,7 @@ mod builder; mod cursor; mod delta; +mod delta_serde; mod iterator; pub use builder::*; diff --git a/rust-lib/flowy-ot/src/core/operation/operation_serde.rs b/rust-lib/flowy-ot/src/core/operation/operation_serde.rs index 62ab3eed7b..b99739a082 100644 --- a/rust-lib/flowy-ot/src/core/operation/operation_serde.rs +++ b/rust-lib/flowy-ot/src/core/operation/operation_serde.rs @@ -92,46 +92,3 @@ impl<'de> Deserialize<'de> for Operation { deserializer.deserialize_any(OperationVisitor) } } - -impl Serialize for Delta { - fn serialize(&self, serializer: S) -> Result - 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(deserializer: D) -> Result - 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(self, mut seq: A) -> Result - 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) - } -} diff --git a/rust-lib/flowy-ot/tests/attribute_test.rs b/rust-lib/flowy-ot/tests/attribute_test.rs index 2c78a43540..f06a7e287a 100644 --- a/rust-lib/flowy-ot/tests/attribute_test.rs +++ b/rust-lib/flowy-ot/tests/attribute_test.rs @@ -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); +} diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index 7585b7a71a..4073c6ac50 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -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(); }, diff --git a/rust-lib/flowy-ot/tests/op_test.rs b/rust-lib/flowy-ot/tests/op_test.rs index 704726bf1e..18f6a1b261 100644 --- a/rust-lib/flowy-ot/tests/op_test.rs +++ b/rust-lib/flowy-ot/tests/op_test.rs @@ -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::(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();