///|
// List operations for list and text containers.
pub(all) enum ListOp {
Insert(slice~ : ListSlice, pos~ : Int)
Delete(DeleteSpanWithId)
Move(from~ : Int, to~ : Int, elem_id~ : @types.IdLp)
Set(elem_id~ : @types.IdLp, value~ : @types.LoroValue)
StyleStart(
start~ : Int,
end~ : Int,
key~ : String,
info~ : Int,
value~ : @types.LoroValue
)
StyleEnd
} derive(Show)
///|
pub(all) enum InnerListOp {
Insert(slice~ : SliceRange, pos~ : Int)
InsertText(
str~ : String,
unicode_start~ : Int,
unicode_len~ : Int,
pos~ : Int
)
Delete(DeleteSpanWithId)
Move(from~ : Int, elem_id~ : @types.IdLp, to~ : Int)
Set(elem_id~ : @types.IdLp, value~ : @types.LoroValue)
StyleStart(
start~ : Int,
end~ : Int,
key~ : String,
info~ : Int,
value~ : @types.LoroValue
)
StyleEnd
} derive(Show)
///|
// Delete span allows reversed spans for efficient merging.
pub struct DeleteSpan {
pos : Int
signed_len : Int
} derive(Show, Eq)
///|
// Delete span with its starting op id.
pub struct DeleteSpanWithId {
id_start : @types.ID
span : DeleteSpan
} derive(Show, Eq)
///|
pub fn DeleteSpan::new(pos : Int, len : Int) -> DeleteSpan {
DeleteSpan::{ pos, signed_len: len }
}
///|
fn abs_int(v : Int) -> Int {
if v >= 0 {
v
} else {
-v
}
}
///|
pub fn DeleteSpan::len(self : DeleteSpan) -> Int {
abs_int(self.signed_len)
}
///|
pub fn DeleteSpan::pos(self : DeleteSpan) -> Int {
self.pos
}
///|
pub fn DeleteSpan::signed_len(self : DeleteSpan) -> Int {
self.signed_len
}
///|
pub fn DeleteSpan::start(self : DeleteSpan) -> Int {
if self.signed_len > 0 {
self.pos
} else {
self.pos + 1 + self.signed_len
}
}
///|
pub fn DeleteSpan::last(self : DeleteSpan) -> Int {
if self.signed_len > 0 {
self.pos + self.signed_len - 1
} else {
self.pos
}
}
///|
pub fn DeleteSpan::end(self : DeleteSpan) -> Int {
if self.signed_len > 0 {
self.pos + self.signed_len
} else {
self.pos + 1
}
}
///|
pub fn DeleteSpan::bidirectional(self : DeleteSpan) -> Bool {
abs_int(self.signed_len) == 1
}
///|
pub fn DeleteSpan::is_reversed(self : DeleteSpan) -> Bool {
self.signed_len < 0
}
///|
pub fn DeleteSpan::direction(self : DeleteSpan) -> Int {
if self.signed_len > 0 {
1
} else {
-1
}
}
///|
pub fn DeleteSpan::next_pos(self : DeleteSpan) -> Int {
if self.signed_len > 0 {
self.start()
} else {
self.start() - 1
}
}
///|
pub fn DeleteSpan::prev_pos(self : DeleteSpan) -> Int {
if self.signed_len > 0 {
self.pos
} else {
self.end()
}
}
///|
pub fn DeleteSpan::is_mergeable(self : DeleteSpan, other : DeleteSpan) -> Bool {
match (self.bidirectional(), other.bidirectional()) {
(true, true) => self.pos == other.pos || self.pos == other.pos + 1
(true, false) => self.pos == other.prev_pos()
(false, true) => self.next_pos() == other.pos
(false, false) =>
self.next_pos() == other.pos && self.direction() == other.direction()
}
}
///|
pub fn DeleteSpan::merge(self : DeleteSpan, other : DeleteSpan) -> DeleteSpan {
let this = self
let mut signed_len = this.signed_len
match (this.bidirectional(), other.bidirectional()) {
(true, true) =>
if this.pos == other.pos {
signed_len = 2
} else if this.pos == other.pos + 1 {
signed_len = -2
}
(true, false) => signed_len = other.signed_len + other.direction()
(false, true) => signed_len = this.signed_len + this.direction()
(false, false) => signed_len = this.signed_len + other.signed_len
}
DeleteSpan::{ pos: this.pos, signed_len }
}
///|
pub fn DeleteSpan::slice(self : DeleteSpan, from : Int, to : Int) -> DeleteSpan {
if self.signed_len > 0 {
DeleteSpan::new(self.pos, to - from)
} else {
DeleteSpan::new(self.pos - from, from - to)
}
}
///|
pub fn DeleteSpanWithId::new(
id_start : @types.ID,
pos : Int,
len : Int,
) -> DeleteSpanWithId {
DeleteSpanWithId::{ id_start, span: DeleteSpan::new(pos, len) }
}
///|
pub fn DeleteSpanWithId::id_start(self : DeleteSpanWithId) -> @types.ID {
self.id_start
}
///|
pub fn DeleteSpanWithId::span(self : DeleteSpanWithId) -> DeleteSpan {
self.span
}
///|
pub fn DeleteSpanWithId::start(self : DeleteSpanWithId) -> Int {
self.span.start()
}
///|
pub fn DeleteSpanWithId::end(self : DeleteSpanWithId) -> Int {
self.span.end()
}
///|
pub fn DeleteSpanWithId::last(self : DeleteSpanWithId) -> Int {
self.span.last()
}
///|
pub fn DeleteSpanWithId::is_reversed(self : DeleteSpanWithId) -> Bool {
self.span.is_reversed()
}
///|
pub fn DeleteSpanWithId::id_end(self : DeleteSpanWithId) -> @types.ID {
self.id_start.inc(self.span.len())
}
///|
pub fn DeleteSpanWithId::id_last(self : DeleteSpanWithId) -> @types.ID {
self.id_start.inc(self.span.len() - 1)
}
///|
pub fn DeleteSpanWithId::content_len(self : DeleteSpanWithId) -> Int {
self.span.len()
}
///|
pub fn DeleteSpanWithId::is_mergeable(
self : DeleteSpanWithId,
other : DeleteSpanWithId,
) -> Bool {
let this = self.span
let rhs = other.span
match (this.bidirectional(), rhs.bidirectional()) {
(true, true) => {
let left = this.pos == rhs.pos && self.id_start.inc(1) == other.id_start
let right = this.pos == rhs.pos + 1 &&
self.id_start == other.id_start.inc(1)
left || right
}
(true, false) =>
if this.pos == rhs.prev_pos() {
if rhs.signed_len > 0 {
self.id_start.inc(1) == other.id_start
} else {
self.id_start == other.id_end()
}
} else {
false
}
(false, true) =>
if this.next_pos() == rhs.pos {
if this.signed_len > 0 {
self.id_end() == other.id_start
} else {
self.id_start == other.id_start.inc(1)
}
} else {
false
}
(false, false) =>
if this.next_pos() == rhs.pos && this.direction() == rhs.direction() {
if this.signed_len > 0 {
self.id_end() == other.id_start
} else {
self.id_start == other.id_end()
}
} else {
false
}
}
}
///|
pub fn DeleteSpanWithId::merge(
self : DeleteSpanWithId,
other : DeleteSpanWithId,
) -> DeleteSpanWithId {
let id_start = if other.id_start.counter < self.id_start.counter {
other.id_start
} else {
self.id_start
}
let span = self.span.merge(other.span)
DeleteSpanWithId::{ id_start, span }
}
///|
pub fn DeleteSpanWithId::slice(
self : DeleteSpanWithId,
from : Int,
to : Int,
) -> DeleteSpanWithId {
let id_start = if self.span.signed_len > 0 {
self.id_start.inc(from)
} else {
self.id_start.inc(self.span.len() - to)
}
DeleteSpanWithId::{ id_start, span: self.span.slice(from, to) }
}
///|
pub fn ListOp::new_del(id_start : @types.ID, pos : Int, len : Int) -> ListOp {
ListOp::Delete(DeleteSpanWithId::new(id_start, pos, len))
}
///|
pub fn ListOp::content_len(self : ListOp) -> Int {
match self {
Insert(slice~, pos=_) => slice.content_len()
Delete(span) => span.content_len()
StyleStart(start=_, end=_, key=_, info=_, value=_) => 1
StyleEnd => 1
Move(from=_, to=_, elem_id=_) => 1
Set(elem_id=_, value=_) => 1
}
}
///|
pub fn ListOp::slice(self : ListOp, from : Int, to : Int) -> ListOp {
match self {
Insert(slice~, pos~) =>
ListOp::Insert(slice=slice.slice(from, to), pos=pos + from)
Delete(span) => ListOp::Delete(span.slice(from, to))
other => other
}
}
///|
pub fn ListOp::is_mergeable(self : ListOp, other : ListOp) -> Bool {
match (self, other) {
(Insert(slice~, pos~), Insert(slice=other_slice, pos=other_pos)) =>
pos + slice.content_len() == other_pos && slice.is_mergeable(other_slice)
(Delete(span), Delete(other_span)) => span.is_mergeable(other_span)
_ => false
}
}
///|
pub fn ListOp::merge(self : ListOp, other : ListOp) -> ListOp {
match (self, other) {
(Delete(span), Delete(other_span)) => ListOp::Delete(span.merge(other_span))
_ => self
}
}
///|
pub fn InnerListOp::content_len(self : InnerListOp) -> Int {
match self {
Insert(slice~, pos=_) => slice.len()
InsertText(str=_, unicode_start=_, unicode_len=len, pos=_) => len
Delete(span) => span.content_len()
StyleStart(start=_, end=_, key=_, info=_, value=_) => 1
StyleEnd => 1
Move(from=_, elem_id=_, to=_) => 1
Set(elem_id=_, value=_) => 1
}
}
///|
pub fn InnerListOp::slice(
self : InnerListOp,
from : Int,
to : Int,
) -> InnerListOp {
match self {
Insert(slice~, pos~) =>
InnerListOp::Insert(slice=slice.slice(from, to), pos=pos + from)
InsertText(str~, unicode_start=start, unicode_len=len, pos~) => {
let sliced = ListSlice::RawStr(str~, unicode_len=len).slice(from, to)
match sliced {
ListSlice::RawStr(str=s, unicode_len=ulen) =>
InnerListOp::InsertText(
str=s,
unicode_start=start + from,
unicode_len=ulen,
pos=pos + from,
)
_ =>
InnerListOp::InsertText(
str="",
unicode_start=start + from,
unicode_len=to - from,
pos=pos + from,
)
}
}
Delete(span) => InnerListOp::Delete(span.slice(from, to))
other => other
}
}
///|
pub fn InnerListOp::is_mergeable(
self : InnerListOp,
other : InnerListOp,
) -> Bool {
match (self, other) {
(Insert(slice~, pos~), Insert(slice=other_slice, pos=other_pos)) =>
pos + slice.len() == other_pos && slice.is_mergeable(other_slice)
(Delete(span), Delete(other_span)) => span.is_mergeable(other_span)
(
InsertText(str=_, unicode_start~, unicode_len=len, pos~),
InsertText(
str=_,
unicode_start=other_unicode_start,
unicode_len=other_len,
pos=other_pos
),
) =>
pos + len == other_pos &&
unicode_start + len == other_unicode_start &&
other_len >= 0
_ => false
}
}
///|
pub fn InnerListOp::merge(
self : InnerListOp,
other : InnerListOp,
) -> InnerListOp {
match (self, other) {
(Insert(slice~, pos~), Insert(slice=other_slice, pos=other_pos)) => {
let merged = slice.merge(other_slice)
let _ = other_pos
InnerListOp::Insert(slice=merged, pos~)
}
(Delete(span), Delete(other_span)) =>
InnerListOp::Delete(span.merge(other_span))
(
InsertText(str~, unicode_start=start, unicode_len=len, pos~),
InsertText(str=other_str, unicode_start=_, unicode_len=other_len, pos=_),
) => {
let merged = String::add(str, other_str)
InnerListOp::InsertText(
str=merged,
unicode_start=start,
unicode_len=len + other_len,
pos~,
)
}
_ => self
}
}