add auto format extension

This commit is contained in:
appflowy 2021-08-16 15:02:57 +08:00
parent f7a6769e89
commit 41eacb7000
30 changed files with 705 additions and 447 deletions

View File

@ -1,4 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="15.7124" y="7.22705" width="1.5" height="12" rx="0.75" transform="rotate(45 15.7124 7.22705)" fill="#333333"/>
<rect x="16.7729" y="15.7124" width="1.5" height="12" rx="0.75" transform="rotate(135 16.7729 15.7124)" fill="#333333"/>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="20.9497" y="9.63599" width="2" height="16" rx="1" transform="rotate(45 20.9497 9.63599)" fill="#333333"/>
<rect x="22.364" y="20.95" width="2" height="16" rx="1" transform="rotate(135 22.364 20.95)" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 328 B

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 6L18.781 11.9243L25 12.8801L20.5 17.489L21.562 24L16 20.9243L10.438 24L11.5 17.489L7 12.8801L13.219 11.9243L16 6Z" fill="#FFD667" stroke="#FFD667" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 329 B

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 6L18.781 11.9243L25 12.8801L20.5 17.489L21.562 24L16 20.9243L10.438 24L11.5 17.489L7 12.8801L13.219 11.9243L16 6Z" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 314 B

View File

@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="6" width="26" height="20" rx="3" stroke="#333333" stroke-width="2"/>
<circle cx="11" cy="13" r="2" stroke="#333333" stroke-width="2"/>
<path d="M10 26L20.2239 16.9121C20.8422 16.3625 21.7349 16.2503 22.4699 16.6296L29 20" stroke="#333333" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 376 B

View File

@ -1,4 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5 3.75V9C13.5 9.82843 14.1716 10.5 15 10.5H18" stroke="#333333" stroke-width="1.5"/>
<path d="M5.25 5.25C5.25 4.42157 5.92157 3.75 6.75 3.75H12H12.75C13.6943 3.75 14.5834 4.19458 15.15 4.95L18.15 8.95C18.5395 9.46929 18.75 10.1009 18.75 10.75V12V18.75C18.75 19.5784 18.0784 20.25 17.25 20.25H6.75C5.92157 20.25 5.25 19.5784 5.25 18.75V5.25Z" stroke="#333333" stroke-width="1.5"/>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 5V12C18 13.1046 18.8954 14 20 14H24" stroke="#333333" stroke-width="2"/>
<path d="M7 7C7 5.89543 7.89543 5 9 5H16H17C18.259 5 19.4446 5.59278 20.2 6.6L24.2 11.9333C24.7193 12.6257 25 13.4679 25 14.3333V16V25C25 26.1046 24.1046 27 23 27H9C7.89543 27 7 26.1046 7 25V7Z" stroke="#333333" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 496 B

After

Width:  |  Height:  |  Size: 420 B

View File

@ -49,7 +49,7 @@ class Rules {
// const PreserveBlockStyleOnInsertRule(),
// const PreserveLineStyleOnSplitRule(),
const ResetLineFormatOnNewLineRule(),
// const AutoFormatLinksRule(),
const AutoFormatLinksRule(),
const PreserveInlineStylesRule(),
const CatchAllInsertRule(),
// const EnsureEmbedLineRule(),

View File

@ -93,7 +93,8 @@ class EditorController extends ChangeNotifier {
toggledStyle = toggledStyle.put(attribute);
}
final change = document.format(index, length, attribute);
final change =
document.format(index, length, LinkAttribute("www.baidu.com"));
final adjustedSelection = selection.copyWith(
baseOffset: change.transformPosition(selection.baseOffset),
extentOffset: change.transformPosition(selection.extentOffset),

View File

@ -1,5 +1,5 @@
use crate::{
client::view::DeleteExt,
client::extensions::DeleteExt,
core::{Delta, DeltaBuilder, Interval},
};

View File

@ -0,0 +1,3 @@
mod default_delete;
pub use default_delete::*;

View File

@ -0,0 +1,49 @@
use crate::{
client::extensions::FormatExt,
core::{Attribute, AttributeKey, CharMetric, Delta, DeltaBuilder, DeltaIter, Interval},
};
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;
}
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(interval.start);
let (before, after) = (iter.next_op_before(interval.size()), iter.next());
let mut start = interval.end;
let mut retain = 0;
if let Some(before) = before {
if before.contain_attribute(attribute) {
start -= before.len();
retain += before.len();
}
}
if let Some(after) = after {
if after.contain_attribute(attribute) {
if retain != 0 {
retain += after.len();
}
}
}
if retain == 0 {
return None;
}
Some(
DeltaBuilder::new()
.retain(start)
.retain_with_attributes(retain, (attribute.clone()).into())
.build(),
)
}
}

View File

@ -0,0 +1,39 @@
use crate::{
client::util::find_newline,
core::{Attribute, AttributeScope, Attributes, Delta, Operation},
};
pub(crate) fn line_break(op: &Operation, attribute: &Attribute, scope: AttributeScope) -> Delta {
let mut new_delta = Delta::new();
let mut start = 0;
let end = op.len();
let mut s = op.get_data();
while let Some(line_break) = find_newline(s) {
match scope {
AttributeScope::Inline => {
new_delta.retain(line_break - start, attribute.clone().into());
new_delta.retain(1, Attributes::empty());
},
AttributeScope::Block => {
new_delta.retain(line_break - start, Attributes::empty());
new_delta.retain(1, attribute.clone().into());
},
_ => {
log::error!("Unsupported parser line break for {:?}", scope);
},
}
start = line_break + 1;
s = &s[start..s.len()];
}
if start < end {
match scope {
AttributeScope::Inline => new_delta.retain(end - start, attribute.clone().into()),
AttributeScope::Block => new_delta.retain(end - start, Attributes::empty()),
_ => log::error!("Unsupported parser line break for {:?}", scope),
}
}
new_delta
}

View File

@ -0,0 +1,8 @@
mod format_at_position;
mod helper;
mod resolve_block_format;
mod resolve_inline_format;
pub use format_at_position::*;
pub use resolve_block_format::*;
pub use resolve_inline_format::*;

View File

@ -0,0 +1,62 @@
use crate::{
client::{
extensions::{format::helper::line_break, FormatExt},
util::find_newline,
},
core::{
Attribute,
AttributeScope,
Attributes,
CharMetric,
Delta,
DeltaBuilder,
DeltaIter,
Interval,
},
};
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 = 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_before(end - start).unwrap();
match find_newline(next_op.get_data()) {
None => new_delta.retain(next_op.len(), Attributes::empty()),
Some(_) => {
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Block);
new_delta.extend(tmp_delta);
},
}
start += next_op.len();
}
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.len(), Attributes::empty()),
Some(line_break) => {
debug_assert_eq!(line_break, 0);
new_delta.retain(1, attribute.clone().into());
break;
},
}
}
Some(new_delta)
}
}

View File

@ -0,0 +1,39 @@
use crate::{
client::{
extensions::{format::helper::line_break, FormatExt},
util::find_newline,
},
core::{Attribute, AttributeScope, CharMetric, Delta, DeltaBuilder, DeltaIter, Interval},
};
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;
}
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_before(end - start).unwrap();
match find_newline(next_op.get_data()) {
None => new_delta.retain(next_op.len(), attribute.clone().into()),
Some(_) => {
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Inline);
new_delta.extend(tmp_delta);
},
}
start += next_op.len();
}
Some(new_delta)
}
}

View File

@ -0,0 +1,26 @@
use crate::{
client::{extensions::InsertExt, util::is_whitespace},
core::{CharMetric, Delta, DeltaIter},
};
pub struct AutoFormatExt {}
impl InsertExt for AutoFormatExt {
fn ext_name(&self) -> &str { "AutoFormatExt" }
fn apply(&self, delta: &Delta, _replace_len: usize, text: &str, index: usize) -> Option<Delta> {
// enter whitespace to trigger auto format
if !is_whitespace(text) {
return None;
}
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(index);
let prev = iter.next_op();
if prev.is_none() {
return None;
}
let _prev = prev.unwrap();
None
}
}

View File

@ -0,0 +1,37 @@
use crate::{
client::extensions::{InsertExt, NEW_LINE},
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter},
};
pub struct DefaultInsertExt {}
impl InsertExt for DefaultInsertExt {
fn ext_name(&self) -> &str { "DefaultInsertExt" }
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
let iter = DeltaIter::new(delta);
let mut attributes = Attributes::new();
// Enable each line split by "\n" remains the block attributes. for example:
// insert "\n" to "123456" at index 3
//
// [{"insert":"123"},{"insert":"\n","attributes":{"header":"1"}},
// {"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]
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_with_attributes(text, attributes)
.build(),
)
}
}

View File

@ -0,0 +1,90 @@
pub use auto_format::*;
pub use default_insert::*;
pub use preserve_inline_style::*;
pub use reset_format_on_new_line::*;
mod auto_format;
mod default_insert;
mod preserve_inline_style;
mod reset_format_on_new_line;
use crate::{
client::extensions::InsertExt,
core::{Delta, DeltaBuilder, DeltaIter, Operation},
};
pub struct PreserveBlockStyleOnInsertExt {}
impl InsertExt for PreserveBlockStyleOnInsertExt {
fn ext_name(&self) -> &str { "PreserveBlockStyleOnInsertExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
pub struct PreserveLineStyleOnSplitExt {}
impl InsertExt for PreserveLineStyleOnSplitExt {
fn ext_name(&self) -> &str { "PreserveLineStyleOnSplitExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
pub struct AutoExitBlockExt {}
impl InsertExt for AutoExitBlockExt {
fn ext_name(&self) -> &str { "AutoExitBlockExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
pub struct InsertEmbedsExt {}
impl InsertExt for InsertEmbedsExt {
fn ext_name(&self) -> &str { "InsertEmbedsExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
pub struct ForceNewlineForInsertsAroundEmbedExt {}
impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
fn ext_name(&self) -> &str { "ForceNewlineForInsertsAroundEmbedExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}

View File

@ -0,0 +1,51 @@
use crate::{
client::{
extensions::InsertExt,
util::{contain_newline, OpNewline},
},
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter},
};
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 contain_newline(text) {
return None;
}
let mut iter = DeltaIter::new(delta);
let prev = iter.next_op_before(index)?;
if OpNewline::parse(&prev).is_contain() {
return None;
}
let mut attributes = prev.get_attributes();
if attributes.is_empty() || !attributes.contains_key(&AttributeKey::Link) {
return Some(
DeltaBuilder::new()
.retain(index + replace_len)
.insert_with_attributes(text, attributes)
.build(),
);
}
let next = iter.next_op();
match &next {
None => attributes = Attributes::empty(),
Some(next) => {
if OpNewline::parse(&next).is_equal() {
attributes = Attributes::empty();
}
},
}
let new_delta = DeltaBuilder::new()
.retain(index + replace_len)
.insert_with_attributes(text, attributes)
.build();
return Some(new_delta);
}
}

View File

@ -0,0 +1,40 @@
use crate::{
client::{
extensions::{InsertExt, NEW_LINE},
util::is_newline,
},
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter},
};
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 !is_newline(text) {
return None;
}
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(index);
let next_op = iter.next()?;
if !next_op.get_data().starts_with(NEW_LINE) {
return None;
}
let mut reset_attribute = Attributes::new();
if next_op.get_attributes().contains_key(&AttributeKey::Header) {
reset_attribute.add(AttributeKey::Header.value(""));
}
let len = index + replace_len;
Some(
DeltaBuilder::new()
.retain(len)
.insert_with_attributes(NEW_LINE, next_op.get_attributes())
.retain_with_attributes(1, reset_attribute)
.trim()
.build(),
)
}
}

View File

@ -1,5 +1,15 @@
pub use delete::*;
pub use format::*;
pub use insert::*;
use crate::core::{Attribute, Delta, Interval};
mod delete;
mod format;
mod insert;
pub const NEW_LINE: &'static str = "\n";
pub type InsertExtension = Box<dyn InsertExt>;
pub type FormatExtension = Box<dyn FormatExt>;
pub type DeleteExtension = Box<dyn DeleteExt>;

View File

@ -2,5 +2,9 @@ mod document;
mod history;
mod view;
pub mod extensions;
mod util;
pub use document::*;
pub use history::*;
pub use view::*;

View File

@ -0,0 +1,66 @@
use crate::{client::extensions::NEW_LINE, core::Operation};
#[inline]
pub fn find_newline(s: &str) -> Option<usize> {
match s.find(NEW_LINE) {
None => None,
Some(line_break) => Some(line_break),
}
}
#[derive(PartialEq, Eq)]
pub enum OpNewline {
Start,
End,
Contain,
Equal,
NotFound,
}
impl OpNewline {
pub fn parse(op: &Operation) -> OpNewline {
let s = op.get_data();
if s == NEW_LINE {
return OpNewline::Equal;
}
if s.starts_with(NEW_LINE) {
return OpNewline::Start;
}
if s.ends_with(NEW_LINE) {
return OpNewline::End;
}
if s.contains(NEW_LINE) {
return OpNewline::Contain;
}
OpNewline::NotFound
}
pub fn is_start(&self) -> bool { self == &OpNewline::Start }
pub fn is_end(&self) -> bool { self == &OpNewline::End }
pub fn is_not_found(&self) -> bool { self == &OpNewline::NotFound }
pub fn is_contain(&self) -> bool {
self.is_start() || self.is_end() || self == &OpNewline::Contain
}
pub fn is_equal(&self) -> bool { self == &OpNewline::Equal }
}
#[inline]
pub fn is_op_contains_newline(op: &Operation) -> bool { contain_newline(op.get_data()) }
#[inline]
pub fn is_newline(s: &str) -> bool { s == NEW_LINE }
#[inline]
pub fn is_whitespace(s: &str) -> bool { s == " " }
#[inline]
pub fn contain_newline(s: &str) -> bool { s.contains(NEW_LINE) }

View File

@ -1,5 +1,5 @@
use super::extensions::*;
use crate::{
client::view::*,
core::{Attribute, Delta, Interval},
errors::{ErrorBuilder, OTError, OTErrorCode},
};
@ -86,7 +86,7 @@ fn construct_insert_exts() -> Vec<InsertExtension> {
Box::new(PreserveBlockStyleOnInsertExt {}),
Box::new(PreserveLineStyleOnSplitExt {}),
Box::new(ResetLineFormatOnNewLineExt {}),
Box::new(AutoFormatLinksExt {}),
Box::new(AutoFormatExt {}),
Box::new(PreserveInlineStylesExt {}),
Box::new(DefaultInsertExt {}),
]

View File

@ -1,173 +0,0 @@
use crate::{
client::view::{util::find_newline, FormatExt},
core::{
Attribute,
AttributeKey,
AttributeScope,
Attributes,
CharMetric,
Delta,
DeltaBuilder,
DeltaIter,
Interval,
Operation,
},
};
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;
}
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(interval.start);
let (before, after) = (iter.next_op_before(interval.size()), iter.next());
let mut start = interval.end;
let mut retain = 0;
if let Some(before) = before {
if before.contain_attribute(attribute) {
start -= before.len();
retain += before.len();
}
}
if let Some(after) = after {
if after.contain_attribute(attribute) {
if retain != 0 {
retain += after.len();
}
}
}
if retain == 0 {
return None;
}
Some(
DeltaBuilder::new()
.retain(start)
.retain_with_attributes(retain, (attribute.clone()).into())
.build(),
)
}
}
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 = 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_before(end - start).unwrap();
match find_newline(next_op.get_data()) {
None => new_delta.retain(next_op.len(), Attributes::empty()),
Some(_) => {
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Block);
new_delta.extend(tmp_delta);
},
}
start += next_op.len();
}
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.len(), 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;
}
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_before(end - start).unwrap();
match find_newline(next_op.get_data()) {
None => new_delta.retain(next_op.len(), attribute.clone().into()),
Some(_) => {
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Inline);
new_delta.extend(tmp_delta);
},
}
start += next_op.len();
}
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.len();
let mut s = op.get_data();
while let Some(line_break) = find_newline(s) {
match scope {
AttributeScope::Inline => {
new_delta.retain(line_break - start, attribute.clone().into());
new_delta.retain(1, Attributes::empty());
},
AttributeScope::Block => {
new_delta.retain(line_break - start, Attributes::empty());
new_delta.retain(1, attribute.clone().into());
},
_ => {
log::error!("Unsupported parser line break for {:?}", scope);
},
}
start = line_break + 1;
s = &s[start..s.len()];
}
if start < end {
match scope {
AttributeScope::Inline => new_delta.retain(end - start, attribute.clone().into()),
AttributeScope::Block => new_delta.retain(end - start, Attributes::empty()),
_ => log::error!("Unsupported parser line break for {:?}", scope),
}
}
new_delta
}

View File

@ -1,198 +0,0 @@
use crate::{
client::view::InsertExt,
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter},
};
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,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
pub struct PreserveLineStyleOnSplitExt {}
impl InsertExt for PreserveLineStyleOnSplitExt {
fn ext_name(&self) -> &str { "PreserveLineStyleOnSplitExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
pub struct AutoExitBlockExt {}
impl InsertExt for AutoExitBlockExt {
fn ext_name(&self) -> &str { "AutoExitBlockExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
pub struct InsertEmbedsExt {}
impl InsertExt for InsertEmbedsExt {
fn ext_name(&self) -> &str { "InsertEmbedsExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
pub struct ForceNewlineForInsertsAroundEmbedExt {}
impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
fn ext_name(&self) -> &str { "ForceNewlineForInsertsAroundEmbedExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
pub struct AutoFormatLinksExt {}
impl InsertExt for AutoFormatLinksExt {
fn ext_name(&self) -> &str { "AutoFormatLinksExt" }
fn apply(
&self,
_delta: &Delta,
_replace_len: usize,
_text: &str,
_index: usize,
) -> Option<Delta> {
None
}
}
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.contains(NEW_LINE) {
return None;
}
let mut iter = DeltaIter::new(delta);
let prev = iter.next_op_before(index)?;
if prev.get_data().contains(NEW_LINE) {
return None;
}
let mut attributes = prev.get_attributes();
if attributes.is_empty() || !attributes.contains_key(&AttributeKey::Link) {
return Some(
DeltaBuilder::new()
.retain(index + replace_len)
.insert_with_attributes(text, attributes)
.build(),
);
}
attributes.remove(&AttributeKey::Link);
let new_delta = DeltaBuilder::new()
.retain(index + replace_len)
.insert_with_attributes(text, attributes)
.build();
return Some(new_delta);
}
}
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;
}
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(index);
let next_op = iter.next()?;
if !next_op.get_data().starts_with(NEW_LINE) {
return None;
}
let mut reset_attribute = Attributes::new();
if next_op.get_attributes().contains_key(&AttributeKey::Header) {
reset_attribute.add(AttributeKey::Header.value(""));
}
let len = index + replace_len;
Some(
DeltaBuilder::new()
.retain(len)
.insert_with_attributes(NEW_LINE, next_op.get_attributes())
.retain_with_attributes(1, reset_attribute)
.trim()
.build(),
)
}
}
pub struct DefaultInsertExt {}
impl InsertExt for DefaultInsertExt {
fn ext_name(&self) -> &str { "DefaultInsertExt" }
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
let iter = DeltaIter::new(delta);
let mut attributes = Attributes::new();
// Enable each line split by "\n" remains the block attributes. for example:
// insert "\n" to "123456" at index 3
//
// [{"insert":"123"},{"insert":"\n","attributes":{"header":"1"}},
// {"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]
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_with_attributes(text, attributes)
.build(),
)
}
}

View File

@ -1,12 +0,0 @@
mod delete_ext;
mod extension;
mod format_ext;
mod insert_ext;
mod util;
mod view;
pub use delete_ext::*;
pub use extension::*;
pub use format_ext::*;
pub use insert_ext::*;
pub use view::*;

View File

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

View File

@ -3,6 +3,8 @@ pub mod helper;
use crate::helper::{TestOp::*, *};
use flowy_ot::core::Interval;
use flowy_ot::client::extensions::NEW_LINE;
#[test]
fn attributes_insert_text() {
let ops = vec![
@ -446,7 +448,7 @@ fn attributes_replace_with_text() {
}
#[test]
fn attributes_add_header() {
fn attributes_header_insert_newline_at_middle() {
let ops = vec![
Insert(0, "123456", 0),
Header(0, Interval::new(0, 6), 1, true),
@ -465,22 +467,7 @@ fn attributes_add_header() {
}
#[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() {
fn attributes_header_insert_newline_at_middle2() {
let ops = vec![
Insert(0, "123456", 0),
Header(0, Interval::new(0, 6), 1, true),
@ -503,3 +490,100 @@ fn attributes_header_add_newline_2() {
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn attributes_header_insert_newline_at_trailing() {
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_add_link() {
let ops = vec![
Insert(0, "123456", 0),
Link(0, Interval::new(0, 6), "https://appflowy.io", true),
AssertOpsJson(
0,
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn attributes_link_insert_char_at_head() {
let ops = vec![
Insert(0, "123456", 0),
Link(0, Interval::new(0, 6), "https://appflowy.io", true),
AssertOpsJson(
0,
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
),
Insert(0, "a", 0),
AssertOpsJson(
0,
r#"[{"insert":"a"},{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn attributes_link_insert_char_at_middle() {
let ops = vec![
Insert(0, "1256", 0),
Link(0, Interval::new(0, 4), "https://appflowy.io", true),
Insert(0, "34", 2),
AssertOpsJson(
0,
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn attributes_link_insert_char_at_trailing() {
let ops = vec![
Insert(0, "123456", 0),
Link(0, Interval::new(0, 6), "https://appflowy.io", true),
AssertOpsJson(
0,
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
),
Insert(0, "a", 6),
AssertOpsJson(
0,
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"a\n"}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn attributes_link_insert_newline_at_middle() {
let ops = vec![
Insert(0, "123456", 0),
Link(0, Interval::new(0, 6), "https://appflowy.io", true),
Insert(0, NEW_LINE, 3),
AssertOpsJson(
0,
r#"[{"insert":"123","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"},{"insert":"456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}

View File

@ -28,6 +28,9 @@ pub enum TestOp {
#[display(fmt = "Header")]
Header(usize, Interval, usize, bool),
#[display(fmt = "Link")]
Link(usize, Interval, &'static str, bool),
#[display(fmt = "Transform")]
Transform(usize, usize),
@ -74,44 +77,52 @@ impl OpTester {
let document = &mut self.documents[*delta_i];
document.insert(*index, s).unwrap();
},
TestOp::Delete(delta_i, interval) => {
TestOp::Delete(delta_i, iv) => {
let document = &mut self.documents[*delta_i];
document.replace(*interval, "").unwrap();
document.replace(*iv, "").unwrap();
},
TestOp::Replace(delta_i, interval, s) => {
TestOp::Replace(delta_i, iv, s) => {
let document = &mut self.documents[*delta_i];
document.replace(*interval, s).unwrap();
document.replace(*iv, s).unwrap();
},
TestOp::InsertBold(delta_i, s, interval) => {
TestOp::InsertBold(delta_i, s, iv) => {
let document = &mut self.documents[*delta_i];
document.insert(interval.start, s).unwrap();
document.insert(iv.start, s).unwrap();
document
.format(*interval, AttributeKey::Bold.value(true))
.format(*iv, AttributeKey::Bold.value(true))
.unwrap();
},
TestOp::Bold(delta_i, interval, enable) => {
TestOp::Bold(delta_i, iv, enable) => {
let document = &mut self.documents[*delta_i];
let attribute = match *enable {
true => AttributeKey::Bold.value(true),
false => AttributeKey::Bold.remove(),
};
document.format(*interval, attribute).unwrap();
document.format(*iv, attribute).unwrap();
},
TestOp::Italic(delta_i, interval, enable) => {
TestOp::Italic(delta_i, iv, enable) => {
let document = &mut self.documents[*delta_i];
let attribute = match *enable {
true => AttributeKey::Italic.value("true"),
false => AttributeKey::Italic.remove(),
};
document.format(*interval, attribute).unwrap();
document.format(*iv, attribute).unwrap();
},
TestOp::Header(delta_i, interval, level, enable) => {
TestOp::Header(delta_i, iv, 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(*iv, attribute).unwrap();
},
TestOp::Link(delta_i, iv, link, enable) => {
let document = &mut self.documents[*delta_i];
let attribute = match *enable {
true => AttributeKey::Link.value(link.to_owned()),
false => AttributeKey::Link.remove(),
};
document.format(*iv, attribute).unwrap();
},
TestOp::Transform(delta_a_i, delta_b_i) => {
let (a_prime, b_prime) = self.documents[*delta_a_i]

View File

@ -4,7 +4,7 @@ use crate::helper::{TestOp::*, *};
use flowy_ot::{client::RECORD_THRESHOLD, core::Interval};
#[test]
fn delta_undo_insert() {
fn history_undo_insert() {
let ops = vec![
Insert(0, "123", 0),
Undo(0),
@ -14,7 +14,7 @@ fn delta_undo_insert() {
}
#[test]
fn delta_undo_insert2() {
fn history_undo_insert2() {
let ops = vec![
Insert(0, "123", 0),
Wait(RECORD_THRESHOLD),
@ -28,7 +28,7 @@ fn delta_undo_insert2() {
}
#[test]
fn delta_redo_insert() {
fn history_redo_insert() {
let ops = vec![
Insert(0, "123", 0),
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
@ -41,7 +41,7 @@ fn delta_redo_insert() {
}
#[test]
fn delta_redo_insert_with_lagging() {
fn history_redo_insert_with_lagging() {
let ops = vec![
Insert(0, "123", 0),
Wait(RECORD_THRESHOLD),
@ -60,7 +60,7 @@ fn delta_redo_insert_with_lagging() {
}
#[test]
fn delta_undo_attributes() {
fn history_undo_attributes() {
let ops = vec![
Insert(0, "123", 0),
Bold(0, Interval::new(0, 3), true),
@ -71,7 +71,7 @@ fn delta_undo_attributes() {
}
#[test]
fn delta_undo_attributes_with_lagging() {
fn history_undo_attributes_with_lagging() {
let ops = vec![
Insert(0, "123", 0),
Wait(RECORD_THRESHOLD),
@ -83,7 +83,7 @@ fn delta_undo_attributes_with_lagging() {
}
#[test]
fn delta_redo_attributes() {
fn history_redo_attributes() {
let ops = vec![
Insert(0, "123", 0),
Bold(0, Interval::new(0, 3), true),
@ -99,7 +99,7 @@ fn delta_redo_attributes() {
}
#[test]
fn delta_redo_attributes_with_lagging() {
fn history_redo_attributes_with_lagging() {
let ops = vec![
Insert(0, "123", 0),
Wait(RECORD_THRESHOLD),
@ -116,7 +116,7 @@ fn delta_redo_attributes_with_lagging() {
}
#[test]
fn delta_undo_delete() {
fn history_undo_delete() {
let ops = vec![
Insert(0, "123", 0),
AssertOpsJson(0, r#"[{"insert":"123"}]"#),
@ -129,7 +129,7 @@ fn delta_undo_delete() {
}
#[test]
fn delta_undo_delete2() {
fn history_undo_delete2() {
let ops = vec![
Insert(0, "123", 0),
Bold(0, Interval::new(0, 3), true),
@ -148,7 +148,7 @@ fn delta_undo_delete2() {
}
#[test]
fn delta_undo_delete2_with_lagging() {
fn history_undo_delete2_with_lagging() {
let ops = vec![
Insert(0, "123", 0),
Wait(RECORD_THRESHOLD),
@ -175,7 +175,7 @@ fn delta_undo_delete2_with_lagging() {
}
#[test]
fn delta_redo_delete() {
fn history_redo_delete() {
let ops = vec![
Insert(0, "123", 0),
Wait(RECORD_THRESHOLD),
@ -189,7 +189,7 @@ fn delta_redo_delete() {
}
#[test]
fn delta_undo_replace() {
fn history_undo_replace() {
let ops = vec![
Insert(0, "123", 0),
Bold(0, Interval::new(0, 3), true),
@ -208,7 +208,7 @@ fn delta_undo_replace() {
}
#[test]
fn delta_undo_replace_with_lagging() {
fn history_undo_replace_with_lagging() {
let ops = vec![
Insert(0, "123", 0),
Wait(RECORD_THRESHOLD),
@ -232,7 +232,7 @@ fn delta_undo_replace_with_lagging() {
}
#[test]
fn delta_redo_replace() {
fn history_redo_replace() {
let ops = vec![
Insert(0, "123", 0),
Bold(0, Interval::new(0, 3), true),
@ -249,3 +249,21 @@ fn delta_redo_replace() {
];
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn history_undo_add_header() {
let ops = vec![
Insert(0, "123456", 0),
Header(0, Interval::new(0, 6), 1, true),
Insert(0, "\n", 3),
Insert(0, "\n", 4),
Undo(0),
Redo(0),
AssertOpsJson(
0,
r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":"1"}},{"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}