mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add rich_text crate
This commit is contained in:
parent
2f413a8e67
commit
8a9a23ddbe
@ -13,7 +13,7 @@ use flowy_document_infra::{
|
||||
entities::ws::{WsDataType, WsDocumentData},
|
||||
protobuf::{Doc, RevId, RevType, Revision, RevisionRange, UpdateDocParams},
|
||||
};
|
||||
use lib_ot::core::{OperationTransformable, RichTextDelta};
|
||||
use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
|
||||
use parking_lot::RwLock;
|
||||
use protobuf::Message;
|
||||
use sqlx::PgPool;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::document::helper::{DocScript, DocumentTest};
|
||||
use flowy_document_infra::core::{Document, FlowyDoc};
|
||||
use lib_ot::core::{Interval, RichTextAttribute};
|
||||
use lib_ot::{core::Interval, rich_text::RichTextAttribute};
|
||||
|
||||
#[rustfmt::skip]
|
||||
// ┌─────────┐ ┌─────────┐
|
||||
|
@ -12,8 +12,9 @@ use tokio::time::{sleep, Duration};
|
||||
// use crate::helper::*;
|
||||
use crate::util::helper::{spawn_server, TestServer};
|
||||
use flowy_document_infra::{entities::doc::DocIdentifier, protobuf::UpdateDocParams};
|
||||
use lib_ot::core::{RichTextAttribute, RichTextDelta, Interval};
|
||||
use lib_ot::rich_text::{RichTextAttribute, RichTextDelta};
|
||||
use parking_lot::RwLock;
|
||||
use lib_ot::core::Interval;
|
||||
|
||||
pub struct DocumentTest {
|
||||
server: TestServer,
|
||||
|
@ -17,7 +17,10 @@ use flowy_document_infra::{
|
||||
errors::DocumentResult,
|
||||
};
|
||||
use lib_infra::retry::{ExponentialBackoff, Retry};
|
||||
use lib_ot::core::{Interval, RichTextAttribute, RichTextDelta};
|
||||
use lib_ot::{
|
||||
core::Interval,
|
||||
rich_text::{RichTextAttribute, RichTextDelta},
|
||||
};
|
||||
use lib_ws::WsConnectState;
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
use tokio::sync::{mpsc, mpsc::UnboundedSender, oneshot};
|
||||
|
@ -6,7 +6,10 @@ use flowy_document_infra::{
|
||||
errors::DocumentError,
|
||||
};
|
||||
use futures::stream::StreamExt;
|
||||
use lib_ot::core::{Interval, OperationTransformable, RichTextAttribute, RichTextDelta};
|
||||
use lib_ot::{
|
||||
core::{Interval, OperationTransformable},
|
||||
rich_text::{RichTextAttribute, RichTextDelta},
|
||||
};
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
use tokio::sync::{mpsc, oneshot, RwLock};
|
||||
|
||||
|
@ -8,7 +8,7 @@ use flowy_document_infra::{
|
||||
util::RevIdCounter,
|
||||
};
|
||||
use lib_infra::future::ResultFuture;
|
||||
use lib_ot::core::{OperationTransformable, RichTextDelta};
|
||||
use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
|
@ -9,7 +9,10 @@ use flowy_database::{ConnectionPool, SqliteConnection};
|
||||
use flowy_document_infra::entities::doc::{revision_from_doc, Doc, RevId, RevType, Revision, RevisionRange};
|
||||
use futures::stream::StreamExt;
|
||||
use lib_infra::future::ResultFuture;
|
||||
use lib_ot::core::{Operation, OperationTransformable, RichTextDelta};
|
||||
use lib_ot::{
|
||||
core::{Operation, OperationTransformable},
|
||||
rich_text::RichTextDelta,
|
||||
};
|
||||
use std::{collections::VecDeque, sync::Arc, time::Duration};
|
||||
use tokio::{
|
||||
sync::{broadcast, mpsc, RwLock},
|
||||
|
@ -1,8 +1,9 @@
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
use crate::editor::{TestBuilder, TestOp::*};
|
||||
use flowy_document_infra::core::{FlowyDoc, PlainDoc};
|
||||
use lib_ot::core::{Interval, OperationTransformable, NEW_LINE, WHITESPACE, FlowyStr, RichTextDelta};
|
||||
use lib_ot::core::{Interval, OperationTransformable, NEW_LINE, WHITESPACE, FlowyStr};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use lib_ot::rich_text::RichTextDelta;
|
||||
|
||||
#[test]
|
||||
fn attributes_bold_added() {
|
||||
|
@ -6,7 +6,10 @@ mod undo_redo_test;
|
||||
|
||||
use derive_more::Display;
|
||||
use flowy_document_infra::core::{CustomDocument, Document};
|
||||
use lib_ot::core::*;
|
||||
use lib_ot::{
|
||||
core::*,
|
||||
rich_text::{RichTextAttribute, RichTextAttributes, RichTextDelta},
|
||||
};
|
||||
use rand::{prelude::*, Rng as WrappedRng};
|
||||
use std::{sync::Once, time::Duration};
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
#![allow(clippy::all)]
|
||||
use crate::editor::{Rng, TestBuilder, TestOp::*};
|
||||
use flowy_document_infra::core::{FlowyDoc, PlainDoc};
|
||||
use lib_ot::core::*;
|
||||
use lib_ot::{
|
||||
core::*,
|
||||
rich_text::{AttributeBuilder, RichTextAttribute, RichTextAttributes, RichTextDelta},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn attributes_insert_text() {
|
||||
|
@ -1,5 +1,8 @@
|
||||
use flowy_document_infra::core::{Document, PlainDoc};
|
||||
use lib_ot::core::*;
|
||||
use lib_ot::{
|
||||
core::*,
|
||||
rich_text::{AttributeBuilder, RichTextAttribute, RichTextAttributeValue, RichTextDelta},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn operation_insert_serialize_test() {
|
||||
|
@ -6,7 +6,10 @@ use crate::{
|
||||
errors::DocumentError,
|
||||
user_default::doc_initial_delta,
|
||||
};
|
||||
use lib_ot::core::*;
|
||||
use lib_ot::{
|
||||
core::*,
|
||||
rich_text::{RichTextAttribute, RichTextDelta},
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub trait CustomDocument {
|
||||
|
@ -1,5 +1,8 @@
|
||||
use crate::core::extensions::DeleteExt;
|
||||
use lib_ot::core::{DeltaBuilder, Interval, RichTextDelta};
|
||||
use lib_ot::{
|
||||
core::{DeltaBuilder, Interval},
|
||||
rich_text::RichTextDelta,
|
||||
};
|
||||
|
||||
pub struct DefaultDelete {}
|
||||
impl DeleteExt for DefaultDelete {
|
||||
|
@ -1,13 +1,7 @@
|
||||
use crate::{core::extensions::DeleteExt, util::is_newline};
|
||||
use lib_ot::core::{
|
||||
plain_attributes,
|
||||
Attributes,
|
||||
CharMetric,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
Interval,
|
||||
RichTextDelta,
|
||||
NEW_LINE,
|
||||
use lib_ot::{
|
||||
core::{Attributes, CharMetric, DeltaBuilder, DeltaIter, Interval, NEW_LINE},
|
||||
rich_text::{plain_attributes, RichTextDelta},
|
||||
};
|
||||
|
||||
pub struct PreserveLineFormatOnMerge {}
|
||||
|
@ -1,5 +1,8 @@
|
||||
use crate::util::find_newline;
|
||||
use lib_ot::core::{plain_attributes, AttributeScope, RichTextAttribute, RichTextDelta, RichTextOperation};
|
||||
use lib_ot::{
|
||||
core::RichTextOperation,
|
||||
rich_text::{plain_attributes, AttributeScope, RichTextAttribute, RichTextDelta},
|
||||
};
|
||||
|
||||
pub(crate) fn line_break(
|
||||
op: &RichTextOperation,
|
||||
|
@ -2,14 +2,9 @@ use crate::{
|
||||
core::extensions::{format::helper::line_break, FormatExt},
|
||||
util::find_newline,
|
||||
};
|
||||
use lib_ot::core::{
|
||||
plain_attributes,
|
||||
AttributeScope,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
Interval,
|
||||
RichTextAttribute,
|
||||
RichTextDelta,
|
||||
use lib_ot::{
|
||||
core::{DeltaBuilder, DeltaIter, Interval},
|
||||
rich_text::{plain_attributes, AttributeScope, RichTextAttribute, RichTextDelta},
|
||||
};
|
||||
|
||||
pub struct ResolveBlockFormat {}
|
||||
|
@ -2,7 +2,10 @@ use crate::{
|
||||
core::extensions::{format::helper::line_break, FormatExt},
|
||||
util::find_newline,
|
||||
};
|
||||
use lib_ot::core::{AttributeScope, DeltaBuilder, DeltaIter, Interval, RichTextAttribute, RichTextDelta};
|
||||
use lib_ot::{
|
||||
core::{DeltaBuilder, DeltaIter, Interval},
|
||||
rich_text::{AttributeScope, RichTextAttribute, RichTextDelta},
|
||||
};
|
||||
|
||||
pub struct ResolveInlineFormat {}
|
||||
impl FormatExt for ResolveInlineFormat {
|
||||
|
@ -1,11 +1,7 @@
|
||||
use crate::{core::extensions::InsertExt, util::is_newline};
|
||||
use lib_ot::core::{
|
||||
attributes_except_header,
|
||||
is_empty_line_at_index,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
RichTextAttributeKey,
|
||||
RichTextDelta,
|
||||
use lib_ot::{
|
||||
core::{is_empty_line_at_index, DeltaBuilder, DeltaIter},
|
||||
rich_text::{attributes_except_header, RichTextAttributeKey, RichTextDelta},
|
||||
};
|
||||
|
||||
pub struct AutoExitBlock {}
|
||||
|
@ -1,12 +1,7 @@
|
||||
use crate::{core::extensions::InsertExt, util::is_whitespace};
|
||||
use lib_ot::core::{
|
||||
count_utf16_code_units,
|
||||
plain_attributes,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
RichTextAttribute,
|
||||
RichTextAttributes,
|
||||
RichTextDelta,
|
||||
use lib_ot::{
|
||||
core::{count_utf16_code_units, DeltaBuilder, DeltaIter},
|
||||
rich_text::{plain_attributes, RichTextAttribute, RichTextAttributes, RichTextDelta},
|
||||
};
|
||||
use std::cmp::min;
|
||||
use url::Url;
|
||||
|
@ -1,12 +1,7 @@
|
||||
use crate::core::extensions::InsertExt;
|
||||
use lib_ot::core::{
|
||||
Attributes,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
RichTextAttributeKey,
|
||||
RichTextAttributes,
|
||||
RichTextDelta,
|
||||
NEW_LINE,
|
||||
use lib_ot::{
|
||||
core::{Attributes, DeltaBuilder, DeltaIter, NEW_LINE},
|
||||
rich_text::{RichTextAttributeKey, RichTextAttributes, RichTextDelta},
|
||||
};
|
||||
|
||||
pub struct DefaultInsertAttribute {}
|
||||
|
@ -2,7 +2,7 @@ use crate::core::extensions::InsertExt;
|
||||
pub use auto_exit_block::*;
|
||||
pub use auto_format::*;
|
||||
pub use default_insert::*;
|
||||
use lib_ot::core::RichTextDelta;
|
||||
use lib_ot::rich_text::RichTextDelta;
|
||||
pub use preserve_block_format::*;
|
||||
pub use preserve_inline_format::*;
|
||||
pub use reset_format_on_new_line::*;
|
||||
|
@ -1,14 +1,14 @@
|
||||
use crate::{core::extensions::InsertExt, util::is_newline};
|
||||
use lib_ot::core::{
|
||||
attributes_except_header,
|
||||
plain_attributes,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
RichTextAttribute,
|
||||
RichTextAttributeKey,
|
||||
RichTextAttributes,
|
||||
RichTextDelta,
|
||||
NEW_LINE,
|
||||
use lib_ot::{
|
||||
core::{DeltaBuilder, DeltaIter, NEW_LINE},
|
||||
rich_text::{
|
||||
attributes_except_header,
|
||||
plain_attributes,
|
||||
RichTextAttribute,
|
||||
RichTextAttributeKey,
|
||||
RichTextAttributes,
|
||||
RichTextDelta,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct PreserveBlockFormatOnInsert {}
|
||||
|
@ -2,14 +2,9 @@ use crate::{
|
||||
core::extensions::InsertExt,
|
||||
util::{contain_newline, is_newline},
|
||||
};
|
||||
use lib_ot::core::{
|
||||
plain_attributes,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
OpNewline,
|
||||
RichTextAttributeKey,
|
||||
RichTextDelta,
|
||||
NEW_LINE,
|
||||
use lib_ot::{
|
||||
core::{DeltaBuilder, DeltaIter, OpNewline, NEW_LINE},
|
||||
rich_text::{plain_attributes, RichTextAttributeKey, RichTextDelta},
|
||||
};
|
||||
|
||||
pub struct PreserveInlineFormat {}
|
||||
|
@ -1,12 +1,7 @@
|
||||
use crate::{core::extensions::InsertExt, util::is_newline};
|
||||
use lib_ot::core::{
|
||||
CharMetric,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
RichTextAttributeKey,
|
||||
RichTextAttributes,
|
||||
RichTextDelta,
|
||||
NEW_LINE,
|
||||
use lib_ot::{
|
||||
core::{CharMetric, DeltaBuilder, DeltaIter, NEW_LINE},
|
||||
rich_text::{RichTextAttributeKey, RichTextAttributes, RichTextDelta},
|
||||
};
|
||||
|
||||
pub struct ResetLineFormatOnNewLine {}
|
||||
|
@ -2,7 +2,10 @@ pub use delete::*;
|
||||
pub use format::*;
|
||||
pub use insert::*;
|
||||
|
||||
use lib_ot::core::{Interval, RichTextAttribute, RichTextDelta};
|
||||
use lib_ot::{
|
||||
core::Interval,
|
||||
rich_text::{RichTextAttribute, RichTextDelta},
|
||||
};
|
||||
|
||||
mod delete;
|
||||
mod format;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use lib_ot::core::RichTextDelta;
|
||||
use lib_ot::rich_text::RichTextDelta;
|
||||
|
||||
const MAX_UNDOS: usize = 20;
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
use crate::core::extensions::*;
|
||||
use lib_ot::{
|
||||
core::{trim, Interval, RichTextAttribute, RichTextDelta},
|
||||
core::{trim, Interval},
|
||||
errors::{ErrorBuilder, OTError, OTErrorCode},
|
||||
rich_text::{RichTextAttribute, RichTextDelta},
|
||||
};
|
||||
|
||||
pub const RECORD_THRESHOLD: usize = 400; // in milliseconds
|
||||
|
@ -1,5 +1,5 @@
|
||||
use flowy_derive::ProtoBuf;
|
||||
use lib_ot::{core::RichTextDelta, errors::OTError};
|
||||
use lib_ot::{errors::OTError, rich_text::RichTextDelta};
|
||||
|
||||
#[derive(ProtoBuf, Default, Debug, Clone)]
|
||||
pub struct CreateDocParams {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{entities::doc::Doc, util::md5};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use lib_ot::core::RichTextDelta;
|
||||
use lib_ot::rich_text::RichTextDelta;
|
||||
use std::{fmt::Formatter, ops::RangeInclusive};
|
||||
|
||||
#[derive(Debug, ProtoBuf_Enum, Clone, Eq, PartialEq)]
|
||||
|
@ -1,4 +1,4 @@
|
||||
use lib_ot::core::{DeltaBuilder, RichTextDelta};
|
||||
use lib_ot::{core::DeltaBuilder, rich_text::RichTextDelta};
|
||||
|
||||
#[inline]
|
||||
pub fn doc_initial_delta() -> RichTextDelta { DeltaBuilder::new().insert("\n").build() }
|
||||
|
@ -1,170 +0,0 @@
|
||||
use crate::{
|
||||
core::{
|
||||
Attributes,
|
||||
OperationTransformable,
|
||||
RichTextAttribute,
|
||||
RichTextAttributeKey,
|
||||
RichTextAttributeValue,
|
||||
RichTextOperation,
|
||||
},
|
||||
errors::OTError,
|
||||
};
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct RichTextAttributes {
|
||||
pub(crate) inner: HashMap<RichTextAttributeKey, RichTextAttributeValue>,
|
||||
}
|
||||
|
||||
impl std::default::Default for RichTextAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: HashMap::with_capacity(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RichTextAttributes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("{:?}", self.inner)) }
|
||||
}
|
||||
|
||||
pub fn plain_attributes() -> RichTextAttributes { RichTextAttributes::default() }
|
||||
|
||||
impl RichTextAttributes {
|
||||
pub fn new() -> Self { RichTextAttributes { inner: HashMap::new() } }
|
||||
|
||||
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
|
||||
|
||||
pub fn add(&mut self, attribute: RichTextAttribute) {
|
||||
let RichTextAttribute { key, value, scope: _ } = attribute;
|
||||
self.inner.insert(key, value);
|
||||
}
|
||||
|
||||
pub fn add_kv(&mut self, key: RichTextAttributeKey, value: RichTextAttributeValue) {
|
||||
self.inner.insert(key, value);
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, key: &RichTextAttributeKey) {
|
||||
self.inner.insert(key.clone(), RichTextAttributeValue(None));
|
||||
}
|
||||
|
||||
pub fn mark_all_as_removed_except(&mut self, attribute: Option<RichTextAttributeKey>) {
|
||||
match attribute {
|
||||
None => {
|
||||
self.inner.iter_mut().for_each(|(_k, v)| v.0 = None);
|
||||
},
|
||||
Some(attribute) => {
|
||||
self.inner.iter_mut().for_each(|(k, v)| {
|
||||
if k != &attribute {
|
||||
v.0 = None;
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: RichTextAttributeKey) { self.inner.retain(|k, _| k != &key); }
|
||||
|
||||
// pub fn block_attributes_except_header(attributes: &Attributes) -> Attributes
|
||||
// { let mut new_attributes = Attributes::new();
|
||||
// attributes.iter().for_each(|(k, v)| {
|
||||
// if k != &AttributeKey::Header {
|
||||
// new_attributes.insert(k.clone(), v.clone());
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// new_attributes
|
||||
// }
|
||||
|
||||
// Update inner by constructing new attributes from the other if it's
|
||||
// not None and replace the key/value with self key/value.
|
||||
pub fn merge(&mut self, other: Option<RichTextAttributes>) {
|
||||
if other.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut new_attributes = other.unwrap().inner;
|
||||
self.inner.iter().for_each(|(k, v)| {
|
||||
new_attributes.insert(k.clone(), v.clone());
|
||||
});
|
||||
self.inner = new_attributes;
|
||||
}
|
||||
}
|
||||
|
||||
impl Attributes for RichTextAttributes {
|
||||
fn is_empty(&self) -> bool { self.inner.is_empty() }
|
||||
|
||||
fn remove_empty(&mut self) { self.inner.retain(|_, v| v.0.is_some()); }
|
||||
|
||||
fn extend_other(&mut self, other: Self) { self.inner.extend(other.inner); }
|
||||
}
|
||||
|
||||
impl OperationTransformable for RichTextAttributes {
|
||||
fn compose(&self, other: &Self) -> Result<Self, OTError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut attributes = self.clone();
|
||||
attributes.extend_other(other.clone());
|
||||
Ok(attributes)
|
||||
}
|
||||
|
||||
fn transform(&self, other: &Self) -> Result<(Self, Self), OTError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let a = self
|
||||
.iter()
|
||||
.fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
|
||||
if !other.contains_key(k) {
|
||||
new_attributes.insert(k.clone(), v.clone());
|
||||
}
|
||||
new_attributes
|
||||
});
|
||||
|
||||
let b = other
|
||||
.iter()
|
||||
.fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
|
||||
if !self.contains_key(k) {
|
||||
new_attributes.insert(k.clone(), v.clone());
|
||||
}
|
||||
new_attributes
|
||||
});
|
||||
|
||||
Ok((a, b))
|
||||
}
|
||||
|
||||
fn invert(&self, other: &Self) -> Self {
|
||||
let base_inverted = other.iter().fold(RichTextAttributes::new(), |mut attributes, (k, v)| {
|
||||
if other.get(k) != self.get(k) && self.contains_key(k) {
|
||||
attributes.insert(k.clone(), v.clone());
|
||||
}
|
||||
attributes
|
||||
});
|
||||
|
||||
let inverted = self.iter().fold(base_inverted, |mut attributes, (k, _)| {
|
||||
if other.get(k) != self.get(k) && !other.contains_key(k) {
|
||||
attributes.delete(k);
|
||||
}
|
||||
attributes
|
||||
});
|
||||
|
||||
inverted
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RichTextAttributes {
|
||||
type Target = HashMap<RichTextAttributeKey, RichTextAttributeValue>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for RichTextAttributes {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
||||
}
|
||||
|
||||
pub fn attributes_except_header(op: &RichTextOperation) -> RichTextAttributes {
|
||||
let mut attributes = op.get_attributes();
|
||||
attributes.remove(RichTextAttributeKey::Header);
|
||||
attributes
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
core::{attributes::*, operation::*, DeltaIter, FlowyStr, Interval, OperationTransformable, MAX_IV_LEN},
|
||||
core::{operation::*, DeltaIter, FlowyStr, Interval, OperationTransformable, MAX_IV_LEN},
|
||||
errors::{ErrorBuilder, OTError, OTErrorCode},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
@ -469,8 +469,6 @@ fn transform_op_attribute<T: Attributes>(left: &Option<Operation<T>>, right: &Op
|
||||
left.transform(&right).unwrap().0
|
||||
}
|
||||
|
||||
pub type RichTextDelta = Delta<RichTextAttributes>;
|
||||
|
||||
impl<T> Delta<T>
|
||||
where
|
||||
T: Attributes + DeserializeOwned,
|
||||
@ -503,22 +501,31 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RichTextDelta {
|
||||
impl<T> FromStr for Delta<T>
|
||||
where
|
||||
T: Attributes,
|
||||
{
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<RichTextDelta, Self::Err> {
|
||||
fn from_str(s: &str) -> Result<Delta<T>, Self::Err> {
|
||||
let mut delta = Delta::with_capacity(1);
|
||||
delta.add(Operation::Insert(s.into()));
|
||||
Ok(delta)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Vec<u8>> for RichTextDelta {
|
||||
impl<T> std::convert::TryFrom<Vec<u8>> for Delta<T>
|
||||
where
|
||||
T: Attributes + DeserializeOwned,
|
||||
{
|
||||
type Error = OTError;
|
||||
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { Delta::from_bytes(bytes) }
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Bytes> for RichTextDelta {
|
||||
impl<T> std::convert::TryFrom<Bytes> for Delta<T>
|
||||
where
|
||||
T: Attributes + DeserializeOwned,
|
||||
{
|
||||
type Error = OTError;
|
||||
|
||||
fn try_from(bytes: Bytes) -> Result<Self, Self::Error> { Delta::from_bytes(&bytes) }
|
||||
|
@ -1,5 +1,8 @@
|
||||
use super::cursor::*;
|
||||
use crate::core::{Attributes, Delta, Interval, Operation, RichTextAttributes, NEW_LINE};
|
||||
use crate::{
|
||||
core::{Attributes, Delta, Interval, Operation, NEW_LINE},
|
||||
rich_text::RichTextAttributes,
|
||||
};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize;
|
||||
|
@ -1,11 +1,9 @@
|
||||
mod attributes;
|
||||
mod delta;
|
||||
mod flowy_str;
|
||||
mod interval;
|
||||
mod operation;
|
||||
|
||||
use crate::errors::OTError;
|
||||
pub use attributes::*;
|
||||
pub use delta::*;
|
||||
pub use flowy_str::*;
|
||||
pub use interval::*;
|
||||
|
@ -1,4 +1,7 @@
|
||||
use crate::core::{Attributes, Operation, RichTextAttributes};
|
||||
use crate::{
|
||||
core::{Attributes, Operation},
|
||||
rich_text::RichTextAttributes,
|
||||
};
|
||||
|
||||
pub type RichTextOpBuilder = OpBuilder<RichTextAttributes>;
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
use crate::core::{FlowyStr, Interval, OpBuilder, OperationTransformable, RichTextAttribute, RichTextAttributes};
|
||||
use crate::{
|
||||
core::{FlowyStr, Interval, OpBuilder, OperationTransformable},
|
||||
rich_text::{RichTextAttribute, RichTextAttributes},
|
||||
};
|
||||
use serde::__private::Formatter;
|
||||
use std::{
|
||||
cmp::min,
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod core;
|
||||
pub mod errors;
|
||||
pub mod rich_text;
|
||||
|
@ -1,11 +1,179 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{block_attribute, core::RichTextAttributes, ignore_attribute, inline_attribute, list_attribute};
|
||||
use crate::{
|
||||
block_attribute,
|
||||
core::{Attributes, OperationTransformable, RichTextOperation},
|
||||
errors::OTError,
|
||||
ignore_attribute,
|
||||
inline_attribute,
|
||||
list_attribute,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
fmt::Formatter,
|
||||
iter::FromIterator,
|
||||
};
|
||||
use strum_macros::Display;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct RichTextAttributes {
|
||||
pub(crate) inner: HashMap<RichTextAttributeKey, RichTextAttributeValue>,
|
||||
}
|
||||
|
||||
impl std::default::Default for RichTextAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: HashMap::with_capacity(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RichTextAttributes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("{:?}", self.inner)) }
|
||||
}
|
||||
|
||||
pub fn plain_attributes() -> RichTextAttributes { RichTextAttributes::default() }
|
||||
|
||||
impl RichTextAttributes {
|
||||
pub fn new() -> Self { RichTextAttributes { inner: HashMap::new() } }
|
||||
|
||||
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
|
||||
|
||||
pub fn add(&mut self, attribute: RichTextAttribute) {
|
||||
let RichTextAttribute { key, value, scope: _ } = attribute;
|
||||
self.inner.insert(key, value);
|
||||
}
|
||||
|
||||
pub fn add_kv(&mut self, key: RichTextAttributeKey, value: RichTextAttributeValue) {
|
||||
self.inner.insert(key, value);
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, key: &RichTextAttributeKey) {
|
||||
self.inner.insert(key.clone(), RichTextAttributeValue(None));
|
||||
}
|
||||
|
||||
pub fn mark_all_as_removed_except(&mut self, attribute: Option<RichTextAttributeKey>) {
|
||||
match attribute {
|
||||
None => {
|
||||
self.inner.iter_mut().for_each(|(_k, v)| v.0 = None);
|
||||
},
|
||||
Some(attribute) => {
|
||||
self.inner.iter_mut().for_each(|(k, v)| {
|
||||
if k != &attribute {
|
||||
v.0 = None;
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: RichTextAttributeKey) { self.inner.retain(|k, _| k != &key); }
|
||||
|
||||
// pub fn block_attributes_except_header(attributes: &Attributes) -> Attributes
|
||||
// { let mut new_attributes = Attributes::new();
|
||||
// attributes.iter().for_each(|(k, v)| {
|
||||
// if k != &AttributeKey::Header {
|
||||
// new_attributes.insert(k.clone(), v.clone());
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// new_attributes
|
||||
// }
|
||||
|
||||
// Update inner by constructing new attributes from the other if it's
|
||||
// not None and replace the key/value with self key/value.
|
||||
pub fn merge(&mut self, other: Option<RichTextAttributes>) {
|
||||
if other.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut new_attributes = other.unwrap().inner;
|
||||
self.inner.iter().for_each(|(k, v)| {
|
||||
new_attributes.insert(k.clone(), v.clone());
|
||||
});
|
||||
self.inner = new_attributes;
|
||||
}
|
||||
}
|
||||
|
||||
impl Attributes for RichTextAttributes {
|
||||
fn is_empty(&self) -> bool { self.inner.is_empty() }
|
||||
|
||||
fn remove_empty(&mut self) { self.inner.retain(|_, v| v.0.is_some()); }
|
||||
|
||||
fn extend_other(&mut self, other: Self) { self.inner.extend(other.inner); }
|
||||
}
|
||||
|
||||
impl OperationTransformable for RichTextAttributes {
|
||||
fn compose(&self, other: &Self) -> Result<Self, OTError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut attributes = self.clone();
|
||||
attributes.extend_other(other.clone());
|
||||
Ok(attributes)
|
||||
}
|
||||
|
||||
fn transform(&self, other: &Self) -> Result<(Self, Self), OTError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let a = self
|
||||
.iter()
|
||||
.fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
|
||||
if !other.contains_key(k) {
|
||||
new_attributes.insert(k.clone(), v.clone());
|
||||
}
|
||||
new_attributes
|
||||
});
|
||||
|
||||
let b = other
|
||||
.iter()
|
||||
.fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
|
||||
if !self.contains_key(k) {
|
||||
new_attributes.insert(k.clone(), v.clone());
|
||||
}
|
||||
new_attributes
|
||||
});
|
||||
|
||||
Ok((a, b))
|
||||
}
|
||||
|
||||
fn invert(&self, other: &Self) -> Self {
|
||||
let base_inverted = other.iter().fold(RichTextAttributes::new(), |mut attributes, (k, v)| {
|
||||
if other.get(k) != self.get(k) && self.contains_key(k) {
|
||||
attributes.insert(k.clone(), v.clone());
|
||||
}
|
||||
attributes
|
||||
});
|
||||
|
||||
let inverted = self.iter().fold(base_inverted, |mut attributes, (k, _)| {
|
||||
if other.get(k) != self.get(k) && !other.contains_key(k) {
|
||||
attributes.delete(k);
|
||||
}
|
||||
attributes
|
||||
});
|
||||
|
||||
inverted
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RichTextAttributes {
|
||||
type Target = HashMap<RichTextAttributeKey, RichTextAttributeValue>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for RichTextAttributes {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
||||
}
|
||||
|
||||
pub fn attributes_except_header(op: &RichTextOperation) -> RichTextAttributes {
|
||||
let mut attributes = op.get_attributes();
|
||||
attributes.remove(RichTextAttributeKey::Header);
|
||||
attributes
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RichTextAttribute {
|
||||
pub key: RichTextAttributeKey,
|
@ -1,6 +1,5 @@
|
||||
#[rustfmt::skip]
|
||||
use crate::core::RichTextAttributeValue;
|
||||
use crate::core::{RichTextAttribute, RichTextAttributeKey, RichTextAttributes};
|
||||
use crate::rich_text::{RichTextAttribute, RichTextAttributeKey, RichTextAttributes, RichTextAttributeValue};
|
||||
use serde::{
|
||||
de,
|
||||
de::{MapAccess, Visitor},
|
@ -1,5 +1,6 @@
|
||||
#![allow(non_snake_case)]
|
||||
use crate::core::{RichTextAttribute, RichTextAttributes};
|
||||
use crate::rich_text::{RichTextAttribute, RichTextAttributes};
|
||||
|
||||
pub struct AttributeBuilder {
|
||||
inner: RichTextAttributes,
|
||||
}
|
3
shared-lib/lib-ot/src/rich_text/delta.rs
Normal file
3
shared-lib/lib-ot/src/rich_text/delta.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use crate::{core::Delta, rich_text::RichTextAttributes};
|
||||
|
||||
pub type RichTextDelta = Delta<RichTextAttributes>;
|
@ -1,12 +1,11 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
mod attribute;
|
||||
mod attributes;
|
||||
mod attributes_serde;
|
||||
mod builder;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod delta;
|
||||
|
||||
pub use attribute::*;
|
||||
pub use attributes::*;
|
||||
pub use builder::*;
|
||||
pub use delta::*;
|
0
shared-lib/lib-ot/tests/main.rs
Normal file
0
shared-lib/lib-ot/tests/main.rs
Normal file
Loading…
Reference in New Issue
Block a user