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;
|
bool get hasRedo => _history.hasRedo;
|
||||||
|
|
||||||
Delta insert(int index, Object? data, {int replaceLength = 0}) {
|
Delta insert(int index, Object? data, {int replaceLength = 0}) {
|
||||||
|
print('insert $data at $index');
|
||||||
assert(index >= 0);
|
assert(index >= 0);
|
||||||
assert(data is String || data is Embeddable);
|
assert(data is String || data is Embeddable);
|
||||||
if (data is Embeddable) {
|
if (data is Embeddable) {
|
||||||
@ -67,7 +68,10 @@ class Document {
|
|||||||
data: data,
|
data: data,
|
||||||
length: replaceLength,
|
length: replaceLength,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
print('insert delta: $delta');
|
||||||
compose(delta, ChangeSource.LOCAL);
|
compose(delta, ChangeSource.LOCAL);
|
||||||
|
print('compose insert, current document $_delta');
|
||||||
return delta;
|
return delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +80,7 @@ class Document {
|
|||||||
final delta = _rules.apply(RuleType.DELETE, this, index, length: length);
|
final delta = _rules.apply(RuleType.DELETE, this, index, length: length);
|
||||||
if (delta.isNotEmpty) {
|
if (delta.isNotEmpty) {
|
||||||
compose(delta, ChangeSource.LOCAL);
|
compose(delta, ChangeSource.LOCAL);
|
||||||
|
print('compose delete, current document $_delta');
|
||||||
}
|
}
|
||||||
return delta;
|
return delta;
|
||||||
}
|
}
|
||||||
@ -92,14 +97,17 @@ class Document {
|
|||||||
// We have to insert before applying delete rules
|
// We have to insert before applying delete rules
|
||||||
// Otherwise delete would be operating on stale document snapshot.
|
// Otherwise delete would be operating on stale document snapshot.
|
||||||
if (dataIsNotEmpty) {
|
if (dataIsNotEmpty) {
|
||||||
|
print('insert $data at $index, replace len: $length');
|
||||||
delta = insert(index, data, replaceLength: length);
|
delta = insert(index, data, replaceLength: length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
|
print('delete $length at $index, len: $length');
|
||||||
final deleteDelta = delete(index, length);
|
final deleteDelta = delete(index, length);
|
||||||
delta = delta.compose(deleteDelta);
|
delta = delta.compose(deleteDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print('replace result $delta');
|
||||||
return delta;
|
return delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +125,7 @@ class Document {
|
|||||||
);
|
);
|
||||||
if (formatDelta.isNotEmpty) {
|
if (formatDelta.isNotEmpty) {
|
||||||
compose(formatDelta, ChangeSource.LOCAL);
|
compose(formatDelta, ChangeSource.LOCAL);
|
||||||
|
print('compose format, current document $_delta');
|
||||||
delta = delta.compose(formatDelta);
|
delta = delta.compose(formatDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,9 @@ class ResolveLineFormatRule extends FormatRule {
|
|||||||
for (var lineBreak = text.indexOf('\n');
|
for (var lineBreak = text.indexOf('\n');
|
||||||
lineBreak >= 0;
|
lineBreak >= 0;
|
||||||
lineBreak = text.indexOf('\n', offset)) {
|
lineBreak = text.indexOf('\n', offset)) {
|
||||||
tmp..retain(lineBreak - offset)..retain(1, attribute.toJson());
|
tmp
|
||||||
|
..retain(lineBreak - offset)
|
||||||
|
..retain(1, attribute.toJson());
|
||||||
offset = lineBreak + 1;
|
offset = lineBreak + 1;
|
||||||
}
|
}
|
||||||
tmp.retain(text.length - offset);
|
tmp.retain(text.length - offset);
|
||||||
@ -59,7 +61,9 @@ class ResolveLineFormatRule extends FormatRule {
|
|||||||
delta.retain(op.length!);
|
delta.retain(op.length!);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
delta..retain(lineBreak)..retain(1, attribute.toJson());
|
delta
|
||||||
|
..retain(lineBreak)
|
||||||
|
..retain(1, attribute.toJson());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return delta;
|
return delta;
|
||||||
@ -91,7 +95,9 @@ class FormatLinkAtCaretPositionRule extends FormatRule {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
delta..retain(beg)..retain(retain!, attribute.toJson());
|
delta
|
||||||
|
..retain(beg)
|
||||||
|
..retain(retain!, attribute.toJson());
|
||||||
return delta;
|
return delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,7 +126,9 @@ class ResolveInlineFormatRule extends FormatRule {
|
|||||||
}
|
}
|
||||||
var pos = 0;
|
var pos = 0;
|
||||||
while (lineBreak >= 0) {
|
while (lineBreak >= 0) {
|
||||||
delta..retain(lineBreak - pos, attribute.toJson())..retain(1);
|
delta
|
||||||
|
..retain(lineBreak - pos, attribute.toJson())
|
||||||
|
..retain(1);
|
||||||
pos = lineBreak + 1;
|
pos = lineBreak + 1;
|
||||||
lineBreak = text.indexOf('\n', pos);
|
lineBreak = text.indexOf('\n', pos);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,9 @@ class AutoExitBlockRule extends InsertRule {
|
|||||||
.firstWhere((k) => Attribute.blockKeysExceptHeader.contains(k));
|
.firstWhere((k) => Attribute.blockKeysExceptHeader.contains(k));
|
||||||
attributes[k] = null;
|
attributes[k] = null;
|
||||||
// retain(1) should be '\n', set it with no attribute
|
// 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));
|
final delta = Delta()..retain(index + (length ?? 0));
|
||||||
if (cursorBeforeEmbed && !text.endsWith('\n')) {
|
if (cursorBeforeEmbed && !text.endsWith('\n')) {
|
||||||
return delta..insert(text)..insert('\n');
|
return delta
|
||||||
|
..insert(text)
|
||||||
|
..insert('\n');
|
||||||
}
|
}
|
||||||
if (cursorAfterEmbed && !text.startsWith('\n')) {
|
if (cursorAfterEmbed && !text.startsWith('\n')) {
|
||||||
return delta..insert('\n')..insert(text);
|
return delta
|
||||||
|
..insert('\n')
|
||||||
|
..insert(text);
|
||||||
}
|
}
|
||||||
return delta..insert(text);
|
return delta..insert(text);
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ class Rules {
|
|||||||
|
|
||||||
static final Rules _instance = Rules([
|
static final Rules _instance = Rules([
|
||||||
const FormatLinkAtCaretPositionRule(),
|
const FormatLinkAtCaretPositionRule(),
|
||||||
// const ResolveLineFormatRule(),
|
const ResolveLineFormatRule(),
|
||||||
const ResolveInlineFormatRule(),
|
const ResolveInlineFormatRule(),
|
||||||
// const InsertEmbedsRule(),
|
// const InsertEmbedsRule(),
|
||||||
// const ForceNewlineForInsertsAroundEmbedRule(),
|
// const ForceNewlineForInsertsAroundEmbedRule(),
|
||||||
@ -70,6 +70,7 @@ class Rules {
|
|||||||
final result = rule.apply(delta, index,
|
final result = rule.apply(delta, index,
|
||||||
length: length, data: data, attribute: attribute);
|
length: length, data: data, attribute: attribute);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
print('apply rule: $rule, result: $result');
|
||||||
return result..trim();
|
return result..trim();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -34,6 +34,7 @@ impl Document {
|
|||||||
let interval = Interval::new(index, index);
|
let interval = Interval::new(index, index);
|
||||||
let _ = validate_interval(&self.delta, &interval)?;
|
let _ = validate_interval(&self.delta, &interval)?;
|
||||||
let delta = self.view.insert(&self.delta, text, interval)?;
|
let delta = self.view.insert(&self.delta, text, interval)?;
|
||||||
|
log::debug!("👉 receive change: {}", delta);
|
||||||
self.add_delta(&delta)?;
|
self.add_delta(&delta)?;
|
||||||
Ok(delta)
|
Ok(delta)
|
||||||
}
|
}
|
||||||
@ -43,6 +44,7 @@ impl Document {
|
|||||||
debug_assert_eq!(interval.is_empty(), false);
|
debug_assert_eq!(interval.is_empty(), false);
|
||||||
let delete = self.view.delete(&self.delta, interval)?;
|
let delete = self.view.delete(&self.delta, interval)?;
|
||||||
if !delete.is_empty() {
|
if !delete.is_empty() {
|
||||||
|
log::debug!("👉 receive change: {}", delete);
|
||||||
let _ = self.add_delta(&delete)?;
|
let _ = self.add_delta(&delete)?;
|
||||||
}
|
}
|
||||||
Ok(delete)
|
Ok(delete)
|
||||||
@ -56,6 +58,7 @@ impl Document {
|
|||||||
.format(&self.delta, attribute.clone(), interval)
|
.format(&self.delta, attribute.clone(), interval)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
log::debug!("👉 receive change: {}", format_delta);
|
||||||
self.add_delta(&format_delta)?;
|
self.add_delta(&format_delta)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -65,6 +68,7 @@ impl Document {
|
|||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
delta = self.view.insert(&self.delta, text, interval)?;
|
delta = self.view.insert(&self.delta, text, interval)?;
|
||||||
|
log::debug!("👉 receive change: {}", delta);
|
||||||
self.add_delta(&delta)?;
|
self.add_delta(&delta)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +125,6 @@ impl Document {
|
|||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
fn add_delta(&mut self, delta: &Delta) -> Result<(), OTError> {
|
fn add_delta(&mut self, delta: &Delta) -> Result<(), OTError> {
|
||||||
log::debug!("👉invert change {}", delta);
|
|
||||||
let composed_delta = self.delta.compose(delta)?;
|
let composed_delta = self.delta.compose(delta)?;
|
||||||
let mut undo_delta = delta.invert(&self.delta);
|
let mut undo_delta = delta.invert(&self.delta);
|
||||||
self.rev_id_counter += 1;
|
self.rev_id_counter += 1;
|
||||||
@ -138,7 +141,7 @@ impl Document {
|
|||||||
self.last_edit_time = now;
|
self.last_edit_time = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("compose previous result: {}", undo_delta);
|
log::debug!("👉 receive change undo: {}", undo_delta);
|
||||||
if !undo_delta.is_empty() {
|
if !undo_delta.is_empty() {
|
||||||
self.history.record(undo_delta);
|
self.history.record(undo_delta);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ use crate::{
|
|||||||
|
|
||||||
pub struct DefaultDeleteExt {}
|
pub struct DefaultDeleteExt {}
|
||||||
impl DeleteExt for DefaultDeleteExt {
|
impl DeleteExt for DefaultDeleteExt {
|
||||||
|
fn ext_name(&self) -> &str { "DeleteExt" }
|
||||||
|
|
||||||
fn apply(&self, _delta: &Delta, interval: Interval) -> Option<Delta> {
|
fn apply(&self, _delta: &Delta, interval: Interval) -> Option<Delta> {
|
||||||
Some(
|
Some(
|
||||||
DeltaBuilder::new()
|
DeltaBuilder::new()
|
||||||
|
@ -5,13 +5,16 @@ pub type FormatExtension = Box<dyn FormatExt>;
|
|||||||
pub type DeleteExtension = Box<dyn DeleteExt>;
|
pub type DeleteExtension = Box<dyn DeleteExt>;
|
||||||
|
|
||||||
pub trait InsertExt {
|
pub trait InsertExt {
|
||||||
|
fn ext_name(&self) -> &str;
|
||||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta>;
|
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FormatExt {
|
pub trait FormatExt {
|
||||||
|
fn ext_name(&self) -> &str;
|
||||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta>;
|
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DeleteExt {
|
pub trait DeleteExt {
|
||||||
|
fn ext_name(&self) -> &str;
|
||||||
fn apply(&self, delta: &Delta, interval: Interval) -> Option<Delta>;
|
fn apply(&self, delta: &Delta, interval: Interval) -> Option<Delta>;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
client::view::{FormatExt, NEW_LINE},
|
client::view::{util::find_newline, FormatExt, NEW_LINE},
|
||||||
core::{
|
core::{
|
||||||
Attribute,
|
Attribute,
|
||||||
AttributeKey,
|
AttributeKey,
|
||||||
@ -17,6 +17,8 @@ use crate::{
|
|||||||
pub struct FormatLinkAtCaretPositionExt {}
|
pub struct FormatLinkAtCaretPositionExt {}
|
||||||
|
|
||||||
impl FormatExt for FormatLinkAtCaretPositionExt {
|
impl FormatExt for FormatLinkAtCaretPositionExt {
|
||||||
|
fn ext_name(&self) -> &str { "FormatLinkAtCaretPositionExt" }
|
||||||
|
|
||||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||||
if attribute.key != AttributeKey::Link || interval.size() != 0 {
|
if attribute.key != AttributeKey::Link || interval.size() != 0 {
|
||||||
return None;
|
return None;
|
||||||
@ -57,26 +59,56 @@ impl FormatExt for FormatLinkAtCaretPositionExt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ResolveLineFormatExt {}
|
pub struct ResolveBlockFormatExt {}
|
||||||
impl FormatExt for ResolveLineFormatExt {
|
impl FormatExt for ResolveBlockFormatExt {
|
||||||
|
fn ext_name(&self) -> &str { "ResolveBlockFormatExt" }
|
||||||
|
|
||||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||||
if attribute.scope != AttributeScope::Block {
|
if attribute.scope != AttributeScope::Block {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut new_delta = Delta::new();
|
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
|
||||||
new_delta.retain(interval.start, Attributes::default());
|
|
||||||
|
|
||||||
let mut iter = DeltaIter::new(delta);
|
let mut iter = DeltaIter::new(delta);
|
||||||
iter.seek::<CharMetric>(interval.start);
|
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 {}
|
pub struct ResolveInlineFormatExt {}
|
||||||
|
|
||||||
impl FormatExt for ResolveInlineFormatExt {
|
impl FormatExt for ResolveInlineFormatExt {
|
||||||
|
fn ext_name(&self) -> &str { "ResolveInlineFormatExt" }
|
||||||
|
|
||||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||||
if attribute.scope != AttributeScope::Inline {
|
if attribute.scope != AttributeScope::Inline {
|
||||||
return None;
|
return None;
|
||||||
@ -85,44 +117,57 @@ impl FormatExt for ResolveInlineFormatExt {
|
|||||||
let mut iter = DeltaIter::new(delta);
|
let mut iter = DeltaIter::new(delta);
|
||||||
iter.seek::<CharMetric>(interval.start);
|
iter.seek::<CharMetric>(interval.start);
|
||||||
|
|
||||||
let mut cur = 0;
|
let mut start = 0;
|
||||||
let len = interval.size();
|
let end = interval.size();
|
||||||
|
|
||||||
while cur < len && iter.has_next() {
|
while start < end && iter.has_next() {
|
||||||
let some_op = iter.next_op_with_len(len - cur);
|
let next_op = iter.next_op_with_len(end - start).unwrap();
|
||||||
if some_op.is_none() {
|
match find_newline(next_op.get_data()) {
|
||||||
return Some(new_delta);
|
None => new_delta.retain(next_op.length(), attribute.clone().into()),
|
||||||
}
|
Some(_) => {
|
||||||
let op = some_op.unwrap();
|
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Inline);
|
||||||
if let Operation::Insert(insert) = &op {
|
new_delta.extend(tmp_delta);
|
||||||
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());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cur += op.length();
|
start += next_op.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(new_delta)
|
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::{
|
use crate::{
|
||||||
client::view::InsertExt,
|
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 const NEW_LINE: &'static str = "\n";
|
||||||
|
|
||||||
pub struct PreserveBlockStyleOnInsertExt {}
|
pub struct PreserveBlockStyleOnInsertExt {}
|
||||||
impl InsertExt for PreserveBlockStyleOnInsertExt {
|
impl InsertExt for PreserveBlockStyleOnInsertExt {
|
||||||
|
fn ext_name(&self) -> &str { "PreserveBlockStyleOnInsertExt" }
|
||||||
|
|
||||||
fn apply(
|
fn apply(
|
||||||
&self,
|
&self,
|
||||||
_delta: &Delta,
|
_delta: &Delta,
|
||||||
@ -20,6 +22,8 @@ impl InsertExt for PreserveBlockStyleOnInsertExt {
|
|||||||
|
|
||||||
pub struct PreserveLineStyleOnSplitExt {}
|
pub struct PreserveLineStyleOnSplitExt {}
|
||||||
impl InsertExt for PreserveLineStyleOnSplitExt {
|
impl InsertExt for PreserveLineStyleOnSplitExt {
|
||||||
|
fn ext_name(&self) -> &str { "PreserveLineStyleOnSplitExt" }
|
||||||
|
|
||||||
fn apply(
|
fn apply(
|
||||||
&self,
|
&self,
|
||||||
_delta: &Delta,
|
_delta: &Delta,
|
||||||
@ -34,6 +38,8 @@ impl InsertExt for PreserveLineStyleOnSplitExt {
|
|||||||
pub struct AutoExitBlockExt {}
|
pub struct AutoExitBlockExt {}
|
||||||
|
|
||||||
impl InsertExt for AutoExitBlockExt {
|
impl InsertExt for AutoExitBlockExt {
|
||||||
|
fn ext_name(&self) -> &str { "AutoExitBlockExt" }
|
||||||
|
|
||||||
fn apply(
|
fn apply(
|
||||||
&self,
|
&self,
|
||||||
_delta: &Delta,
|
_delta: &Delta,
|
||||||
@ -47,6 +53,8 @@ impl InsertExt for AutoExitBlockExt {
|
|||||||
|
|
||||||
pub struct InsertEmbedsExt {}
|
pub struct InsertEmbedsExt {}
|
||||||
impl InsertExt for InsertEmbedsExt {
|
impl InsertExt for InsertEmbedsExt {
|
||||||
|
fn ext_name(&self) -> &str { "InsertEmbedsExt" }
|
||||||
|
|
||||||
fn apply(
|
fn apply(
|
||||||
&self,
|
&self,
|
||||||
_delta: &Delta,
|
_delta: &Delta,
|
||||||
@ -60,6 +68,8 @@ impl InsertExt for InsertEmbedsExt {
|
|||||||
|
|
||||||
pub struct ForceNewlineForInsertsAroundEmbedExt {}
|
pub struct ForceNewlineForInsertsAroundEmbedExt {}
|
||||||
impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
|
impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
|
||||||
|
fn ext_name(&self) -> &str { "ForceNewlineForInsertsAroundEmbedExt" }
|
||||||
|
|
||||||
fn apply(
|
fn apply(
|
||||||
&self,
|
&self,
|
||||||
_delta: &Delta,
|
_delta: &Delta,
|
||||||
@ -73,6 +83,8 @@ impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
|
|||||||
|
|
||||||
pub struct AutoFormatLinksExt {}
|
pub struct AutoFormatLinksExt {}
|
||||||
impl InsertExt for AutoFormatLinksExt {
|
impl InsertExt for AutoFormatLinksExt {
|
||||||
|
fn ext_name(&self) -> &str { "AutoFormatLinksExt" }
|
||||||
|
|
||||||
fn apply(
|
fn apply(
|
||||||
&self,
|
&self,
|
||||||
_delta: &Delta,
|
_delta: &Delta,
|
||||||
@ -86,8 +98,10 @@ impl InsertExt for AutoFormatLinksExt {
|
|||||||
|
|
||||||
pub struct PreserveInlineStylesExt {}
|
pub struct PreserveInlineStylesExt {}
|
||||||
impl InsertExt for 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> {
|
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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +133,8 @@ impl InsertExt for PreserveInlineStylesExt {
|
|||||||
|
|
||||||
pub struct ResetLineFormatOnNewLineExt {}
|
pub struct ResetLineFormatOnNewLineExt {}
|
||||||
impl InsertExt for 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> {
|
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||||
if text != NEW_LINE {
|
if text != NEW_LINE {
|
||||||
return None;
|
return None;
|
||||||
@ -133,7 +149,7 @@ impl InsertExt for ResetLineFormatOnNewLineExt {
|
|||||||
|
|
||||||
let mut reset_attribute = Attributes::new();
|
let mut reset_attribute = Attributes::new();
|
||||||
if next_op.get_attributes().contains_key(&AttributeKey::Header) {
|
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;
|
let len = index + replace_len;
|
||||||
@ -150,11 +166,27 @@ impl InsertExt for ResetLineFormatOnNewLineExt {
|
|||||||
|
|
||||||
pub struct DefaultInsertExt {}
|
pub struct DefaultInsertExt {}
|
||||||
impl InsertExt for 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(
|
Some(
|
||||||
DeltaBuilder::new()
|
DeltaBuilder::new()
|
||||||
.retain(index + replace_len)
|
.retain(index + replace_len)
|
||||||
.insert(text)
|
.insert_with_attributes(text, attributes)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ mod delete_ext;
|
|||||||
mod extension;
|
mod extension;
|
||||||
mod format_ext;
|
mod format_ext;
|
||||||
mod insert_ext;
|
mod insert_ext;
|
||||||
|
mod util;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
pub use delete_ext::*;
|
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::{
|
use crate::{
|
||||||
client::view::*,
|
client::view::*,
|
||||||
core::{Attribute, Delta, Interval},
|
core::{Attribute, Delta, Interval, Operation},
|
||||||
errors::{ErrorBuilder, OTError, OTErrorCode},
|
errors::{ErrorBuilder, OTError, OTErrorCode},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,6 +28,7 @@ impl View {
|
|||||||
let mut new_delta = None;
|
let mut new_delta = None;
|
||||||
for ext in &self.insert_exts {
|
for ext in &self.insert_exts {
|
||||||
if let Some(delta) = ext.apply(delta, interval.size(), text, interval.start) {
|
if let Some(delta) = ext.apply(delta, interval.size(), text, interval.start) {
|
||||||
|
log::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
|
||||||
new_delta = Some(delta);
|
new_delta = Some(delta);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -43,6 +44,7 @@ impl View {
|
|||||||
let mut new_delta = None;
|
let mut new_delta = None;
|
||||||
for ext in &self.delete_exts {
|
for ext in &self.delete_exts {
|
||||||
if let Some(delta) = ext.apply(delta, interval) {
|
if let Some(delta) = ext.apply(delta, interval) {
|
||||||
|
log::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
|
||||||
new_delta = Some(delta);
|
new_delta = Some(delta);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -63,6 +65,7 @@ impl View {
|
|||||||
let mut new_delta = None;
|
let mut new_delta = None;
|
||||||
for ext in &self.format_exts {
|
for ext in &self.format_exts {
|
||||||
if let Some(delta) = ext.apply(delta, interval, &attribute) {
|
if let Some(delta) = ext.apply(delta, interval, &attribute) {
|
||||||
|
log::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
|
||||||
new_delta = Some(delta);
|
new_delta = Some(delta);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -92,7 +95,7 @@ fn construct_insert_exts() -> Vec<InsertExtension> {
|
|||||||
fn construct_format_exts() -> Vec<FormatExtension> {
|
fn construct_format_exts() -> Vec<FormatExtension> {
|
||||||
vec![
|
vec![
|
||||||
Box::new(FormatLinkAtCaretPositionExt {}),
|
Box::new(FormatLinkAtCaretPositionExt {}),
|
||||||
Box::new(ResolveLineFormatExt {}),
|
Box::new(ResolveBlockFormatExt {}),
|
||||||
Box::new(ResolveInlineFormatExt {}),
|
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};
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
pub const REMOVE_FLAG: &'static str = "";
|
pub const REMOVE_FLAG: &'static str = "";
|
||||||
pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG }
|
pub(crate) fn should_remove(val: &AttributeValue) -> bool { val.0 == REMOVE_FLAG }
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Attributes {
|
pub struct Attributes {
|
||||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub(crate) inner: HashMap<AttributeKey, String>,
|
pub(crate) inner: HashMap<AttributeKey, AttributeValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Attributes {
|
impl fmt::Display for Attributes {
|
||||||
@ -38,7 +38,8 @@ impl Attributes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, key: &AttributeKey) {
|
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: "" }
|
// Remove the key if its value is empty. e.g. { bold: "" }
|
||||||
@ -62,7 +63,7 @@ impl Attributes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for Attributes {
|
impl std::ops::Deref for Attributes {
|
||||||
type Target = HashMap<AttributeKey, String>;
|
type Target = HashMap<AttributeKey, AttributeValue>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.inner }
|
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 derive_more::Display;
|
||||||
use std::{fmt, fmt::Formatter};
|
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)]
|
#[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum AttributeKey {
|
pub enum AttributeKey {
|
||||||
@ -23,8 +30,6 @@ pub enum AttributeKey {
|
|||||||
Color,
|
Color,
|
||||||
#[display(fmt = "background")]
|
#[display(fmt = "background")]
|
||||||
Background,
|
Background,
|
||||||
#[display(fmt = "header")]
|
|
||||||
Header,
|
|
||||||
#[display(fmt = "ident")]
|
#[display(fmt = "ident")]
|
||||||
Ident,
|
Ident,
|
||||||
#[display(fmt = "align")]
|
#[display(fmt = "align")]
|
||||||
@ -41,18 +46,8 @@ pub enum AttributeKey {
|
|||||||
Height,
|
Height,
|
||||||
#[display(fmt = "style")]
|
#[display(fmt = "style")]
|
||||||
Style,
|
Style,
|
||||||
#[display(fmt = "h1")]
|
#[display(fmt = "header")]
|
||||||
H1,
|
Header,
|
||||||
#[display(fmt = "h2")]
|
|
||||||
H2,
|
|
||||||
#[display(fmt = "h3")]
|
|
||||||
H3,
|
|
||||||
#[display(fmt = "h4")]
|
|
||||||
H4,
|
|
||||||
#[display(fmt = "h5")]
|
|
||||||
H5,
|
|
||||||
#[display(fmt = "h6")]
|
|
||||||
H6,
|
|
||||||
#[display(fmt = "left")]
|
#[display(fmt = "left")]
|
||||||
LeftAlignment,
|
LeftAlignment,
|
||||||
#[display(fmt = "center")]
|
#[display(fmt = "center")]
|
||||||
@ -82,13 +77,13 @@ pub enum AttributeScope {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Attribute {
|
pub struct Attribute {
|
||||||
pub key: AttributeKey,
|
pub key: AttributeKey,
|
||||||
pub value: String,
|
pub value: AttributeValue,
|
||||||
pub scope: AttributeScope,
|
pub scope: AttributeScope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Attribute {
|
impl fmt::Display for Attribute {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
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)
|
f.write_str(&s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,13 +124,13 @@ impl AttrsBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert<T: Into<String>>(mut self, key: AttributeKey, value: T) -> Self {
|
pub fn insert<T: Into<AttributeValue>>(mut self, key: AttributeKey, value: T) -> Self {
|
||||||
self.inner.add(key.with_value(value));
|
self.inner.add(key.value(value));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove<T: Into<String>>(mut self, key: AttributeKey) -> 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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,9 +143,11 @@ impl AttrsBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AttributeKey {
|
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 key = self.clone();
|
||||||
let value: String = value.into();
|
let value: AttributeValue = value.into();
|
||||||
match self {
|
match self {
|
||||||
AttributeKey::Bold
|
AttributeKey::Bold
|
||||||
| AttributeKey::Italic
|
| AttributeKey::Italic
|
||||||
@ -167,12 +164,6 @@ impl AttributeKey {
|
|||||||
},
|
},
|
||||||
|
|
||||||
AttributeKey::Header
|
AttributeKey::Header
|
||||||
| AttributeKey::H1
|
|
||||||
| AttributeKey::H2
|
|
||||||
| AttributeKey::H3
|
|
||||||
| AttributeKey::H4
|
|
||||||
| AttributeKey::H5
|
|
||||||
| AttributeKey::H6
|
|
||||||
| AttributeKey::LeftAlignment
|
| AttributeKey::LeftAlignment
|
||||||
| AttributeKey::CenterAlignment
|
| AttributeKey::CenterAlignment
|
||||||
| AttributeKey::RightAlignment
|
| 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;
|
||||||
|
mod attributes_serde;
|
||||||
mod builder;
|
mod builder;
|
||||||
|
|
||||||
pub use attributes::*;
|
pub use attributes::*;
|
||||||
|
@ -42,11 +42,16 @@ impl<'a> Cursor<'a> {
|
|||||||
next_op = find_next_op(self);
|
next_op = find_next_op(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let old_c_index = self.c_index;
|
||||||
while find_op.is_none() && next_op.is_some() {
|
while find_op.is_none() && next_op.is_some() {
|
||||||
let op = next_op.take().unwrap();
|
let op = next_op.take().unwrap();
|
||||||
let interval = self.next_op_interval_with_constraint(force_len);
|
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);
|
let suffix = Interval::new(0, op.length()).suffix(interval);
|
||||||
if !suffix.is_empty() {
|
if !suffix.is_empty() {
|
||||||
self.next_op = op.shrink(suffix);
|
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) }
|
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 {
|
fn seek(cursor: &mut Cursor, index: usize) -> SeekResult {
|
||||||
let _ = check_bound(cursor.c_index, index)?;
|
let _ = check_bound(cursor.c_index, index)?;
|
||||||
let _ = cursor.next_op_with_len(Some(index));
|
let _ = cursor.next_op_with_len(Some(index));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -599,6 +599,8 @@ impl Delta {
|
|||||||
pub fn is_empty(&self) -> bool { self.ops.is_empty() }
|
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 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(
|
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 builder;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
mod delta;
|
mod delta;
|
||||||
|
mod delta_serde;
|
||||||
mod iterator;
|
mod iterator;
|
||||||
|
|
||||||
pub use builder::*;
|
pub use builder::*;
|
||||||
|
@ -92,46 +92,3 @@ impl<'de> Deserialize<'de> for Operation {
|
|||||||
deserializer.deserialize_any(OperationVisitor)
|
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;
|
use flowy_ot::core::Interval;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_insert_text() {
|
fn attributes_insert_text() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Insert(0, "456", 3),
|
Insert(0, "456", 3),
|
||||||
@ -14,7 +14,7 @@ fn delta_insert_text() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_insert_text_at_head() {
|
fn attributes_insert_text_at_head() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Insert(0, "456", 0),
|
Insert(0, "456", 0),
|
||||||
@ -24,7 +24,7 @@ fn delta_insert_text_at_head() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_insert_text_at_middle() {
|
fn attributes_insert_text_at_middle() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Insert(0, "456", 1),
|
Insert(0, "456", 1),
|
||||||
@ -34,7 +34,7 @@ fn delta_insert_text_at_middle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_insert_text_with_attr() {
|
fn attributes_insert_text_with_attr() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "145", 0),
|
Insert(0, "145", 0),
|
||||||
Insert(0, "23", 1),
|
Insert(0, "23", 1),
|
||||||
@ -53,7 +53,7 @@ fn delta_insert_text_with_attr() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_add_bold() {
|
fn attributes_add_bold() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123456", 0),
|
Insert(0, "123456", 0),
|
||||||
Bold(0, Interval::new(3, 5), true),
|
Bold(0, Interval::new(3, 5), true),
|
||||||
@ -70,7 +70,7 @@ fn delta_add_bold() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_add_bold_and_invert_all() {
|
fn attributes_add_bold_and_invert_all() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Bold(0, Interval::new(0, 3), true),
|
Bold(0, Interval::new(0, 3), true),
|
||||||
@ -82,7 +82,7 @@ fn delta_add_bold_and_invert_all() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_add_bold_and_invert_partial_suffix() {
|
fn attributes_add_bold_and_invert_partial_suffix() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "1234", 0),
|
Insert(0, "1234", 0),
|
||||||
Bold(0, Interval::new(0, 4), true),
|
Bold(0, Interval::new(0, 4), true),
|
||||||
@ -97,7 +97,7 @@ fn delta_add_bold_and_invert_partial_suffix() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_add_bold_and_invert_partial_suffix2() {
|
fn attributes_add_bold_and_invert_partial_suffix2() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "1234", 0),
|
Insert(0, "1234", 0),
|
||||||
Bold(0, Interval::new(0, 4), true),
|
Bold(0, Interval::new(0, 4), true),
|
||||||
@ -114,7 +114,35 @@ fn delta_add_bold_and_invert_partial_suffix2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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![
|
let ops = vec![
|
||||||
Insert(0, "1234", 0),
|
Insert(0, "1234", 0),
|
||||||
Bold(0, Interval::new(0, 4), true),
|
Bold(0, Interval::new(0, 4), true),
|
||||||
@ -129,7 +157,7 @@ fn delta_add_bold_and_invert_partial_prefix() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_add_bold_consecutive() {
|
fn attributes_add_bold_consecutive() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "1234", 0),
|
Insert(0, "1234", 0),
|
||||||
Bold(0, Interval::new(0, 1), true),
|
Bold(0, Interval::new(0, 1), true),
|
||||||
@ -147,31 +175,26 @@ fn delta_add_bold_consecutive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_add_bold_italic() {
|
fn attributes_add_bold_italic() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "1234", 0),
|
Insert(0, "1234", 0),
|
||||||
Bold(0, Interval::new(0, 4), true),
|
Bold(0, Interval::new(0, 4), true),
|
||||||
Italic(0, Interval::new(0, 4), true),
|
Italic(0, Interval::new(0, 4), true),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
0,
|
0,
|
||||||
r#"[{"insert":"1234","attributes":{"italic":"true","bold":"true"}}]"#,
|
r#"[{"insert":"1234","attributes":{"italic":"true","bold":"true"}},{"insert":"\n"}]"#,
|
||||||
),
|
),
|
||||||
Insert(0, "5678", 4),
|
Insert(0, "5678", 4),
|
||||||
AssertOpsJson(
|
AssertOpsJson(
|
||||||
0,
|
0,
|
||||||
r#"[{"insert":"12345678","attributes":{"italic":"true","bold":"true"}}]"#,
|
r#"[{"insert":"12345678","attributes":{"bold":"true","italic":"true"}},{"insert":"\n"}]"#,
|
||||||
),
|
|
||||||
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"}}]"#,
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_add_bold_italic2() {
|
fn attributes_add_bold_italic2() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123456", 0),
|
Insert(0, "123456", 0),
|
||||||
Bold(0, Interval::new(0, 6), true),
|
Bold(0, Interval::new(0, 6), true),
|
||||||
@ -199,7 +222,7 @@ fn delta_add_bold_italic2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_add_bold_italic3() {
|
fn attributes_add_bold_italic3() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123456789", 0),
|
Insert(0, "123456789", 0),
|
||||||
Bold(0, Interval::new(0, 5), true),
|
Bold(0, Interval::new(0, 5), true),
|
||||||
@ -236,7 +259,7 @@ fn delta_add_bold_italic3() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_add_bold_italic_delete() {
|
fn attributes_add_bold_italic_delete() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123456789", 0),
|
Insert(0, "123456789", 0),
|
||||||
Bold(0, Interval::new(0, 5), true),
|
Bold(0, Interval::new(0, 5), true),
|
||||||
@ -275,7 +298,7 @@ fn delta_add_bold_italic_delete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_merge_inserted_text_with_same_attribute() {
|
fn attributes_merge_inserted_text_with_same_attribute() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123", Interval::new(0, 3)),
|
InsertBold(0, "123", Interval::new(0, 3)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
|
||||||
@ -286,7 +309,7 @@ fn delta_merge_inserted_text_with_same_attribute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_compose_attr_delta_with_attr_delta_test() {
|
fn attributes_compose_attr_attributes_with_attr_attributes_test() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -301,7 +324,7 @@ fn delta_compose_attr_delta_with_attr_delta_test() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_compose_attr_delta_with_attr_delta_test2() {
|
fn attributes_compose_attr_attributes_with_attr_attributes_test2() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Insert(0, "123456", 0),
|
Insert(0, "123456", 0),
|
||||||
Bold(0, Interval::new(0, 6), true),
|
Bold(0, Interval::new(0, 6), true),
|
||||||
@ -342,7 +365,7 @@ fn delta_compose_attr_delta_with_attr_delta_test2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 expected = r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"7"}]"#;
|
||||||
|
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
@ -358,7 +381,7 @@ fn delta_compose_attr_delta_with_no_attr_delta_test() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_replace_heading() {
|
fn attributes_replace_heading() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -370,7 +393,7 @@ fn delta_replace_heading() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_replace_trailing() {
|
fn attributes_replace_trailing() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -382,7 +405,7 @@ fn delta_replace_trailing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_replace_middle() {
|
fn attributes_replace_middle() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -396,7 +419,7 @@ fn delta_replace_middle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_replace_all() {
|
fn attributes_replace_all() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -408,7 +431,7 @@ fn delta_replace_all() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_replace_with_text() {
|
fn attributes_replace_with_text() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
InsertBold(0, "123456", Interval::new(0, 6)),
|
InsertBold(0, "123456", Interval::new(0, 6)),
|
||||||
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
|
||||||
@ -421,3 +444,62 @@ fn delta_replace_with_text() {
|
|||||||
|
|
||||||
OpTester::new().run_script(ops);
|
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")]
|
#[display(fmt = "Italic")]
|
||||||
Italic(usize, Interval, bool),
|
Italic(usize, Interval, bool),
|
||||||
|
|
||||||
|
#[display(fmt = "Header")]
|
||||||
|
Header(usize, Interval, usize, bool),
|
||||||
|
|
||||||
#[display(fmt = "Transform")]
|
#[display(fmt = "Transform")]
|
||||||
Transform(usize, usize),
|
Transform(usize, usize),
|
||||||
|
|
||||||
@ -60,7 +63,7 @@ impl OpTester {
|
|||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
color_eyre::install().unwrap();
|
color_eyre::install().unwrap();
|
||||||
std::env::set_var("RUST_LOG", "info");
|
std::env::set_var("RUST_LOG", "debug");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -86,22 +89,30 @@ impl OpTester {
|
|||||||
let document = &mut self.documents[*delta_i];
|
let document = &mut self.documents[*delta_i];
|
||||||
document.insert(interval.start, s).unwrap();
|
document.insert(interval.start, s).unwrap();
|
||||||
document
|
document
|
||||||
.format(*interval, AttributeKey::Bold.with_value("true".to_owned()))
|
.format(*interval, AttributeKey::Bold.value(true))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
},
|
},
|
||||||
TestOp::Bold(delta_i, interval, enable) => {
|
TestOp::Bold(delta_i, interval, enable) => {
|
||||||
let document = &mut self.documents[*delta_i];
|
let document = &mut self.documents[*delta_i];
|
||||||
let attribute = match *enable {
|
let attribute = match *enable {
|
||||||
true => AttributeKey::Bold.with_value("true".to_owned()),
|
true => AttributeKey::Bold.value(true),
|
||||||
false => AttributeKey::Bold.with_value("".to_owned()),
|
false => AttributeKey::Bold.remove(),
|
||||||
};
|
};
|
||||||
document.format(*interval, attribute).unwrap();
|
document.format(*interval, attribute).unwrap();
|
||||||
},
|
},
|
||||||
TestOp::Italic(delta_i, interval, enable) => {
|
TestOp::Italic(delta_i, interval, enable) => {
|
||||||
let document = &mut self.documents[*delta_i];
|
let document = &mut self.documents[*delta_i];
|
||||||
let attribute = match *enable {
|
let attribute = match *enable {
|
||||||
true => AttributeKey::Italic.with_value("true"),
|
true => AttributeKey::Italic.value("true"),
|
||||||
false => AttributeKey::Italic.with_value(REMOVE_FLAG),
|
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();
|
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]
|
#[test]
|
||||||
fn delta_next_op_len_test() {
|
fn delta_next_op_len_test() {
|
||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user