fix invert attribute bugs

This commit is contained in:
appflowy 2021-08-07 09:19:18 +08:00
parent 481f158a4a
commit 51ae139f89
9 changed files with 130 additions and 107 deletions

View File

@ -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,

View File

@ -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()),

View File

@ -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),
} }
} }

View 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 }
}

View File

@ -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::*;

View File

@ -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 => {},

View File

@ -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,
} }
} }

View File

@ -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();
}); });

View File

@ -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);
}