mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
config header attribute & add test
This commit is contained in:
parent
c8502151ed
commit
aef5e54c3f
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ mod delete_ext;
|
||||
mod extension;
|
||||
mod format_ext;
|
||||
mod insert_ext;
|
||||
mod util;
|
||||
mod view;
|
||||
|
||||
pub use delete_ext::*;
|
||||
|
8
rust-lib/flowy-ot/src/client/view/util.rs
Normal file
8
rust-lib/flowy-ot/src/client/view/util.rs
Normal 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),
|
||||
}
|
||||
}
|
@ -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 {}),
|
||||
]
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
39
rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs
Normal file
39
rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod attributes;
|
||||
mod attributes_serde;
|
||||
mod builder;
|
||||
|
||||
pub use attributes::*;
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
53
rust-lib/flowy-ot/src/core/delta/delta_serde.rs
Normal file
53
rust-lib/flowy-ot/src/core/delta/delta_serde.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
mod builder;
|
||||
mod cursor;
|
||||
mod delta;
|
||||
mod delta_serde;
|
||||
mod iterator;
|
||||
|
||||
pub use builder::*;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
},
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user