mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix invert attribute bugs
This commit is contained in:
parent
481f158a4a
commit
51ae139f89
@ -55,8 +55,8 @@ impl Document {
|
|||||||
enable: bool,
|
enable: bool,
|
||||||
) -> Result<(), OTError> {
|
) -> Result<(), OTError> {
|
||||||
let attributes = match enable {
|
let attributes = match enable {
|
||||||
true => AttrsBuilder::new().add_attribute(attribute).build(),
|
true => AttrsBuilder::new().add(attribute).build(),
|
||||||
false => AttrsBuilder::new().remove_attribute(attribute).build(),
|
false => AttrsBuilder::new().remove(&attribute).build(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.update_with_attribute(attributes, interval)
|
self.update_with_attribute(attributes, interval)
|
||||||
@ -161,7 +161,7 @@ impl Document {
|
|||||||
let new_attributes = match &mut attributes {
|
let new_attributes = match &mut attributes {
|
||||||
Attributes::Follow => old_attributes,
|
Attributes::Follow => old_attributes,
|
||||||
Attributes::Custom(attr_data) => {
|
Attributes::Custom(attr_data) => {
|
||||||
attr_data.merge(old_attributes.data());
|
attr_data.extend(old_attributes.data(), true);
|
||||||
attr_data.clone().into_attributes()
|
attr_data.clone().into_attributes()
|
||||||
},
|
},
|
||||||
Attributes::Empty => Attributes::Empty,
|
Attributes::Empty => Attributes::Empty,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::core::{should_remove, Operation};
|
use crate::core::{Attribute, AttributesData, AttributesRule, Operation};
|
||||||
use std::{collections::HashMap, fmt};
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@ -50,91 +50,6 @@ impl fmt::Display for Attributes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct AttributesData {
|
|
||||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
|
||||||
#[serde(flatten)]
|
|
||||||
inner: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AttributesData {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
AttributesData {
|
|
||||||
inner: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.inner.values().filter(|v| !should_remove(v)).count() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_empty(&mut self) { self.inner.retain(|_, v| !should_remove(v)); }
|
|
||||||
|
|
||||||
pub fn extend(&mut self, other: AttributesData) { self.inner.extend(other.inner); }
|
|
||||||
|
|
||||||
pub fn merge(&mut self, other: Option<AttributesData>) {
|
|
||||||
if other.is_none() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut new_attributes = other.unwrap().inner;
|
|
||||||
self.inner.iter().for_each(|(k, v)| {
|
|
||||||
if should_remove(v) {
|
|
||||||
new_attributes.remove(k);
|
|
||||||
} else {
|
|
||||||
new_attributes.insert(k.clone(), v.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.inner = new_attributes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AttributesDataRule {
|
|
||||||
fn apply_rule(&mut self);
|
|
||||||
|
|
||||||
fn into_attributes(self) -> Attributes;
|
|
||||||
}
|
|
||||||
impl AttributesDataRule for AttributesData {
|
|
||||||
fn apply_rule(&mut self) { self.remove_empty(); }
|
|
||||||
|
|
||||||
fn into_attributes(mut self) -> Attributes {
|
|
||||||
self.apply_rule();
|
|
||||||
|
|
||||||
if self.is_empty() {
|
|
||||||
Attributes::Empty
|
|
||||||
} else {
|
|
||||||
Attributes::Custom(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AttributesRule {
|
|
||||||
fn apply_rule(self) -> Attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AttributesRule for Attributes {
|
|
||||||
fn apply_rule(self) -> Attributes {
|
|
||||||
match self {
|
|
||||||
Attributes::Follow => self,
|
|
||||||
Attributes::Custom(data) => data.into_attributes(),
|
|
||||||
Attributes::Empty => self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::convert::From<HashMap<String, String>> for AttributesData {
|
|
||||||
fn from(attributes: HashMap<String, String>) -> Self { AttributesData { inner: attributes } }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for AttributesData {
|
|
||||||
type Target = HashMap<String, String>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::DerefMut for AttributesData {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
|
pub(crate) fn attributes_from(operation: &Option<Operation>) -> Option<Attributes> {
|
||||||
match operation {
|
match operation {
|
||||||
None => None,
|
None => None,
|
||||||
@ -180,6 +95,7 @@ pub fn transform_operation(left: &Option<Operation>, right: &Option<Operation>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes {
|
pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes {
|
||||||
|
log::info!("Invert attributes: {:?} : {:?}", attr, base);
|
||||||
let attr = attr.data();
|
let attr = attr.data();
|
||||||
let base = base.data();
|
let base = base.data();
|
||||||
|
|
||||||
@ -213,7 +129,7 @@ pub fn merge_attributes(attributes: Attributes, other: Attributes) -> Attributes
|
|||||||
match (&attributes, &other) {
|
match (&attributes, &other) {
|
||||||
(Attributes::Custom(data), Attributes::Custom(o_data)) => {
|
(Attributes::Custom(data), Attributes::Custom(o_data)) => {
|
||||||
let mut data = data.clone();
|
let mut data = data.clone();
|
||||||
data.extend(o_data.clone());
|
data.extend(Some(o_data.clone()), false);
|
||||||
Attributes::Custom(data)
|
Attributes::Custom(data)
|
||||||
},
|
},
|
||||||
(Attributes::Custom(data), _) => Attributes::Custom(data.clone()),
|
(Attributes::Custom(data), _) => Attributes::Custom(data.clone()),
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use crate::core::{Attributes, AttributesData};
|
use crate::core::{Attributes, AttributesData};
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
const REMOVE_FLAG: &'static str = "";
|
|
||||||
pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG }
|
|
||||||
|
|
||||||
#[derive(Clone, Display)]
|
#[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum Attribute {
|
pub enum Attribute {
|
||||||
#[display(fmt = "bold")]
|
#[display(fmt = "bold")]
|
||||||
Bold,
|
Bold,
|
||||||
@ -22,29 +21,27 @@ impl AttrsBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_attribute(mut self, attribute: Attribute) -> Self {
|
pub fn add(mut self, attribute: Attribute) -> Self {
|
||||||
self.inner
|
self.inner.add(attribute);
|
||||||
.insert(format!("{}", attribute), "true".to_owned());
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_attribute(mut self, attribute: Attribute) -> Self {
|
pub fn remove(mut self, attribute: &Attribute) -> Self {
|
||||||
self.inner
|
self.inner.remove(attribute);
|
||||||
.insert(format!("{}", attribute), REMOVE_FLAG.to_owned());
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bold(self, bold: bool) -> Self {
|
pub fn bold(self, bold: bool) -> Self {
|
||||||
match bold {
|
match bold {
|
||||||
true => self.add_attribute(Attribute::Bold),
|
true => self.add(Attribute::Bold),
|
||||||
false => self.remove_attribute(Attribute::Bold),
|
false => self.remove(&Attribute::Bold),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn italic(self, italic: bool) -> Self {
|
pub fn italic(self, italic: bool) -> Self {
|
||||||
match italic {
|
match italic {
|
||||||
true => self.add_attribute(Attribute::Italic),
|
true => self.add(Attribute::Italic),
|
||||||
false => self.remove_attribute(Attribute::Italic),
|
false => self.remove(&Attribute::Italic),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
95
rust-lib/flowy-ot/src/core/attributes/data.rs
Normal file
95
rust-lib/flowy-ot/src/core/attributes/data.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
use crate::core::{Attribute, Attributes};
|
||||||
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
|
pub(crate) const REMOVE_FLAG: &'static str = "";
|
||||||
|
pub(crate) fn should_remove(s: &str) -> bool { s == REMOVE_FLAG }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct AttributesData {
|
||||||
|
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub(crate) inner: HashMap<Attribute, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttributesData {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
AttributesData {
|
||||||
|
inner: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.inner.values().filter(|v| !should_remove(v)).count() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, attribute: &Attribute) {
|
||||||
|
self.inner.insert(attribute.clone(), REMOVE_FLAG.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, attribute: Attribute) { self.inner.insert(attribute, "true".to_owned()); }
|
||||||
|
|
||||||
|
pub fn extend(&mut self, other: Option<AttributesData>, prune: bool) {
|
||||||
|
if other.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if prune {
|
||||||
|
let mut new_attributes = other.unwrap().inner;
|
||||||
|
self.inner.iter().for_each(|(k, v)| {
|
||||||
|
if should_remove(v) {
|
||||||
|
new_attributes.remove(k);
|
||||||
|
} else {
|
||||||
|
new_attributes.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.inner = new_attributes;
|
||||||
|
} else {
|
||||||
|
self.inner.extend(other.unwrap().inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AttributesDataRule {
|
||||||
|
fn apply_rule(&mut self);
|
||||||
|
|
||||||
|
fn into_attributes(self) -> Attributes;
|
||||||
|
}
|
||||||
|
impl AttributesDataRule for AttributesData {
|
||||||
|
fn apply_rule(&mut self) { self.inner.retain(|_, v| !should_remove(v)); }
|
||||||
|
|
||||||
|
fn into_attributes(mut self) -> Attributes {
|
||||||
|
self.apply_rule();
|
||||||
|
|
||||||
|
if self.is_empty() {
|
||||||
|
Attributes::Empty
|
||||||
|
} else {
|
||||||
|
Attributes::Custom(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AttributesRule {
|
||||||
|
fn apply_rule(self) -> Attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttributesRule for Attributes {
|
||||||
|
fn apply_rule(self) -> Attributes {
|
||||||
|
match self {
|
||||||
|
Attributes::Follow => self,
|
||||||
|
Attributes::Custom(data) => data.into_attributes(),
|
||||||
|
Attributes::Empty => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// impl std::convert::From<HashMap<String, String>> for AttributesData {
|
||||||
|
// fn from(attributes: HashMap<String, String>) -> Self { AttributesData {
|
||||||
|
// inner: attributes } } }
|
||||||
|
|
||||||
|
impl std::ops::Deref for AttributesData {
|
||||||
|
type Target = HashMap<Attribute, String>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for AttributesData {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
mod attributes;
|
mod attributes;
|
||||||
mod builder;
|
mod builder;
|
||||||
|
mod data;
|
||||||
|
|
||||||
pub use attributes::*;
|
pub use attributes::*;
|
||||||
pub use builder::*;
|
pub use builder::*;
|
||||||
|
pub use data::*;
|
||||||
|
@ -515,6 +515,7 @@ impl Delta {
|
|||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
for op in &self.ops {
|
for op in &self.ops {
|
||||||
let len: usize = op.length() as usize;
|
let len: usize = op.length() as usize;
|
||||||
|
log::info!("{:?}", op);
|
||||||
match op {
|
match op {
|
||||||
Operation::Delete(_) => {
|
Operation::Delete(_) => {
|
||||||
inverted_from_other(&mut inverted, op, index, index + len);
|
inverted_from_other(&mut inverted, op, index, index + len);
|
||||||
@ -600,7 +601,7 @@ impl Delta {
|
|||||||
Attributes::Custom(data) => {
|
Attributes::Custom(data) => {
|
||||||
if interval.contains_range(offset, offset + end) {
|
if interval.contains_range(offset, offset + end) {
|
||||||
log::debug!("Get attributes from op: {:?} at {:?}", op, interval);
|
log::debug!("Get attributes from op: {:?} at {:?}", op, interval);
|
||||||
attributes_data.extend(data.clone());
|
attributes_data.extend(Some(data.clone()), false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Attributes::Empty => {},
|
Attributes::Empty => {},
|
||||||
|
@ -54,7 +54,7 @@ impl Operation {
|
|||||||
pub fn has_attribute(&self) -> bool {
|
pub fn has_attribute(&self) -> bool {
|
||||||
match self.get_attributes() {
|
match self.get_attributes() {
|
||||||
Attributes::Follow => false,
|
Attributes::Follow => false,
|
||||||
Attributes::Custom(data) => data.is_empty(),
|
Attributes::Custom(data) => !data.is_empty(),
|
||||||
Attributes::Empty => false,
|
Attributes::Empty => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ impl OpTester {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
std::env::set_var("RUST_LOG", "info");
|
std::env::set_var("RUST_LOG", "debug");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
pub mod helper;
|
pub mod helper;
|
||||||
|
|
||||||
use crate::helper::{TestOp::*, *};
|
use crate::helper::{TestOp::*, *};
|
||||||
|
use flowy_ot::core::Interval;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delta_undo_insert() {
|
fn delta_undo_insert() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
//
|
|
||||||
Insert(0, "\n", 0),
|
Insert(0, "\n", 0),
|
||||||
Insert(0, "123", 0),
|
Insert(0, "123", 0),
|
||||||
Undo(0),
|
Undo(0),
|
||||||
@ -59,3 +59,15 @@ fn delta_redo_insert2() {
|
|||||||
];
|
];
|
||||||
OpTester::new().run_script(ops);
|
OpTester::new().run_script(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delta_undo_attributes() {
|
||||||
|
let ops = vec![
|
||||||
|
Insert(0, "\n", 0),
|
||||||
|
Insert(0, "123", 0),
|
||||||
|
Bold(0, Interval::new(0, 3), true),
|
||||||
|
Undo(0),
|
||||||
|
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
||||||
|
];
|
||||||
|
OpTester::new().run_script(ops);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user