mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add auto format link test
This commit is contained in:
parent
41eacb7000
commit
0fb808ef4c
@ -69,6 +69,7 @@ class Document {
|
|||||||
length: replaceLength,
|
length: replaceLength,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
print('current document delta: $_delta');
|
||||||
print('insert delta: $delta');
|
print('insert delta: $delta');
|
||||||
compose(delta, ChangeSource.LOCAL);
|
compose(delta, ChangeSource.LOCAL);
|
||||||
print('compose insert, current document $_delta');
|
print('compose insert, current document $_delta');
|
||||||
@ -79,6 +80,7 @@ class Document {
|
|||||||
assert(index >= 0 && length > 0);
|
assert(index >= 0 && length > 0);
|
||||||
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) {
|
||||||
|
print('current document delta: $_delta');
|
||||||
compose(delta, ChangeSource.LOCAL);
|
compose(delta, ChangeSource.LOCAL);
|
||||||
print('compose delete, current document $_delta');
|
print('compose delete, current document $_delta');
|
||||||
}
|
}
|
||||||
@ -124,6 +126,7 @@ class Document {
|
|||||||
attribute: attribute,
|
attribute: attribute,
|
||||||
);
|
);
|
||||||
if (formatDelta.isNotEmpty) {
|
if (formatDelta.isNotEmpty) {
|
||||||
|
print('current document delta: $_delta');
|
||||||
compose(formatDelta, ChangeSource.LOCAL);
|
compose(formatDelta, ChangeSource.LOCAL);
|
||||||
print('compose format, current document $_delta');
|
print('compose format, current document $_delta');
|
||||||
delta = delta.compose(formatDelta);
|
delta = delta.compose(formatDelta);
|
||||||
|
@ -14,6 +14,7 @@ log = "0.4"
|
|||||||
color-eyre = { version = "0.5", default-features = false }
|
color-eyre = { version = "0.5", default-features = false }
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
url = "2.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
@ -7,19 +7,83 @@ pub struct AutoFormatExt {}
|
|||||||
impl InsertExt for AutoFormatExt {
|
impl InsertExt for AutoFormatExt {
|
||||||
fn ext_name(&self) -> &str { "AutoFormatExt" }
|
fn ext_name(&self) -> &str { "AutoFormatExt" }
|
||||||
|
|
||||||
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> {
|
||||||
// enter whitespace to trigger auto format
|
// enter whitespace to trigger auto format
|
||||||
if !is_whitespace(text) {
|
if !is_whitespace(text) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut iter = DeltaIter::new(delta);
|
let mut iter = DeltaIter::new(delta);
|
||||||
iter.seek::<CharMetric>(index);
|
if let Some(prev) = iter.next_op_before(index) {
|
||||||
let prev = iter.next_op();
|
match AutoFormat::parse(prev.get_data()) {
|
||||||
if prev.is_none() {
|
None => {},
|
||||||
return None;
|
Some(formatter) => {
|
||||||
}
|
let mut new_attributes = prev.get_attributes();
|
||||||
|
|
||||||
let _prev = prev.unwrap();
|
// format_len should not greater than index. The url crate will add "/" to the
|
||||||
|
// end of input string that causes the format_len greater than the input string
|
||||||
|
let format_len = min(index, formatter.format_len());
|
||||||
|
|
||||||
|
let format_attributes = formatter.to_attributes();
|
||||||
|
format_attributes.iter().for_each(|(k, v)| {
|
||||||
|
if !new_attributes.contains_key(k) {
|
||||||
|
new_attributes.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let next_attributes = match iter.next_op() {
|
||||||
|
None => Attributes::empty(),
|
||||||
|
Some(op) => op.get_attributes(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return Some(
|
||||||
|
DeltaBuilder::new()
|
||||||
|
.retain(index + replace_len - min(index, format_len))
|
||||||
|
.retain_with_attributes(format_len, format_attributes)
|
||||||
|
.insert_with_attributes(text, next_attributes)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
client::extensions::NEW_LINE,
|
||||||
|
core::{Attribute, AttributeBuilder, Attributes, DeltaBuilder, Operation},
|
||||||
|
};
|
||||||
|
use bytecount::num_chars;
|
||||||
|
use std::cmp::min;
|
||||||
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
|
pub enum AutoFormatter {
|
||||||
|
Url(Url),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AutoFormatter {
|
||||||
|
pub fn to_attributes(&self) -> Attributes {
|
||||||
|
match self {
|
||||||
|
AutoFormatter::Url(url) => AttributeBuilder::new().link(url.as_str(), true).build(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_len(&self) -> usize {
|
||||||
|
let s = match self {
|
||||||
|
AutoFormatter::Url(url) => url.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
num_chars(s.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AutoFormat {}
|
||||||
|
impl AutoFormat {
|
||||||
|
fn parse(s: &str) -> Option<AutoFormatter> {
|
||||||
|
if let Ok(url) = Url::parse(s) {
|
||||||
|
return Some(AutoFormatter::Url(url));
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ mod format;
|
|||||||
mod insert;
|
mod insert;
|
||||||
|
|
||||||
pub const NEW_LINE: &'static str = "\n";
|
pub const NEW_LINE: &'static str = "\n";
|
||||||
|
pub const WHITESPACE: &'static str = " ";
|
||||||
|
|
||||||
pub type InsertExtension = Box<dyn InsertExt>;
|
pub type InsertExtension = Box<dyn InsertExt>;
|
||||||
pub type FormatExtension = Box<dyn FormatExt>;
|
pub type FormatExtension = Box<dyn FormatExt>;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use crate::{client::extensions::NEW_LINE, core::Operation};
|
use crate::{
|
||||||
|
client::extensions::{NEW_LINE, WHITESPACE},
|
||||||
|
core::Operation,
|
||||||
|
};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn find_newline(s: &str) -> Option<usize> {
|
pub fn find_newline(s: &str) -> Option<usize> {
|
||||||
@ -60,7 +63,7 @@ pub fn is_op_contains_newline(op: &Operation) -> bool { contain_newline(op.get_d
|
|||||||
pub fn is_newline(s: &str) -> bool { s == NEW_LINE }
|
pub fn is_newline(s: &str) -> bool { s == NEW_LINE }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_whitespace(s: &str) -> bool { s == " " }
|
pub fn is_whitespace(s: &str) -> bool { s == WHITESPACE }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contain_newline(s: &str) -> bool { s.contains(NEW_LINE) }
|
pub fn contain_newline(s: &str) -> bool { s.contains(NEW_LINE) }
|
||||||
|
@ -96,7 +96,7 @@ impl std::convert::Into<Attributes> for Attribute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AttrsBuilder {
|
pub struct AttributeBuilder {
|
||||||
inner: Attributes,
|
inner: Attributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,19 @@ macro_rules! impl_bool_attribute {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttrsBuilder {
|
macro_rules! impl_str_attribute {
|
||||||
|
($name: ident,$key: expr) => {
|
||||||
|
pub fn $name(self, s: &str, value: bool) -> Self {
|
||||||
|
let value = match value {
|
||||||
|
true => s,
|
||||||
|
false => REMOVE_FLAG,
|
||||||
|
};
|
||||||
|
self.insert($key, value)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttributeBuilder {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Attributes::default(),
|
inner: Attributes::default(),
|
||||||
@ -134,10 +146,12 @@ impl AttrsBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AttributeBuilder::new().bold(true).build()
|
||||||
impl_bool_attribute!(bold, AttributeKey::Bold);
|
impl_bool_attribute!(bold, AttributeKey::Bold);
|
||||||
impl_bool_attribute!(italic, AttributeKey::Italic);
|
impl_bool_attribute!(italic, AttributeKey::Italic);
|
||||||
impl_bool_attribute!(underline, AttributeKey::Underline);
|
impl_bool_attribute!(underline, AttributeKey::Underline);
|
||||||
impl_bool_attribute!(strike_through, AttributeKey::StrikeThrough);
|
impl_bool_attribute!(strike_through, AttributeKey::StrikeThrough);
|
||||||
|
impl_str_attribute!(link, AttributeKey::Link);
|
||||||
|
|
||||||
pub fn build(self) -> Attributes { self.inner }
|
pub fn build(self) -> Attributes { self.inner }
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ pub mod helper;
|
|||||||
use crate::helper::{TestOp::*, *};
|
use crate::helper::{TestOp::*, *};
|
||||||
use flowy_ot::core::Interval;
|
use flowy_ot::core::Interval;
|
||||||
|
|
||||||
use flowy_ot::client::extensions::NEW_LINE;
|
use flowy_ot::client::extensions::{NEW_LINE, WHITESPACE};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn attributes_insert_text() {
|
fn attributes_insert_text() {
|
||||||
@ -587,3 +587,51 @@ fn attributes_link_insert_newline_at_middle() {
|
|||||||
|
|
||||||
OpTester::new().run_script_with_newline(ops);
|
OpTester::new().run_script_with_newline(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attributes_auto_format_link() {
|
||||||
|
let site = "https://appflowy.io";
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, site, 0),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
|
||||||
|
Insert(0, WHITESPACE, site.len()),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
OpTester::new().run_script_with_newline(ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attributes_auto_format_exist_link() {
|
||||||
|
let site = "https://appflowy.io";
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, site, 0),
|
||||||
|
Link(0, Interval::new(0, site.len()), site, true),
|
||||||
|
Insert(0, WHITESPACE, site.len()),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
OpTester::new().run_script_with_newline(ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attributes_auto_format_exist_link2() {
|
||||||
|
let site = "https://appflowy.io";
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, site, 0),
|
||||||
|
Link(0, Interval::new(0, site.len() / 2), site, true),
|
||||||
|
Insert(0, WHITESPACE, site.len()),
|
||||||
|
AssertOpsJson(
|
||||||
|
0,
|
||||||
|
r#"[{"insert":"https://a","attributes":{"link":"https://appflowy.io"}},{"insert":"ppflowy.io \n"}]"#,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
OpTester::new().run_script_with_newline(ops);
|
||||||
|
}
|
||||||
|
@ -3,6 +3,8 @@ use flowy_ot::{client::Document, core::*};
|
|||||||
use rand::{prelude::*, Rng as WrappedRng};
|
use rand::{prelude::*, Rng as WrappedRng};
|
||||||
use std::{sync::Once, time::Duration};
|
use std::{sync::Once, time::Duration};
|
||||||
|
|
||||||
|
const LEVEL: &'static str = "info";
|
||||||
|
|
||||||
#[derive(Clone, Debug, Display)]
|
#[derive(Clone, Debug, Display)]
|
||||||
pub enum TestOp {
|
pub enum TestOp {
|
||||||
#[display(fmt = "Insert")]
|
#[display(fmt = "Insert")]
|
||||||
@ -63,7 +65,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", LEVEL);
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ fn delta_seek_4() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn delta_seek_5() {
|
fn delta_seek_5() {
|
||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
|
let attributes = AttributeBuilder::new().bold(true).italic(true).build();
|
||||||
delta.add(
|
delta.add(
|
||||||
OpBuilder::insert("1234")
|
OpBuilder::insert("1234")
|
||||||
.attributes(attributes.clone())
|
.attributes(attributes.clone())
|
||||||
@ -469,7 +469,7 @@ fn transform2() {
|
|||||||
fn delta_transform_test() {
|
fn delta_transform_test() {
|
||||||
let mut a = Delta::default();
|
let mut a = Delta::default();
|
||||||
let mut a_s = String::new();
|
let mut a_s = String::new();
|
||||||
a.insert("123", AttrsBuilder::new().bold(true).build());
|
a.insert("123", AttributeBuilder::new().bold(true).build());
|
||||||
a_s = a.apply(&a_s).unwrap();
|
a_s = a.apply(&a_s).unwrap();
|
||||||
assert_eq!(&a_s, "123");
|
assert_eq!(&a_s, "123");
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use flowy_ot::core::*;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn operation_insert_serialize_test() {
|
fn operation_insert_serialize_test() {
|
||||||
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
|
let attributes = AttributeBuilder::new().bold(true).italic(true).build();
|
||||||
let operation = OpBuilder::insert("123").attributes(attributes).build();
|
let operation = OpBuilder::insert("123").attributes(attributes).build();
|
||||||
let json = serde_json::to_string(&operation).unwrap();
|
let json = serde_json::to_string(&operation).unwrap();
|
||||||
eprintln!("{}", json);
|
eprintln!("{}", json);
|
||||||
@ -32,7 +32,7 @@ fn operation_delete_serialize_test() {
|
|||||||
fn delta_serialize_test() {
|
fn delta_serialize_test() {
|
||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
|
|
||||||
let attributes = AttrsBuilder::new().bold(true).italic(true).build();
|
let attributes = AttributeBuilder::new().bold(true).italic(true).build();
|
||||||
let retain = OpBuilder::insert("123").attributes(attributes).build();
|
let retain = OpBuilder::insert("123").attributes(attributes).build();
|
||||||
|
|
||||||
delta.add(retain);
|
delta.add(retain);
|
||||||
|
Loading…
Reference in New Issue
Block a user