use std::ops::Range;
use generic_btree::rle::{CanRemove, HasLength, Mergeable, Sliceable, TryInsert};
use loro_common::{CompactId, Counter, HasId, IdFull, IdSpan, Lamport, ID};
use serde::{Deserialize, Serialize};
use super::{tracker::UNKNOWN_PEER_ID, AnchorType};
#[derive(Clone, PartialEq, Eq, Copy, Serialize, Deserialize)]
pub(crate) struct RichtextChunk {
start: u32,
end: u32,
}
impl std::fmt::Debug for RichtextChunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RichtextChunk")
.field("value", &self.value())
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub(crate) enum RichtextChunkKind {
Text,
StyleAnchor,
Unknown,
MoveAnchor,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum RichtextChunkValue {
Text(Range<u32>),
StyleAnchor {
id: u32,
anchor_type: AnchorType,
},
Unknown(u32),
MoveAnchor,
}
impl RichtextChunk {
pub(crate) const UNKNOWN: u32 = u32::MAX;
pub(crate) const START_STYLE_ANCHOR: u32 = u32::MAX - 1;
pub(crate) const END_STYLE_ANCHOR: u32 = u32::MAX - 2;
pub(crate) const MOVE_ANCHOR: u32 = u32::MAX - 3;
#[inline]
pub fn new_text(range: Range<u32>) -> Self {
Self {
start: range.start,
end: range.end,
}
}
#[inline]
pub fn new_style_anchor(idx: u32, anchor_type: AnchorType) -> Self {
match anchor_type {
AnchorType::Start => Self {
start: Self::START_STYLE_ANCHOR,
end: idx,
},
AnchorType::End => Self {
start: Self::END_STYLE_ANCHOR,
end: idx,
},
}
}
#[inline]
pub fn new_unknown(len: u32) -> Self {
Self {
start: Self::UNKNOWN,
end: len,
}
}
#[inline]
pub fn new_move() -> Self {
Self {
start: Self::MOVE_ANCHOR,
end: Self::MOVE_ANCHOR,
}
}
#[inline]
pub(crate) fn kind(&self) -> RichtextChunkKind {
match self.start {
Self::START_STYLE_ANCHOR => RichtextChunkKind::StyleAnchor,
Self::END_STYLE_ANCHOR => RichtextChunkKind::StyleAnchor,
Self::UNKNOWN => RichtextChunkKind::Unknown,
Self::MOVE_ANCHOR => RichtextChunkKind::MoveAnchor,
_ => RichtextChunkKind::Text,
}
}
#[inline(always)]
pub fn len(&self) -> usize {
match self.start {
Self::UNKNOWN => self.end as usize,
Self::START_STYLE_ANCHOR | Self::END_STYLE_ANCHOR | Self::MOVE_ANCHOR => 1,
_ => (self.end - self.start) as usize,
}
}
#[inline]
pub(crate) fn value(&self) -> RichtextChunkValue {
match self.start {
Self::UNKNOWN => RichtextChunkValue::Unknown(self.end),
Self::START_STYLE_ANCHOR => RichtextChunkValue::StyleAnchor {
id: self.end,
anchor_type: AnchorType::Start,
},
Self::END_STYLE_ANCHOR => RichtextChunkValue::StyleAnchor {
id: self.end,
anchor_type: AnchorType::End,
},
Self::MOVE_ANCHOR => RichtextChunkValue::MoveAnchor,
_ => RichtextChunkValue::Text(self.start..self.end),
}
}
}
impl Mergeable for RichtextChunk {
fn can_merge(&self, rhs: &Self) -> bool {
match (self.kind(), rhs.kind()) {
(RichtextChunkKind::Text, RichtextChunkKind::Text) => self.end == rhs.start,
(RichtextChunkKind::Unknown, RichtextChunkKind::Unknown) => true,
_ => false,
}
}
fn merge_right(&mut self, rhs: &Self) {
match (self.kind(), rhs.kind()) {
(RichtextChunkKind::Text, RichtextChunkKind::Text) => self.end = rhs.end,
(RichtextChunkKind::Unknown, RichtextChunkKind::Unknown) => self.end += rhs.end,
_ => unreachable!(),
}
}
fn merge_left(&mut self, left: &Self) {
match (self.kind(), left.kind()) {
(RichtextChunkKind::Text, RichtextChunkKind::Text) => self.start = left.start,
(RichtextChunkKind::Unknown, RichtextChunkKind::Unknown) => self.end += left.end,
_ => unreachable!(),
}
}
}
impl HasLength for RichtextChunk {
#[inline(always)]
fn rle_len(&self) -> usize {
self.len()
}
}
impl Sliceable for RichtextChunk {
fn _slice(&self, range: Range<usize>) -> Self {
match self.kind() {
RichtextChunkKind::Text => {
assert!(
range.len() <= self.len(),
"range: {:?}, self: {:?}",
range,
self
);
Self {
start: self.start + range.start as u32,
end: self.start + range.end as u32,
}
}
RichtextChunkKind::StyleAnchor | RichtextChunkKind::MoveAnchor => {
assert_eq!(range.len(), 1);
*self
}
RichtextChunkKind::Unknown => {
assert!(range.len() <= self.len());
Self {
start: Self::UNKNOWN,
end: range.len() as u32,
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub(super) struct FugueSpan {
pub id: IdFull,
pub real_id: Option<CompactId>,
pub status: Status,
pub diff_status: Option<Status>,
pub origin_left: Option<CompactId>,
pub origin_right: Option<CompactId>,
pub content: RichtextChunk,
}
pub(super) enum DiffStatus {
NotChanged,
Created,
Deleted,
}
impl FugueSpan {
#[inline(always)]
pub fn id_span(&self) -> IdSpan {
IdSpan::new(
self.id.peer,
self.id.counter,
self.id.counter + self.content.len() as Counter,
)
}
#[inline]
pub fn diff(&self) -> DiffStatus {
if self.diff_status.is_none() {
return DiffStatus::NotChanged;
}
match (
self.status.is_activated(),
self.diff_status.unwrap().is_activated(),
) {
(true, false) => DiffStatus::Deleted,
(false, true) => DiffStatus::Created,
_ => DiffStatus::NotChanged,
}
}
pub fn is_activated_in_diff(&self) -> bool {
match self.diff_status {
Some(d) => d.is_activated(),
None => self.status.is_activated(),
}
}
pub(crate) fn real_id(&self) -> ID {
if let Some(id) = self.real_id {
id.to_id()
} else {
self.id.id()
}
}
}
impl Sliceable for FugueSpan {
fn _slice(&self, range: Range<usize>) -> Self {
Self {
id: self.id.inc(range.start as Counter),
real_id: self.real_id.map(|x| x.inc(range.start as Counter)),
status: self.status,
diff_status: self.diff_status,
origin_left: if range.start == 0 {
self.origin_left
} else {
Some(
self.id
.inc((range.start - 1) as Counter)
.id()
.try_into()
.unwrap(),
)
},
origin_right: self.origin_right,
content: self.content._slice(range),
}
}
}
impl HasLength for FugueSpan {
#[inline(always)]
fn rle_len(&self) -> usize {
self.content.len()
}
}
impl Mergeable for FugueSpan {
fn can_merge(&self, rhs: &Self) -> bool {
self.id.peer == rhs.id.peer
&& self.status == rhs.status
&& self.diff_status == rhs.diff_status
&& self.id.counter + self.content.len() as Counter == rhs.id.counter
&& self.id.lamport + self.content.len() as Lamport == rhs.id.lamport
&& rhs.origin_left.is_some()
&& rhs.origin_left.unwrap().peer == self.id.peer
&& rhs.origin_left.unwrap().counter.get()
== self.id.counter + self.content.len() as Counter - 1
&& self.origin_right == rhs.origin_right
&& self.content.can_merge(&rhs.content)
&& ((self.real_id.is_none() && rhs.real_id.is_none())
|| (self.real_id.is_some()
&& rhs.real_id.is_some()
&& self.real_id.unwrap().inc(self.content.len() as i32)
== rhs.real_id.unwrap()))
}
fn merge_right(&mut self, rhs: &Self) {
self.content.merge_right(&rhs.content);
}
fn merge_left(&mut self, left: &Self) {
self.id = left.id;
self.real_id = left.real_id;
self.origin_left = left.origin_left;
self.content.merge_left(&left.content);
}
}
impl TryInsert for FugueSpan {
fn try_insert(&mut self, _pos: usize, elem: Self) -> Result<(), Self>
where
Self: Sized,
{
Err(elem)
}
}
impl CanRemove for FugueSpan {
fn can_remove(&self) -> bool {
self.content.len() == 0
}
}
impl FugueSpan {
#[allow(unused)]
pub fn new(id: IdFull, content: RichtextChunk) -> Self {
Self {
id,
real_id: if id.peer == UNKNOWN_PEER_ID {
None
} else {
Some(id.id().try_into().unwrap())
},
status: Status::default(),
diff_status: None,
origin_left: None,
origin_right: None,
content,
}
}
#[inline(always)]
pub fn is_activated(&self) -> bool {
self.status.is_activated()
}
#[inline]
pub fn activated_len(&self) -> usize {
if self.is_activated() {
self.content.len()
} else {
0
}
}
}
impl HasId for FugueSpan {
fn id_start(&self) -> ID {
self.id.id()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Copy)]
pub(super) struct Status {
pub future: bool,
pub delete_times: i16,
}
impl Status {
#[inline(always)]
pub fn is_activated(&self) -> bool {
self.delete_times == 0 && !self.future
}
}