use enum_as_inner::EnumAsInner;
use fxhash::FxHashMap;
use itertools::Itertools;
use loro_delta::{array_vec::ArrayVec, delta_trait::DeltaAttr, DeltaItem, DeltaRope};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::{
container::richtext::richtext_state::RichtextStateChunk,
delta::{Delta, MapDelta, Meta, MovableListInnerDelta, ResolvedMapDelta, TreeDelta, TreeDiff},
diff_calc::DiffMode,
handler::ValueOrHandler,
op::SliceWithId,
utils::string_slice::StringSlice,
InternalString,
};
use std::{borrow::Cow, hash::Hash};
use loro_common::{ContainerID, LoroValue, TreeID};
use crate::{container::idx::ContainerIdx, version::Frontiers};
#[derive(Debug, Clone)]
pub struct ContainerDiff {
pub id: ContainerID,
pub path: Vec<(ContainerID, Index)>,
pub(crate) idx: ContainerIdx,
pub is_unknown: bool,
pub diff: Diff,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EventTriggerKind {
Local,
Import,
Checkout,
}
impl std::fmt::Display for EventTriggerKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EventTriggerKind::Local => write!(f, "local"),
EventTriggerKind::Import => write!(f, "import"),
EventTriggerKind::Checkout => write!(f, "checkout"),
}
}
}
impl EventTriggerKind {
#[inline]
pub fn is_local(&self) -> bool {
matches!(self, EventTriggerKind::Local)
}
#[inline]
pub fn is_import(&self) -> bool {
matches!(self, EventTriggerKind::Import)
}
#[inline]
pub fn is_checkout(&self) -> bool {
matches!(self, EventTriggerKind::Checkout)
}
}
#[derive(Debug, Clone)]
pub struct DiffEvent<'a> {
pub current_target: Option<ContainerID>,
pub events: &'a [&'a ContainerDiff],
pub event_meta: &'a DocDiff,
}
#[derive(Debug, Clone)]
pub struct DocDiff {
pub from: Frontiers,
pub to: Frontiers,
pub origin: InternalString,
pub by: EventTriggerKind,
pub diff: Vec<ContainerDiff>,
}
#[derive(Debug, Clone)]
pub(crate) struct InternalContainerDiff {
pub(crate) idx: ContainerIdx,
pub(crate) bring_back: bool,
pub(crate) is_container_deleted: bool,
pub(crate) diff: DiffVariant,
pub(crate) diff_mode: DiffMode,
}
#[derive(Default, Debug, Clone, EnumAsInner)]
pub(crate) enum DiffVariant {
#[default]
None,
Internal(InternalDiff),
External(Diff),
}
#[derive(Debug, Clone)]
pub(crate) struct InternalDocDiff<'a> {
pub(crate) origin: InternalString,
pub(crate) by: EventTriggerKind,
pub(crate) diff: Cow<'a, [InternalContainerDiff]>,
pub(crate) new_version: Cow<'a, Frontiers>,
}
impl InternalDocDiff<'_> {
pub fn into_owned(self) -> InternalDocDiff<'static> {
InternalDocDiff {
origin: self.origin,
by: self.by,
diff: Cow::Owned((*self.diff).to_owned()),
new_version: Cow::Owned((*self.new_version).to_owned()),
}
}
pub fn can_merge(&self, other: &Self) -> bool {
self.by == other.by
}
}
pub type Path = SmallVec<[Index; 4]>;
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, enum_as_inner::EnumAsInner)]
pub enum Index {
Key(InternalString),
Seq(usize),
Node(TreeID),
}
impl std::fmt::Debug for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Key(arg0) => write!(f, "Index::Key(\"{}\")", arg0),
Self::Seq(arg0) => write!(f, "Index::Seq({})", arg0),
Self::Node(arg0) => write!(f, "Index::Node({})", arg0),
}
}
}
impl std::fmt::Display for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Index::Key(key) => write!(f, "{}", key),
Index::Seq(s) => write!(f, "{}", s),
Index::Node(id) => write!(f, "{}@{}", id.peer, id.counter),
}
}
}
impl From<usize> for Index {
fn from(s: usize) -> Self {
Index::Seq(s)
}
}
impl TryFrom<&str> for Index {
type Error = &'static str;
fn try_from(s: &str) -> Result<Self, &'static str> {
if s.is_empty() {
return Ok(Index::Key(InternalString::default()));
}
let c = s.chars().next().unwrap();
if c.is_ascii_digit() {
if let Ok(seq) = s.parse::<usize>() {
Ok(Index::Seq(seq))
} else if let Ok(id) = s.try_into() {
Ok(Index::Node(id))
} else {
Ok(Index::Key(InternalString::from(s)))
}
} else {
Ok(Index::Key(InternalString::from(s)))
}
}
}
impl DiffVariant {
pub fn compose(self, other: Self) -> Result<Self, Self> {
match (self, other) {
(DiffVariant::Internal(a), DiffVariant::Internal(b)) => {
Ok(DiffVariant::Internal(a.compose(b)?))
}
(DiffVariant::External(a), DiffVariant::External(b)) => {
Ok(DiffVariant::External(a.compose(b)?))
}
(a, _) => Err(a),
}
}
pub fn is_empty(&self) -> bool {
match self {
DiffVariant::Internal(diff) => diff.is_empty(),
DiffVariant::External(diff) => diff.is_empty(),
DiffVariant::None => true,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, EnumAsInner)]
pub(crate) enum InternalDiff {
ListRaw(Delta<SliceWithId>),
RichtextRaw(DeltaRope<RichtextStateChunk, ()>),
Map(MapDelta),
Tree(TreeDelta),
MovableList(MovableListInnerDelta),
#[cfg(feature = "counter")]
Counter(f64),
Unknown,
}
impl From<InternalDiff> for DiffVariant {
fn from(diff: InternalDiff) -> Self {
DiffVariant::Internal(diff)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct ListDeltaMeta {
pub from_move: bool,
}
impl Meta for ListDeltaMeta {
fn is_empty(&self) -> bool {
!self.from_move
}
fn compose(
&mut self,
other: &Self,
type_pair: (crate::delta::DeltaType, crate::delta::DeltaType),
) {
if let (crate::delta::DeltaType::Insert, crate::delta::DeltaType::Insert) = type_pair {
unreachable!()
}
self.from_move = self.from_move || other.from_move;
}
fn is_mergeable(&self, other: &Self) -> bool {
self.from_move == other.from_move
}
fn merge(&mut self, _other: &Self) {}
}
impl DeltaAttr for ListDeltaMeta {
fn compose(&mut self, other: &Self) {
self.from_move = self.from_move || other.from_move;
}
fn attr_is_empty(&self) -> bool {
!self.from_move
}
}
pub type ListDiffInsertItem = ArrayVec<ValueOrHandler, 8>;
pub type ListDiffItem = DeltaItem<ListDiffInsertItem, ListDeltaMeta>;
pub type ListDiff = DeltaRope<ListDiffInsertItem, ListDeltaMeta>;
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TextMeta(pub FxHashMap<String, LoroValue>);
impl From<TextMeta> for FxHashMap<String, LoroValue> {
fn from(value: TextMeta) -> Self {
value.0
}
}
impl From<FxHashMap<String, LoroValue>> for TextMeta {
fn from(value: FxHashMap<String, LoroValue>) -> Self {
TextMeta(value)
}
}
pub type TextDiffItem = DeltaItem<StringSlice, TextMeta>;
pub type TextDiff = DeltaRope<StringSlice, TextMeta>;
impl DeltaAttr for TextMeta {
fn compose(&mut self, other: &Self) {
for (key, value) in other.0.iter() {
self.0.insert(key.clone(), value.clone());
}
}
fn attr_is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[non_exhaustive]
#[derive(Clone, Debug, EnumAsInner)]
pub enum Diff {
List(ListDiff),
Text(TextDiff),
Map(ResolvedMapDelta),
Tree(TreeDiff),
#[cfg(feature = "counter")]
Counter(f64),
Unknown,
}
impl From<Diff> for DiffVariant {
fn from(diff: Diff) -> Self {
DiffVariant::External(diff)
}
}
impl InternalDiff {
pub(crate) fn is_empty(&self) -> bool {
match self {
InternalDiff::ListRaw(s) => s.is_empty(),
InternalDiff::RichtextRaw(t) => t.is_empty(),
InternalDiff::Map(m) => m.updated.is_empty(),
InternalDiff::Tree(t) => t.is_empty(),
InternalDiff::MovableList(t) => t.is_empty(),
#[cfg(feature = "counter")]
InternalDiff::Counter(c) => c.abs() < f64::EPSILON,
InternalDiff::Unknown => true,
}
}
pub(crate) fn compose(self, diff: InternalDiff) -> Result<Self, Self> {
match (self, diff) {
(InternalDiff::ListRaw(a), InternalDiff::ListRaw(b)) => {
Ok(InternalDiff::ListRaw(a.compose(b)))
}
(InternalDiff::RichtextRaw(a), InternalDiff::RichtextRaw(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))),
(a, _) => Err(a),
}
}
}
impl Diff {
pub fn compose_ref(&mut self, diff: &Diff) {
match (self, diff) {
(Diff::List(a), Diff::List(b)) => {
a.compose(b);
}
(Diff::Text(a), Diff::Text(b)) => {
a.compose(b);
}
(Diff::Map(a), Diff::Map(b)) => {
*a = a.clone().compose(b.clone());
}
(Diff::Tree(a), Diff::Tree(b)) => {
*a = a.clone().compose(b.clone());
}
#[cfg(feature = "counter")]
(Diff::Counter(a), Diff::Counter(b)) => *a += b,
(_, _) => unreachable!(),
}
}
pub fn compose(self, diff: Diff) -> Result<Self, Self> {
match (self, diff) {
(Diff::List(mut a), Diff::List(b)) => {
a.compose(&b);
Ok(Diff::List(a))
}
(Diff::Text(mut a), Diff::Text(b)) => {
a.compose(&b);
Ok(Diff::Text(a))
}
(Diff::Map(a), Diff::Map(b)) => Ok(Diff::Map(a.compose(b))),
(Diff::Tree(a), Diff::Tree(b)) => Ok(Diff::Tree(a.compose(b))),
#[cfg(feature = "counter")]
(Diff::Counter(a), Diff::Counter(b)) => Ok(Diff::Counter(a + b)),
(a, _) => Err(a),
}
}
pub fn transform(&mut self, other: &Self, left_prior: bool) {
match (self, other) {
(Diff::List(a), Diff::List(b)) => a.transform_(b, left_prior),
(Diff::Text(a), Diff::Text(b)) => a.transform_(b, left_prior),
(Diff::Map(a), Diff::Map(b)) => a.transform(b, left_prior),
(Diff::Tree(a), Diff::Tree(b)) => a.transform(b, left_prior),
#[cfg(feature = "counter")]
(Diff::Counter(a), Diff::Counter(b)) => {
if left_prior {
*a += b;
} else {
*a -= b;
}
}
_ => {}
}
}
pub fn is_empty(&self) -> bool {
match self {
Diff::List(s) => s.is_empty(),
Diff::Text(t) => t.is_empty(),
Diff::Map(m) => m.updated.is_empty(),
Diff::Tree(t) => t.diff.is_empty(),
#[cfg(feature = "counter")]
Diff::Counter(c) => c.abs() < f64::EPSILON,
Diff::Unknown => true,
}
}
#[allow(unused)]
pub(crate) fn concat(self, diff: Diff) -> Diff {
match (self, diff) {
(Diff::List(mut a), Diff::List(b)) => {
a.compose(&b);
Diff::List(a)
}
(Diff::Text(mut a), Diff::Text(b)) => {
a.compose(&b);
Diff::Text(a)
}
(Diff::Map(a), Diff::Map(b)) => {
let mut a = a;
for (k, v) in b.updated {
a = a.with_entry(k, v);
}
Diff::Map(a)
}
(Diff::Tree(a), Diff::Tree(b)) => Diff::Tree(a.extend(b.diff)),
#[cfg(feature = "counter")]
(Diff::Counter(a), Diff::Counter(b)) => Diff::Counter(a + b),
_ => unreachable!(),
}
}
pub(crate) fn transform_cursor(&self, pos: usize, left_prior: bool) -> usize {
match self {
Diff::List(list) => list.transform_pos(pos, left_prior),
Diff::Text(text) => text.transform_pos(pos, left_prior),
_ => pos,
}
}
}
pub fn str_to_path(s: &str) -> Option<Vec<Index>> {
s.split('/').map(|x| x.try_into()).try_collect().ok()
}
pub fn path_to_str(path: &[Index]) -> String {
path.iter().map(|x| x.to_string()).join("/")
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use itertools::Itertools;
use loro_common::LoroValue;
use crate::{ApplyDiff, LoroDoc};
#[test]
fn test_text_event() {
let loro = LoroDoc::new();
let _g = loro.subscribe_root(Arc::new(|event| {
let mut value = LoroValue::String(Default::default());
value.apply_diff(&event.events.iter().map(|x| x.diff.clone()).collect_vec());
assert_eq!(value, "h223ello".into());
}));
let mut txn = loro.txn().unwrap();
let text = loro.get_text("id");
text.insert_with_txn(&mut txn, 0, "hello").unwrap();
text.insert_with_txn(&mut txn, 1, "223").unwrap();
txn.commit().unwrap();
}
}