mirror of
https://github.com/loro-dev/loro.git
synced 2024-11-24 12:20:06 +00:00
feat: add delta compose
This commit is contained in:
parent
3c9818ef82
commit
9544e27be4
8 changed files with 406 additions and 55 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -10,6 +10,7 @@
|
|||
"LOGSTORE",
|
||||
"napi",
|
||||
"nextest",
|
||||
"peekable",
|
||||
"Peritext",
|
||||
"RUSTFLAGS",
|
||||
"smstring",
|
||||
|
|
|
@ -473,10 +473,8 @@ impl Container for ListContainer {
|
|||
InnerContent::List(op) => match op {
|
||||
InnerListOp::Insert { slice, pos } => {
|
||||
if should_notify {
|
||||
let mut delta = Delta::new();
|
||||
let delta_vec = self.raw_data.slice(&slice.0).to_vec();
|
||||
delta.retain(*pos);
|
||||
delta.insert(delta_vec);
|
||||
let delta = Delta::new().retain(*pos).insert(delta_vec);
|
||||
context.push_diff(&self.id, Diff::List(delta));
|
||||
}
|
||||
|
||||
|
@ -485,9 +483,9 @@ impl Container for ListContainer {
|
|||
}
|
||||
InnerListOp::Delete(span) => {
|
||||
if should_notify {
|
||||
let mut delta = Delta::new();
|
||||
delta.retain(span.start() as usize);
|
||||
delta.delete(span.atom_len());
|
||||
let delta = Delta::new()
|
||||
.retain(span.start() as usize)
|
||||
.delete(span.atom_len());
|
||||
context.push_diff(&self.id, Diff::List(delta));
|
||||
}
|
||||
|
||||
|
@ -543,9 +541,7 @@ impl Container for ListContainer {
|
|||
match effect {
|
||||
Effect::Del { pos, len } => {
|
||||
if should_notify {
|
||||
let mut delta = Delta::new();
|
||||
delta.retain(pos);
|
||||
delta.delete(len);
|
||||
let delta = Delta::new().retain(pos).delete(len);
|
||||
diff.push(Diff::List(delta));
|
||||
}
|
||||
|
||||
|
@ -568,15 +564,12 @@ impl Container for ListContainer {
|
|||
}
|
||||
Effect::Ins { pos, content } => {
|
||||
if should_notify {
|
||||
let mut delta = Delta::new();
|
||||
delta.retain(pos);
|
||||
let s = if content.is_unknown() {
|
||||
(0..content.atom_len()).map(|_| LoroValue::Null).collect()
|
||||
} else {
|
||||
self.raw_data.slice(&content.0).to_vec()
|
||||
};
|
||||
|
||||
delta.insert(s);
|
||||
let delta = Delta::new().retain(pos).insert(s);
|
||||
diff.push(Diff::List(delta));
|
||||
}
|
||||
if !content.is_unknown() {
|
||||
|
@ -665,10 +658,9 @@ impl Container for ListContainer {
|
|||
// notify
|
||||
let should_notify = hierarchy.should_notify(&self.id);
|
||||
if should_notify {
|
||||
let mut delta = Delta::new();
|
||||
let delta_vec = self.raw_data.slice(&(0..state_len)).to_vec();
|
||||
delta.retain(0);
|
||||
delta.insert(delta_vec);
|
||||
let delta = Delta::new().retain(0).insert(delta_vec);
|
||||
|
||||
ctx.push_diff(&self.id, Diff::List(delta));
|
||||
}
|
||||
} else {
|
||||
|
@ -850,9 +842,9 @@ mod test {
|
|||
let mut loro = LoroCore::default();
|
||||
let mut list = loro.get_list("id");
|
||||
{
|
||||
let mut txn = loro.transact();
|
||||
list.insert(&mut txn, 0, 123).unwrap();
|
||||
list.insert(&mut txn, 1, 123).unwrap();
|
||||
let txn = loro.transact();
|
||||
list.insert(&txn, 0, 123).unwrap();
|
||||
list.insert(&txn, 1, 123).unwrap();
|
||||
}
|
||||
assert_eq!(list.get(0), Some(123.into()));
|
||||
assert_eq!(list.get(1), Some(123.into()));
|
||||
|
|
|
@ -367,9 +367,7 @@ impl Container for TextContainer {
|
|||
} else {
|
||||
self.raw_str.slice(&slice.0).to_owned()
|
||||
};
|
||||
let mut delta = Delta::new();
|
||||
delta.retain(*pos);
|
||||
delta.insert(s);
|
||||
let delta = Delta::new().retain(*pos).insert(s);
|
||||
ctx.push_diff(&self.id, Diff::Text(delta));
|
||||
}
|
||||
self.state.insert(
|
||||
|
@ -379,9 +377,9 @@ impl Container for TextContainer {
|
|||
}
|
||||
InnerListOp::Delete(span) => {
|
||||
if should_notify {
|
||||
let mut delta = Delta::new();
|
||||
delta.retain(span.start() as usize);
|
||||
delta.delete(span.atom_len());
|
||||
let delta = Delta::new()
|
||||
.retain(span.start() as usize)
|
||||
.delete(span.atom_len());
|
||||
ctx.push_diff(&self.id, Diff::Text(delta));
|
||||
}
|
||||
|
||||
|
@ -433,9 +431,7 @@ impl Container for TextContainer {
|
|||
match effect {
|
||||
Effect::Del { pos, len } => {
|
||||
if should_notify {
|
||||
let mut delta = Delta::new();
|
||||
delta.retain(pos);
|
||||
delta.delete(len);
|
||||
let delta = Delta::new().retain(pos).delete(len);
|
||||
diff.push(Diff::Text(delta));
|
||||
}
|
||||
|
||||
|
@ -449,9 +445,7 @@ impl Container for TextContainer {
|
|||
} else {
|
||||
self.raw_str.slice(&content.0).to_owned()
|
||||
};
|
||||
let mut delta = Delta::new();
|
||||
delta.retain(pos);
|
||||
delta.insert(s);
|
||||
let delta = Delta::new().retain(pos).insert(s);
|
||||
diff.push(Diff::Text(delta));
|
||||
}
|
||||
|
||||
|
@ -544,9 +538,7 @@ impl Container for TextContainer {
|
|||
let should_notify = hierarchy.should_notify(&self.id);
|
||||
if should_notify {
|
||||
let s = self.raw_str.slice(&(0..state_len)).to_owned();
|
||||
let mut delta = Delta::new();
|
||||
delta.retain(0);
|
||||
delta.insert(s);
|
||||
let delta = Delta::new().retain(0).insert(s);
|
||||
ctx.push_diff(&self.id, Diff::Text(delta));
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
use enum_as_inner::EnumAsInner;
|
||||
use rle::{HasLength, Mergable, RleVec};
|
||||
use rle::{HasLength, Mergable, RleVec, Sliceable};
|
||||
use serde::Serialize;
|
||||
use smallvec::{smallvec, IntoIter, SmallVec};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::LoroValue;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Delta<Value, Meta = ()> {
|
||||
vec: RleVec<[DeltaItem<Value, Meta>; 1]>,
|
||||
vec: SmallVec<[DeltaItem<Value, Meta>; 2]>,
|
||||
}
|
||||
|
||||
impl<V: Serialize, M: Serialize> Serialize for Delta<V, M> {
|
||||
|
@ -23,6 +27,42 @@ pub enum DeltaItem<Value, Meta> {
|
|||
Delete(usize),
|
||||
}
|
||||
|
||||
pub trait Meta: Debug + Default + Clone + PartialEq {
|
||||
fn is_empty(&self) -> bool;
|
||||
}
|
||||
|
||||
impl Meta for () {
|
||||
fn is_empty(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DeltaValue: Debug + HasLength + Sliceable + Clone + PartialEq {
|
||||
fn merge(&mut self, other: Self);
|
||||
}
|
||||
|
||||
impl<Value: DeltaValue, M: Meta> DeltaItem<Value, M> {
|
||||
pub fn meta(&self) -> Option<&M> {
|
||||
match self {
|
||||
DeltaItem::Insert { value, meta } => Some(meta),
|
||||
DeltaItem::Retain { len, meta } => Some(meta),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_retain(&self) -> bool {
|
||||
matches!(self, Self::Retain { .. })
|
||||
}
|
||||
|
||||
pub fn is_insert(&self) -> bool {
|
||||
matches!(self, Self::Insert { .. })
|
||||
}
|
||||
|
||||
pub fn is_delete(&self) -> bool {
|
||||
matches!(self, Self::Delete(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Value: HasLength, Meta> HasLength for DeltaItem<Value, Meta> {
|
||||
fn content_len(&self) -> usize {
|
||||
match self {
|
||||
|
@ -35,31 +75,220 @@ impl<Value: HasLength, Meta> HasLength for DeltaItem<Value, Meta> {
|
|||
|
||||
impl<Value, Meta> Mergable for DeltaItem<Value, Meta> {}
|
||||
|
||||
impl<Value: HasLength, Meta> Delta<Value, Meta> {
|
||||
pub struct DeltaIterator<V, M: Meta> {
|
||||
ops: SmallVec<[DeltaItem<V, M>; 2]>,
|
||||
index: usize,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<V: DeltaValue, M: Meta> DeltaIterator<V, M> {
|
||||
fn new(ops: SmallVec<[DeltaItem<V, M>; 2]>) -> Self {
|
||||
Self {
|
||||
ops,
|
||||
index: 0,
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn next<L: Into<Option<usize>>>(&mut self, len: L) -> DeltaItem<V, M> {
|
||||
self.next_impl(len.into())
|
||||
}
|
||||
|
||||
fn next_impl(&mut self, mut len: Option<usize>) -> DeltaItem<V, M> {
|
||||
if len.is_none() {
|
||||
len = Some(usize::MAX)
|
||||
}
|
||||
let mut length = len.unwrap();
|
||||
{
|
||||
let next_op = self.peek();
|
||||
if next_op.is_none() {
|
||||
return DeltaItem::Retain {
|
||||
len: usize::MAX,
|
||||
meta: Default::default(),
|
||||
};
|
||||
}
|
||||
}
|
||||
// TODO: Maybe can avoid cloning
|
||||
let op = self.peek().unwrap().clone();
|
||||
let op_length = op.content_len();
|
||||
let offset = self.offset;
|
||||
if length >= op_length - offset {
|
||||
length = op_length - offset;
|
||||
self.index += 1;
|
||||
self.offset = 0;
|
||||
} else {
|
||||
self.offset += length;
|
||||
}
|
||||
|
||||
if op.is_delete() {
|
||||
DeltaItem::Delete(length)
|
||||
} else {
|
||||
let mut ans_op = op;
|
||||
if ans_op.is_retain() {
|
||||
*ans_op.as_retain_mut().unwrap().0 = length;
|
||||
} else if ans_op.is_insert() {
|
||||
let v = ans_op.as_insert_mut().unwrap().0;
|
||||
*v = v.slice(offset, offset + length);
|
||||
}
|
||||
ans_op
|
||||
}
|
||||
}
|
||||
|
||||
fn rest(&mut self) -> SmallVec<[DeltaItem<V, M>; 2]> {
|
||||
if !self.has_next() {
|
||||
smallvec![]
|
||||
} else if self.offset == 0 {
|
||||
// TODO avoid cloning
|
||||
self.ops[self.index..].into()
|
||||
} else {
|
||||
let offset = self.offset;
|
||||
let index = self.index;
|
||||
let next = self.next(None);
|
||||
let rest = self.ops[self.index..].to_vec();
|
||||
self.offset = offset;
|
||||
self.index = index;
|
||||
let mut ans = smallvec![next];
|
||||
ans.extend(rest);
|
||||
ans
|
||||
}
|
||||
}
|
||||
|
||||
fn has_next(&self) -> bool {
|
||||
self.peek_length() < usize::MAX
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<&DeltaItem<V, M>> {
|
||||
self.ops.get(self.index)
|
||||
}
|
||||
|
||||
fn peek_length(&self) -> usize {
|
||||
if let Some(op) = self.peek() {
|
||||
if op.content_len() == usize::MAX {
|
||||
return usize::MAX;
|
||||
}
|
||||
op.content_len() - self.offset
|
||||
} else {
|
||||
usize::MAX
|
||||
}
|
||||
}
|
||||
|
||||
// fn peek_is_retain(&self) -> bool {
|
||||
// if let Some(op) = self.peek() {
|
||||
// op.is_retain()
|
||||
// } else {
|
||||
// // default
|
||||
// true
|
||||
// }
|
||||
// }
|
||||
|
||||
fn peek_is_insert(&self) -> bool {
|
||||
if let Some(op) = self.peek() {
|
||||
op.is_insert()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_is_delete(&self) -> bool {
|
||||
if let Some(op) = self.peek() {
|
||||
op.is_delete()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Value: DeltaValue, M: Meta> Delta<Value, M> {
|
||||
pub fn new() -> Self {
|
||||
Self { vec: RleVec::new() }
|
||||
Self {
|
||||
vec: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn retain_with_meta(&mut self, len: usize, meta: Meta) {
|
||||
pub fn retain_with_meta(mut self, len: usize, meta: M) -> Self {
|
||||
self.vec.push(DeltaItem::Retain { len, meta });
|
||||
self
|
||||
}
|
||||
|
||||
pub fn insert_with_meta(&mut self, value: Value, meta: Meta) {
|
||||
pub fn insert_with_meta(mut self, value: Value, meta: M) -> Self {
|
||||
self.vec.push(DeltaItem::Insert { value, meta });
|
||||
self
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, len: usize) {
|
||||
pub fn delete(mut self, len: usize) -> Self {
|
||||
if len == 0 {
|
||||
return self;
|
||||
}
|
||||
self.vec.push(DeltaItem::Delete(len));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &DeltaItem<Value, Meta>> {
|
||||
pub fn push(&mut self, new_op: DeltaItem<Value, M>) {
|
||||
let mut index = self.vec.len();
|
||||
let last_op = self.vec.last_mut();
|
||||
if let Some(mut last_op) = last_op {
|
||||
if new_op.is_delete() && last_op.is_delete() {
|
||||
self.vec[index - 1] =
|
||||
DeltaItem::Delete(last_op.content_len() + new_op.content_len());
|
||||
return;
|
||||
}
|
||||
// Since it does not matter if we insert before or after deleting at the same index,
|
||||
// always prefer to insert first
|
||||
if last_op.is_delete() && new_op.is_insert() {
|
||||
index -= 1;
|
||||
let _last_op = self.vec.get_mut(index - 1);
|
||||
if let Some(_last_op_inner) = _last_op {
|
||||
last_op = _last_op_inner;
|
||||
} else {
|
||||
self.vec.insert(0, new_op);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if new_op.meta() == last_op.meta() {
|
||||
if new_op.is_insert() && last_op.is_insert() {
|
||||
// TODO avoid cloning
|
||||
let mut value = last_op.as_insert_mut().unwrap().0.clone();
|
||||
value.merge(new_op.as_insert().unwrap().0.clone());
|
||||
self.vec[index - 1] = DeltaItem::Insert {
|
||||
value,
|
||||
meta: new_op.meta().unwrap().clone(),
|
||||
};
|
||||
return;
|
||||
} else if new_op.is_retain() && last_op.is_retain() {
|
||||
println!("last op {:?} new_op {:?}", last_op, new_op);
|
||||
println!(
|
||||
"len = {} + {} usize MAX {}",
|
||||
last_op.content_len(),
|
||||
new_op.content_len(),
|
||||
usize::MAX
|
||||
);
|
||||
self.vec[index - 1] = DeltaItem::Retain {
|
||||
len: last_op.content_len() + new_op.content_len(),
|
||||
meta: new_op.meta().unwrap().clone(),
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if index == self.vec.len() {
|
||||
self.vec.push(new_op);
|
||||
} else {
|
||||
self.vec.insert(index, new_op);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &DeltaItem<Value, M>> {
|
||||
self.vec.iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut DeltaItem<Value, Meta>> {
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut DeltaItem<Value, M>> {
|
||||
self.vec.iter_mut()
|
||||
}
|
||||
|
||||
pub fn into_op_iter(self) -> DeltaIterator<Value, M> {
|
||||
DeltaIterator::new(self.vec)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.vec.len()
|
||||
}
|
||||
|
@ -67,30 +296,160 @@ impl<Value: HasLength, Meta> Delta<Value, Meta> {
|
|||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Reference: [Quill Delta](https://github.com/quilljs/delta)
|
||||
pub fn compose(self, other: Self) -> Self {
|
||||
let mut this_iter = self.into_op_iter();
|
||||
let mut other_iter = other.into_op_iter();
|
||||
let mut ops = vec![];
|
||||
let first_other = other_iter.peek();
|
||||
if let Some(first_other) = first_other {
|
||||
if first_other.is_retain()
|
||||
&& (first_other.meta().is_none() || first_other.meta().unwrap().is_empty())
|
||||
{
|
||||
let mut first_left = first_other.content_len();
|
||||
let mut first_this = this_iter.peek();
|
||||
while let Some(first_this_inner) = first_this {
|
||||
if first_this_inner.is_insert() && first_this_inner.content_len() <= first_left
|
||||
{
|
||||
first_left -= first_this_inner.content_len();
|
||||
ops.push(this_iter.next(None));
|
||||
first_this = this_iter.peek();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if first_other.content_len() - first_left > 0 {
|
||||
other_iter.next(first_other.content_len() - first_left);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut delta = Delta::new();
|
||||
while this_iter.has_next() || other_iter.has_next() {
|
||||
if other_iter.peek_is_insert() {
|
||||
delta.push(other_iter.next(None));
|
||||
} else if this_iter.peek_is_delete() {
|
||||
delta.push(this_iter.next(None));
|
||||
} else {
|
||||
let length = this_iter.peek_length().min(other_iter.peek_length());
|
||||
let this_op = this_iter.next(length);
|
||||
let other_op = other_iter.next(length);
|
||||
if other_op.is_retain() {
|
||||
let new_op = if this_op.is_retain() {
|
||||
DeltaItem::Retain {
|
||||
len: length,
|
||||
meta: M::default(),
|
||||
}
|
||||
} else {
|
||||
this_op.clone()
|
||||
};
|
||||
// TODO: Meta compose
|
||||
delta.push(new_op.clone());
|
||||
if !other_iter.has_next() && delta.vec[delta.vec.len() - 1].eq(&new_op) {
|
||||
let rest = Delta {
|
||||
vec: this_iter.rest(),
|
||||
};
|
||||
return delta.concat(rest).chop();
|
||||
}
|
||||
} else if other_op.is_delete() {
|
||||
if this_op.is_retain() {
|
||||
delta.push(other_op);
|
||||
} else {
|
||||
// this op is insert
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delta.chop()
|
||||
}
|
||||
|
||||
fn concat(&mut self, mut other: Self) -> Self {
|
||||
let mut delta = Delta {
|
||||
vec: self.vec.clone(),
|
||||
};
|
||||
if !other.vec.is_empty() {
|
||||
// TODO: why?
|
||||
let other_first = other.vec.remove(0);
|
||||
delta.push(other_first);
|
||||
delta.vec.extend(other.vec);
|
||||
}
|
||||
delta
|
||||
}
|
||||
|
||||
fn chop(mut self) -> Self {
|
||||
let last_op = self.vec.last();
|
||||
if let Some(last_op) = last_op {
|
||||
// TODO: check
|
||||
if last_op.is_retain() && last_op.meta().unwrap().is_empty() {
|
||||
self.vec.pop();
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Value: HasLength, Meta> Default for Delta<Value, Meta> {
|
||||
impl<Value: DeltaValue, M: Meta> Default for Delta<Value, M> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Value: HasLength, Meta: Default> Delta<Value, Meta> {
|
||||
pub fn retain(&mut self, len: usize) {
|
||||
impl<Value: HasLength, M: Default + Meta> Delta<Value, M> {
|
||||
pub fn retain(mut self, len: usize) -> Self {
|
||||
if len == 0 {
|
||||
return;
|
||||
return self;
|
||||
}
|
||||
|
||||
self.vec.push(DeltaItem::Retain {
|
||||
len,
|
||||
meta: Default::default(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: Value) {
|
||||
pub fn insert(mut self, value: Value) -> Self {
|
||||
self.vec.push(DeltaItem::Insert {
|
||||
value,
|
||||
meta: Default::default(),
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DeltaValue for Vec<LoroValue> {
|
||||
fn merge(&mut self, other: Self) {
|
||||
self.extend(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeltaValue for String {
|
||||
fn merge(&mut self, other: Self) {
|
||||
self.push_str(&other)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Delta, DeltaItem};
|
||||
|
||||
#[test]
|
||||
fn delta_push() {
|
||||
let mut a: Delta<String, ()> = Delta::new().insert("a".to_string());
|
||||
a.push(DeltaItem::Insert {
|
||||
value: "b".to_string(),
|
||||
meta: (),
|
||||
});
|
||||
assert_eq!(a, Delta::new().insert("ab".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_compose() {
|
||||
let a: Delta<String, ()> = Delta::new().retain(3).insert("abcde".to_string());
|
||||
let b = Delta::new().retain(5).delete(6);
|
||||
assert_eq!(
|
||||
a.compose(b),
|
||||
Delta::new().retain(3).insert("ab".to_string()).delete(3)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ impl Deref for TransactionWrap {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TransactionWrap(pub Arc<Mutex<Transaction>>);
|
||||
pub struct TransactionWrap(pub(crate) Arc<Mutex<Transaction>>);
|
||||
|
||||
pub struct Transaction {
|
||||
client_id: ClientID,
|
||||
|
|
|
@ -67,6 +67,7 @@ pub enum ListTxnOp {
|
|||
Delete {
|
||||
pos: usize,
|
||||
len: usize,
|
||||
|
||||
deleted_container: Option<SmallVec<[ContainerIdx; 1]>>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -96,3 +96,15 @@ impl<T> HasLength for Vec<T> {
|
|||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Sliceable for Vec<T> {
|
||||
fn slice(&self, from: usize, to: usize) -> Self {
|
||||
self[from..to].to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sliceable for String {
|
||||
fn slice(&self, from: usize, to: usize) -> Self {
|
||||
self[from..to].to_string()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -357,12 +357,6 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
impl Sliceable for String {
|
||||
fn slice(&self, start: usize, end: usize) -> Self {
|
||||
self[start..end].to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_at_atom_index() {
|
||||
let mut vec: RleVecWithIndex<String> = RleVecWithIndex::new();
|
||||
|
|
Loading…
Reference in a new issue