refactor: add linear mode for text

This commit is contained in:
Zixuan Chen 2024-08-07 23:42:15 +08:00
parent a9c6c32b3e
commit af274eac79
No known key found for this signature in database
7 changed files with 604 additions and 353 deletions

View file

@ -433,6 +433,31 @@ impl<V: DeltaValue, Attr: DeltaAttr> DeltaRope<V, Attr> {
.insert_many_by_cursor(Some(pos.cursor), values.into_iter());
}
pub fn insert_value(&mut self, pos: usize, value: V, attr: Attr) {
if self.len() < pos {
self.push_retain(pos - self.len(), Default::default());
}
if pos == self.len() {
self.tree.push(DeltaItem::Replace {
value,
attr,
delete: 0,
});
return;
}
let pos = self.tree.query::<LengthFinder>(&pos).unwrap();
self.tree.insert_by_path(
pos.cursor,
DeltaItem::Replace {
value,
attr,
delete: 0,
},
);
}
fn update_attr_in_range(&mut self, range: Range<usize>, attr: &Attr) {
if range.start == range.end || self.is_empty() {
return;

View file

@ -59,6 +59,17 @@ impl<V: DeltaValue, Attr: DeltaAttr> DeltaRope<V, Attr> {
// trace!("Composed {:#?}", &self);
}
pub fn delete(&mut self, mut index: usize, len: usize) {
self._compose_replace(
DeltaReplace {
value: &Default::default(),
attr: &Default::default(),
delete: len,
},
&mut index,
);
}
fn _compose_replace(
&mut self,
delta_replace_item @ DeltaReplace {
@ -70,8 +81,11 @@ impl<V: DeltaValue, Attr: DeltaAttr> DeltaRope<V, Attr> {
) {
let mut should_insert = this_value.rle_len() > 0;
let mut left_del_len = delete;
if delete > 0 {
assert!(*index < self.len());
if *index > self.len() {
self.push_retain(*index - self.len(), Attr::default());
}
if delete > 0 && *index != self.len() {
let range = *index..(*index + left_del_len).min(self.len());
let from = self.tree.query::<LengthFinder>(&range.start).unwrap();
let to = self.tree.query::<LengthFinder>(&range.end).unwrap();

View file

@ -397,11 +397,21 @@ pub(crate) enum RichtextStateChunk {
},
}
impl Default for RichtextStateChunk {
fn default() -> Self {
Self::new_empty()
}
}
impl RichtextStateChunk {
pub fn new_text(s: BytesSlice, id: IdFull) -> Self {
Self::Text(TextChunk::new(s, id))
}
pub fn new_empty() -> Self {
Self::Text(TextChunk::new_empty())
}
pub fn new_style(style: Arc<StyleOp>, anchor_type: AnchorType) -> Self {
Self::Style { style, anchor_type }
}
@ -450,6 +460,8 @@ impl RichtextStateChunk {
}
}
impl loro_delta::delta_trait::DeltaValue for RichtextStateChunk {}
impl DeltaValue for RichtextStateChunk {
fn value_extend(&mut self, other: Self) -> Result<(), Self> {
Err(other)
@ -1066,7 +1078,6 @@ mod cursor_cache {
};
use generic_btree::{rle::HasLength, BTree, Cursor, LeafIndex};
#[derive(Debug, Clone)]
struct CursorCacheItem {
pos: usize,

View file

@ -598,7 +598,7 @@ impl Tracker {
self._checkout(from, false);
self._checkout(to, true);
// self.id_to_cursor.diagnose();
// tracing::trace!("Trace::diff {:#?}, ", &self);
tracing::trace!("Trace::diff {:#?}, ", &self);
self.rope.get_diff()
}

View file

@ -15,6 +15,7 @@ use loro_common::{
CompactIdLp, ContainerID, Counter, HasCounterSpan, HasIdSpan, IdFull, IdLp, IdSpan, LoroValue,
PeerID, ID,
};
use loro_delta::DeltaRope;
use smallvec::SmallVec;
use tracing::{instrument, trace, warn};
@ -161,6 +162,7 @@ impl DiffCalculator {
if *has_all {
use_persisted_shortcut = true;
trace!("use persisted shortcut");
}
}
}
@ -184,6 +186,7 @@ impl DiffCalculator {
tracing::debug!("LCA: {:?} mode={:?}", &lca, diff_mode);
let mut started_set = FxHashSet::default();
for (change, start_counter, vv) in iter {
let end_counter = *merged.get(&change.id.peer).unwrap();
if let DiffCalculatorRetainMode::Persist { has_all, last_vv } =
&mut self.retain_mode
{
@ -197,7 +200,7 @@ impl DiffCalculator {
);
}
last_vv.extend_to_include_end_id(change.id_end());
last_vv.extend_to_include_end_id(ID::new(change.id.peer, end_counter));
}
}
@ -206,9 +209,8 @@ impl DiffCalculator {
.binary_search_by(|op| op.ctr_last().cmp(&start_counter))
.unwrap_or_else(|e| e);
let mut visited = FxHashSet::default();
let end_counter = merged.get(&change.id.peer).unwrap();
for mut op in &change.ops.vec()[iter_start..] {
if op.counter >= *end_counter {
if op.counter >= end_counter {
break;
}
@ -226,7 +228,7 @@ impl DiffCalculator {
continue;
}
if op.counter < start_counter || op.ctr_end() > *end_counter {
if op.counter < start_counter || op.ctr_end() > end_counter {
stack_sliced_op = Some(op.slice(
(start_counter as usize).saturating_sub(op.counter as usize),
op.atom_len().min((end_counter - op.counter) as usize),
@ -391,7 +393,7 @@ impl DiffCalculator {
.or_insert_with(|| match idx.get_type() {
crate::ContainerType::Text => (
depth,
ContainerDiffCalculator::Richtext(RichtextDiffCalculator::default()),
ContainerDiffCalculator::Richtext(RichtextDiffCalculator::new()),
),
crate::ContainerType::Map => (
depth,
@ -764,19 +766,45 @@ impl DiffCalculatorTrait for ListDiffCalculator {
}
}
#[derive(Debug, Default)]
#[derive(Debug)]
pub(crate) struct RichtextDiffCalculator {
start_vv: VersionVector,
mode: Box<RichtextCalcMode>,
}
#[derive(Debug)]
enum RichtextCalcMode {
Crdt {
tracker: Box<RichtextTracker>,
styles: Vec<StyleOp>,
start_vv: VersionVector,
},
Linear {
diff: DeltaRope<RichtextStateChunk, ()>,
last_style_start: Option<(Arc<StyleOp>, u32)>,
},
}
impl RichtextDiffCalculator {
pub fn new() -> Self {
Self {
mode: Box::new(RichtextCalcMode::Crdt {
tracker: Box::new(RichtextTracker::new_with_unknown()),
styles: Vec::new(),
start_vv: VersionVector::new(),
}),
}
}
/// This should be called after calc_diff
///
/// TODO: Refactor, this can be simplified
pub fn get_id_latest_pos(&self, id: ID) -> Option<AbsolutePosition> {
self.tracker.get_target_id_latest_index_at_new_version(id)
match &*self.mode {
RichtextCalcMode::Crdt { tracker, .. } => {
tracker.get_target_id_latest_index_at_new_version(id)
}
RichtextCalcMode::Linear { .. } => unreachable!(),
}
}
}
@ -787,13 +815,42 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
vv: &crate::VersionVector,
mode: DiffMode,
) {
if !vv.includes_vv(&self.start_vv) || !self.tracker.all_vv().includes_vv(vv) {
self.tracker = Box::new(RichtextTracker::new_with_unknown());
self.styles.clear();
self.start_vv = vv.clone();
match mode {
DiffMode::Linear => {
self.mode = Box::new(RichtextCalcMode::Linear {
diff: DeltaRope::new(),
last_style_start: None,
});
}
_ => {
if !matches!(&*self.mode, RichtextCalcMode::Crdt { .. }) {
self.mode = Box::new(RichtextCalcMode::Crdt {
tracker: Box::new(RichtextTracker::new_with_unknown()),
styles: Vec::new(),
start_vv: vv.clone(),
});
return;
}
}
}
self.tracker.checkout(vv);
match &mut *self.mode {
RichtextCalcMode::Crdt {
tracker,
styles,
start_vv,
} => {
if !vv.includes_vv(start_vv) || tracker.all_vv().includes_vv(vv) {
*tracker = Box::new(RichtextTracker::new_with_unknown());
styles.clear();
*start_vv = vv.clone();
}
tracker.checkout(vv);
}
RichtextCalcMode::Linear { .. } => {}
}
}
fn apply_change(
@ -802,12 +859,16 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
op: crate::op::RichOp,
vv: Option<&crate::VersionVector>,
) {
if let Some(vv) = vv {
self.tracker.checkout(vv);
}
match &op.raw_op().content {
trace!("apply_change: {:?}", op.id());
match &mut *self.mode {
RichtextCalcMode::Linear {
diff,
last_style_start,
} => match &op.raw_op().content {
crate::op::InnerContent::List(l) => match l {
InnerListOp::Insert { .. } | InnerListOp::Move { .. } | InnerListOp::Set { .. } => {
InnerListOp::Insert { .. }
| InnerListOp::Move { .. }
| InnerListOp::Set { .. } => {
unreachable!()
}
crate::container::list::list_op::InnerListOp::InsertText {
@ -816,14 +877,84 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
unicode_len: len,
pos,
} => {
self.tracker.insert(
let s = oplog.arena.slice_by_unicode(
*unicode_start as usize..(*unicode_start + *len) as usize,
);
diff.insert_value(
*pos as usize,
RichtextStateChunk::new_text(s, op.id_full()),
(),
);
}
crate::container::list::list_op::InnerListOp::Delete(del) => {
diff.delete(del.start() as usize, del.atom_len());
}
crate::container::list::list_op::InnerListOp::StyleStart {
start,
end,
key,
info,
value,
} => {
debug_assert!(start < end, "start: {}, end: {}", start, end);
let style_op = Arc::new(StyleOp {
lamport: op.lamport(),
peer: op.peer,
cnt: op.id_start().counter,
key: key.clone(),
value: value.clone(),
info: *info,
});
*last_style_start = Some((style_op.clone(), *end));
diff.insert_value(
*start as usize,
RichtextStateChunk::new_style(style_op, AnchorType::Start),
(),
);
}
crate::container::list::list_op::InnerListOp::StyleEnd => {
let (style_op, pos) = last_style_start.take().unwrap();
assert_eq!(style_op.peer, op.peer);
assert_eq!(style_op.cnt, op.id_start().counter - 1);
diff.insert_value(
pos as usize + 1,
RichtextStateChunk::new_style(style_op, AnchorType::End),
(),
);
}
},
_ => unreachable!(),
},
RichtextCalcMode::Crdt {
tracker,
styles,
start_vv,
} => {
if let Some(vv) = vv {
tracker.checkout(vv);
}
match &op.raw_op().content {
crate::op::InnerContent::List(l) => match l {
InnerListOp::Insert { .. }
| InnerListOp::Move { .. }
| InnerListOp::Set { .. } => {
unreachable!()
}
crate::container::list::list_op::InnerListOp::InsertText {
slice: _,
unicode_start,
unicode_len: len,
pos,
} => {
tracker.insert(
op.id_full(),
*pos as usize,
RichtextChunk::new_text(*unicode_start..*unicode_start + *len),
);
}
crate::container::list::list_op::InnerListOp::Delete(del) => {
self.tracker.delete(
tracker.delete(
op.id_start(),
del.id_start,
del.start() as usize,
@ -839,8 +970,8 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
value,
} => {
debug_assert!(start < end, "start: {}, end: {}", start, end);
let style_id = self.styles.len();
self.styles.push(StyleOp {
let style_id = styles.len();
styles.push(StyleOp {
lamport: op.lamport(),
peer: op.peer,
cnt: op.id_start().counter,
@ -848,7 +979,7 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
value: value.clone(),
info: *info,
});
self.tracker.insert(
tracker.insert(
op.id_full(),
*start as usize,
RichtextChunk::new_style_anchor(style_id as u32, AnchorType::Start),
@ -868,12 +999,14 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
else {
unreachable!()
};
let style_id = match self.styles.last() {
Some(last) if last.peer == id.peer && last.cnt == id.counter - 1 => {
self.styles.len() - 1
let style_id = match styles.last() {
Some(last)
if last.peer == id.peer && last.cnt == id.counter - 1 =>
{
styles.len() - 1
}
_ => {
self.styles.push(StyleOp {
styles.push(StyleOp {
lamport: op.lamport() - 1,
peer: id.peer,
cnt: id.counter - 1,
@ -881,10 +1014,10 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
value: value.clone(),
info: *info,
});
self.styles.len() - 1
styles.len() - 1
}
};
self.tracker.insert(
tracker.insert(
op.id_full(),
// need to shift 1 because we insert the start style anchor before this pos
*end as usize + 1,
@ -895,8 +1028,8 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
_ => unreachable!(),
}
}
fn finish_this_round(&mut self) {}
}
}
fn calculate_diff(
&mut self,
@ -905,13 +1038,25 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
to: &crate::VersionVector,
_: impl FnMut(&ContainerID),
) -> (InternalDiff, DiffMode) {
// TODO: PERF: It can be linearized in certain cases
match &mut *self.mode {
RichtextCalcMode::Linear { diff, .. } => {
trace!("end with linear mode");
(
InternalDiff::RichtextRaw(std::mem::take(diff)),
DiffMode::Linear,
)
}
RichtextCalcMode::Crdt {
tracker, styles, ..
} => {
trace!("end with crdt mode");
tracing::debug!("CalcDiff {:?} {:?}", from, to);
let mut delta = Delta::new();
for item in self.tracker.diff(from, to) {
trace!("tracker version vector = {:?}", tracker.all_vv());
let mut delta = DeltaRope::new();
for item in tracker.diff(from, to) {
match item {
CrdtRopeDelta::Retain(len) => {
delta = delta.retain(len);
delta.push_retain(len, ());
}
CrdtRopeDelta::Insert {
chunk: value,
@ -919,21 +1064,27 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
lamport,
} => match value.value() {
RichtextChunkValue::Text(text) => {
delta = delta.insert(RichtextStateChunk::Text(
delta.push_insert(
RichtextStateChunk::Text(
// PERF: can be speedup by acquiring lock on arena
TextChunk::new(
oplog
.arena
.slice_by_unicode(text.start as usize..text.end as usize),
oplog.arena.slice_by_unicode(
text.start as usize..text.end as usize,
),
IdFull::new(id.peer, id.counter, lamport.unwrap()),
),
));
),
(),
);
}
RichtextChunkValue::StyleAnchor { id, anchor_type } => {
delta = delta.insert(RichtextStateChunk::Style {
style: Arc::new(self.styles[id as usize].clone()),
delta.push_insert(
RichtextStateChunk::Style {
style: Arc::new(styles[id as usize].clone()),
anchor_type,
});
},
(),
);
}
RichtextChunkValue::Unknown(len) => {
// assert not unknown id
@ -953,10 +1104,13 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
slice,
..
} => {
delta = delta.insert(RichtextStateChunk::Text(TextChunk::new(
delta.push_insert(
RichtextStateChunk::Text(TextChunk::new(
slice.clone(),
IdFull::new(id.peer, op.counter, lamport),
)));
)),
(),
);
}
_ => unreachable!("{:?}", content),
}
@ -967,7 +1121,7 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
RichtextChunkValue::MoveAnchor => unreachable!(),
},
CrdtRopeDelta::Delete(len) => {
delta = delta.delete(len);
delta.push_delete(len);
}
}
}
@ -975,11 +1129,26 @@ impl DiffCalculatorTrait for RichtextDiffCalculator {
(InternalDiff::RichtextRaw(delta), DiffMode::Checkout)
}
}
}
fn finish_this_round(&mut self) {
match &mut *self.mode {
RichtextCalcMode::Crdt { .. } => {}
RichtextCalcMode::Linear {
diff,
last_style_start,
} => {
*diff = DeltaRope::new();
last_style_start.take();
}
}
}
}
#[derive(Debug)]
pub(crate) struct MovableListDiffCalculator {
container_idx: ContainerIdx,
list: ListDiffCalculator,
list: Box<ListDiffCalculator>,
changed_elements: FxHashMap<CompactIdLp, ElementDelta>,
current_mode: DiffMode,
}
@ -1284,3 +1453,19 @@ impl MovableListDiffCalculator {
}
}
}
#[test]
fn test_size() {
let text = RichtextDiffCalculator::new();
let size = std::mem::size_of_val(&text);
assert!(size < 50, "RichtextDiffCalculator size: {}", size);
let list = MovableListDiffCalculator::new(ContainerIdx::from_index_and_type(
0,
loro_common::ContainerType::MovableList,
));
let size = std::mem::size_of_val(&list);
assert!(size < 50, "MovableListDiffCalculator size: {}", size);
let calc = ContainerDiffCalculator::Richtext(text);
let size = std::mem::size_of_val(&calc);
assert!(size < 50, "ContainerDiffCalculator size: {}", size);
}

View file

@ -234,7 +234,7 @@ impl DiffVariant {
pub(crate) enum InternalDiff {
ListRaw(Delta<SliceRanges>),
/// This always uses entity indexes.
RichtextRaw(Delta<RichtextStateChunk>),
RichtextRaw(DeltaRope<RichtextStateChunk, ()>),
Map(MapDelta),
Tree(TreeDelta),
MovableList(MovableListInnerDelta),
@ -356,7 +356,9 @@ impl InternalDiff {
Ok(InternalDiff::ListRaw(a.compose(b)))
}
(InternalDiff::RichtextRaw(a), InternalDiff::RichtextRaw(b)) => {
Ok(InternalDiff::RichtextRaw(a.compose(b)))
let mut ans = a.clone();
ans.compose(&b);
Ok(InternalDiff::RichtextRaw(ans))
}
(InternalDiff::Map(a), InternalDiff::Map(b)) => Ok(InternalDiff::Map(a.compose(b))),
(InternalDiff::Tree(a), InternalDiff::Tree(b)) => Ok(InternalDiff::Tree(a.compose(b))),

View file

@ -7,6 +7,7 @@ use fxhash::{FxHashMap, FxHashSet};
use generic_btree::rle::HasLength;
use loro_common::{ContainerID, InternalString, LoroError, LoroResult, LoroValue, ID};
use loro_delta::DeltaRopeBuilder;
use tracing::trace;
use crate::{
arena::SharedArena,
@ -282,6 +283,7 @@ impl ContainerState for RichtextState {
unreachable!()
};
trace!("diff = {:#?}, mode = {:?}", richtext, _ctx.mode);
// tracing::info!("Self state = {:#?}", &self);
// PERF: compose delta
let mut ans: TextDiff = TextDiff::new();
@ -290,91 +292,23 @@ impl ContainerState for RichtextState {
let mut entity_index = 0;
let mut event_index = 0;
let mut new_style_deltas: Vec<TextDiff> = Vec::new();
for span in richtext.vec.iter() {
for span in richtext.iter() {
match span {
crate::delta::DeltaItem::Retain { retain: len, .. } => {
loro_delta::DeltaItem::Retain { len, .. } => {
entity_index += len;
}
crate::delta::DeltaItem::Insert { insert: value, .. } => {
match value {
RichtextStateChunk::Text(s) => {
let (pos, styles) = self.state.get_mut().insert_elem_at_entity_index(
entity_index,
RichtextStateChunk::Text(s.clone()),
);
let insert_styles = styles.clone().into();
if pos > event_index {
ans.push_retain(pos - event_index, Default::default());
}
event_index = pos + s.event_len() as usize;
ans.push_insert(StringSlice::from(s.bytes().clone()), insert_styles);
}
RichtextStateChunk::Style { anchor_type, style } => {
let (new_event_index, _) =
self.state.get_mut().insert_elem_at_entity_index(
entity_index,
RichtextStateChunk::Style {
style: style.clone(),
anchor_type: *anchor_type,
},
);
if new_event_index > event_index {
ans.push_retain(new_event_index - event_index, Default::default());
// inserting style anchor will not affect event_index's positions
event_index = new_event_index;
}
match anchor_type {
AnchorType::Start => {
style_starts.insert(
style.clone(),
Pos {
entity_index,
event_index: new_event_index,
},
);
}
AnchorType::End => {
// get the pair of style anchor. now we can annotate the range
let Pos {
entity_index: start_entity_index,
event_index: start_event_index,
} = self.get_style_start(&mut style_starts, style);
let mut delta: TextDiff = DeltaRopeBuilder::new()
.retain(start_event_index, Default::default())
.build();
// we need to + 1 because we also need to annotate the end anchor
let event =
self.state.get_mut().annotate_style_range_with_event(
start_entity_index..entity_index + 1,
style.clone(),
);
for (s, l) in event {
delta.push_retain(l, s);
}
delta.chop();
new_style_deltas.push(delta);
}
}
}
}
entity_index += value.rle_len();
}
crate::delta::DeltaItem::Delete {
delete: len,
attributes: _,
} => {
let mut deleted_style_keys: FxHashSet<InternalString> = FxHashSet::default();
loro_delta::DeltaItem::Replace { value, delete, .. } => {
if *delete > 0 {
// Deletions
let mut deleted_style_keys: FxHashSet<InternalString> =
FxHashSet::default();
let DrainInfo {
start_event_index: start,
end_event_index: end,
affected_style_range,
} = self.state.get_mut().drain_by_entity_index(
entity_index,
*len,
*delete,
Some(&mut |c| match c {
RichtextStateChunk::Style {
style,
@ -441,6 +375,85 @@ impl ContainerState for RichtextState {
ans.push_delete(end - start);
}
if value.rle_len() > 0 {
// Insertions
match value {
RichtextStateChunk::Text(s) => {
let (pos, styles) =
self.state.get_mut().insert_elem_at_entity_index(
entity_index,
RichtextStateChunk::Text(s.clone()),
);
let insert_styles = styles.clone().into();
if pos > event_index {
ans.push_retain(pos - event_index, Default::default());
}
event_index = pos + s.event_len() as usize;
ans.push_insert(
StringSlice::from(s.bytes().clone()),
insert_styles,
);
}
RichtextStateChunk::Style { anchor_type, style } => {
let (new_event_index, _) =
self.state.get_mut().insert_elem_at_entity_index(
entity_index,
RichtextStateChunk::Style {
style: style.clone(),
anchor_type: *anchor_type,
},
);
if new_event_index > event_index {
ans.push_retain(
new_event_index - event_index,
Default::default(),
);
// inserting style anchor will not affect event_index's positions
event_index = new_event_index;
}
match anchor_type {
AnchorType::Start => {
style_starts.insert(
style.clone(),
Pos {
entity_index,
event_index: new_event_index,
},
);
}
AnchorType::End => {
// get the pair of style anchor. now we can annotate the range
let Pos {
entity_index: start_entity_index,
event_index: start_event_index,
} = self.get_style_start(&mut style_starts, style);
let mut delta: TextDiff = DeltaRopeBuilder::new()
.retain(start_event_index, Default::default())
.build();
// we need to + 1 because we also need to annotate the end anchor
let event =
self.state.get_mut().annotate_style_range_with_event(
start_entity_index..entity_index + 1,
style.clone(),
);
for (s, l) in event {
delta.push_retain(l, s);
}
delta.chop();
new_style_deltas.push(delta);
}
}
}
}
entity_index += value.rle_len();
}
}
}
}
@ -458,20 +471,27 @@ impl ContainerState for RichtextState {
unreachable!()
};
trace!("diff = {:#?}", richtext);
let mut style_starts: FxHashMap<Arc<StyleOp>, usize> = FxHashMap::default();
let mut entity_index = 0;
for span in richtext.vec.iter() {
for span in richtext.iter() {
match span {
crate::delta::DeltaItem::Retain {
retain: len,
attributes: _,
} => {
loro_delta::DeltaItem::Retain { len, .. } => {
entity_index += len;
}
crate::delta::DeltaItem::Insert {
insert: value,
attributes: _,
loro_delta::DeltaItem::Replace {
value,
attr,
delete,
} => {
if *delete > 0 {
// Deletions
self.state
.get_mut()
.drain_by_entity_index(entity_index, *delete, None);
}
if value.rle_len() > 0 {
// Insertions
match value {
RichtextStateChunk::Text(s) => {
self.state.get_mut().insert_elem_at_entity_index(
@ -505,7 +525,8 @@ impl ContainerState for RichtextState {
break;
}
RichtextStateChunk::Text(t) => {
start_entity_index += t.unicode_len() as usize;
start_entity_index +=
t.unicode_len() as usize;
}
RichtextStateChunk::Style { .. } => {
start_entity_index += 1;
@ -525,13 +546,6 @@ impl ContainerState for RichtextState {
}
entity_index += value.rle_len();
}
crate::delta::DeltaItem::Delete {
delete: len,
attributes: _,
} => {
self.state
.get_mut()
.drain_by_entity_index(entity_index, *len, None);
}
}
}