mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-05 20:17:13 +00:00
Merge branch 'dev' into feat-gc
This commit is contained in:
commit
4a27a0645f
45 changed files with 985 additions and 731 deletions
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
@ -4,7 +4,7 @@ on:
|
|||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
branches: ["main", "dev"]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
env:
|
||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1351,6 +1351,7 @@ dependencies = [
|
|||
"criterion 0.5.1",
|
||||
"fractional_index",
|
||||
"imbl",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"serde",
|
||||
"smallvec",
|
||||
|
|
|
@ -16,6 +16,7 @@ imbl = "^3.0"
|
|||
smallvec = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive", "rc"], optional = true }
|
||||
rand = { version = "^0.8" }
|
||||
once_cell = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
fraction_index = { version = "^2.0", package = "fractional_index" }
|
||||
|
|
|
@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize};
|
|||
mod jitter;
|
||||
|
||||
const TERMINATOR: u8 = 128;
|
||||
static DEFAULT_FRACTIONAL_INDEX: once_cell::sync::Lazy<FractionalIndex> =
|
||||
once_cell::sync::Lazy::new(|| FractionalIndex(Arc::new(vec![TERMINATOR])));
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
@ -16,7 +18,7 @@ pub struct FractionalIndex(Arc<Vec<u8>>);
|
|||
|
||||
impl Default for FractionalIndex {
|
||||
fn default() -> Self {
|
||||
FractionalIndex(Arc::new(vec![TERMINATOR]))
|
||||
DEFAULT_FRACTIONAL_INDEX.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
1
crates/fuzz/fuzz/Cargo.lock
generated
1
crates/fuzz/fuzz/Cargo.lock
generated
|
@ -780,6 +780,7 @@ name = "loro_fractional_index"
|
|||
version = "0.16.2"
|
||||
dependencies = [
|
||||
"imbl",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"serde",
|
||||
"smallvec",
|
||||
|
|
|
@ -119,6 +119,9 @@ impl Actor {
|
|||
}
|
||||
|
||||
if let Some(idx) = idx {
|
||||
if let Container::Tree(tree) = &idx {
|
||||
tree.set_enable_fractional_index(0);
|
||||
}
|
||||
self.add_new_container(idx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use loro::{Container, ContainerID, ContainerType, LoroDoc, LoroMap, LoroValue};
|
|||
|
||||
use crate::{
|
||||
actions::{Actionable, FromGenericAction, GenericAction},
|
||||
actor::{ActionExecutor, ActorTrait},
|
||||
actor::{assert_value_eq, ActionExecutor, ActorTrait},
|
||||
crdt_fuzzer::FuzzValue,
|
||||
value::{ApplyDiff, ContainerTracker, MapTracker, Value},
|
||||
};
|
||||
|
@ -66,7 +66,11 @@ impl ActorTrait for MapActor {
|
|||
let map = self.loro.get_map("map");
|
||||
let value_a = map.get_deep_value();
|
||||
let value_b = self.tracker.lock().unwrap().to_value();
|
||||
assert_eq!(&value_a, value_b.into_map().unwrap().get("map").unwrap());
|
||||
assert_value_eq(
|
||||
&value_a,
|
||||
value_b.into_map().unwrap().get("map").unwrap(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
fn container_len(&self) -> u8 {
|
||||
|
|
|
@ -10,11 +10,10 @@ use loro::{
|
|||
event::Diff, Container, ContainerID, ContainerType, LoroDoc, LoroError, LoroTree, LoroValue,
|
||||
TreeExternalDiff, TreeID,
|
||||
};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::{
|
||||
actions::{Actionable, FromGenericAction, GenericAction},
|
||||
actor::{ActionExecutor, ActorTrait},
|
||||
actor::{assert_value_eq, ActionExecutor, ActorTrait},
|
||||
crdt_fuzzer::FuzzValue,
|
||||
value::{ApplyDiff, ContainerTracker, MapTracker, Value},
|
||||
};
|
||||
|
@ -117,6 +116,7 @@ impl TreeActor {
|
|||
);
|
||||
|
||||
let root = loro.get_tree("tree");
|
||||
root.set_enable_fractional_index(0);
|
||||
Self {
|
||||
loro,
|
||||
containers: vec![root],
|
||||
|
@ -135,7 +135,11 @@ impl ActorTrait for TreeActor {
|
|||
let tree = loro.get_tree("tree");
|
||||
let result = tree.get_value_with_meta();
|
||||
let tracker = self.tracker.lock().unwrap().to_value();
|
||||
assert_eq!(&result, tracker.into_map().unwrap().get("tree").unwrap());
|
||||
assert_value_eq(
|
||||
&result,
|
||||
tracker.into_map().unwrap().get("tree").unwrap(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
fn add_new_container(&mut self, container: Container) {
|
||||
|
@ -180,7 +184,7 @@ impl Actionable for TreeAction {
|
|||
}
|
||||
*parent = (nodes[parent_idx].peer, nodes[parent_idx].counter);
|
||||
*index %= tree
|
||||
.children_num(Some(TreeID::new(parent.0, parent.1)))
|
||||
.children_num(TreeID::new(parent.0, parent.1))
|
||||
.unwrap_or(0)
|
||||
+ 1;
|
||||
}
|
||||
|
@ -422,7 +426,7 @@ impl ApplyDiff for TreeTracker {
|
|||
index,
|
||||
position,
|
||||
} => {
|
||||
self.create_node(target, parent, position.to_string(), index);
|
||||
self.create_node(target, &parent.tree_id(), position.to_string(), index);
|
||||
}
|
||||
TreeExternalDiff::Delete { .. } => {
|
||||
let node = self.find_node_by_id(target).unwrap();
|
||||
|
@ -438,9 +442,10 @@ impl ApplyDiff for TreeTracker {
|
|||
parent,
|
||||
index,
|
||||
position,
|
||||
..
|
||||
} => {
|
||||
let Some(node) = self.find_node_by_id(target) else {
|
||||
self.create_node(target, parent, position.to_string(), index);
|
||||
self.create_node(target, &parent.tree_id(), position.to_string(), index);
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -452,10 +457,10 @@ impl ApplyDiff for TreeTracker {
|
|||
let index = self.tree.iter().position(|n| n.id == target).unwrap();
|
||||
self.tree.remove(index)
|
||||
};
|
||||
node.parent = *parent;
|
||||
node.parent = parent.tree_id();
|
||||
node.position = position.to_string();
|
||||
if let Some(parent) = parent {
|
||||
let parent = self.find_node_by_id_mut(*parent).unwrap();
|
||||
if let Some(parent) = parent.tree_id() {
|
||||
let parent = self.find_node_by_id_mut(parent).unwrap();
|
||||
parent.children.insert(*index, node);
|
||||
} else {
|
||||
if self.find_node_by_id_mut(target).is_some() {
|
||||
|
|
|
@ -92,6 +92,8 @@ pub enum LoroTreeError {
|
|||
TreeNodeNotExist(TreeID),
|
||||
#[error("The index({index}) should be <= the length of children ({len})")]
|
||||
IndexOutOfBound { len: usize, index: usize },
|
||||
#[error("Fractional index is not enabled, you should enable it first by `LoroTree::set_enable_fractional_index`")]
|
||||
FractionalIndexNotEnabled,
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{fmt::Display, io::Write, str::Bytes, sync::Arc};
|
||||
use std::{fmt::Display, io::Write, sync::Arc};
|
||||
|
||||
use arbitrary::Arbitrary;
|
||||
use enum_as_inner::EnumAsInner;
|
||||
|
|
|
@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
|||
mod tree {
|
||||
use super::*;
|
||||
use criterion::{AxisScale, BenchmarkId, PlotConfiguration};
|
||||
use loro_internal::LoroDoc;
|
||||
use loro_internal::{LoroDoc, TreeParentId};
|
||||
use rand::{rngs::StdRng, Rng};
|
||||
|
||||
pub fn tree_move(c: &mut Criterion) {
|
||||
|
@ -22,7 +22,7 @@ mod tree {
|
|||
let loro = LoroDoc::new_auto_commit();
|
||||
let tree = loro.get_tree("tree");
|
||||
for idx in 0..*i {
|
||||
tree.create_at(None, idx as usize).unwrap();
|
||||
tree.create_at(TreeParentId::Root, idx as usize).unwrap();
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -39,15 +39,16 @@ mod tree {
|
|||
let mut ids = vec![];
|
||||
for _ in 0..SIZE {
|
||||
let pos = rng.gen::<usize>() % (ids.len() + 1);
|
||||
ids.push(tree.create_at(None, pos).unwrap());
|
||||
ids.push(tree.create_at(TreeParentId::Root, pos).unwrap());
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..*i {
|
||||
tree.create_at(None, 0).unwrap();
|
||||
tree.create_at(TreeParentId::Root, 0).unwrap();
|
||||
let i = rng.gen::<usize>() % SIZE;
|
||||
let j = rng.gen::<usize>() % SIZE;
|
||||
tree.mov(ids[i], ids[j]).unwrap_or_default();
|
||||
tree.mov(ids[i], TreeParentId::Node(ids[j]))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -62,14 +63,14 @@ mod tree {
|
|||
let mut versions = vec![];
|
||||
let size = 1000;
|
||||
for _ in 0..size {
|
||||
ids.push(tree.create(None).unwrap())
|
||||
ids.push(tree.create(TreeParentId::Root).unwrap())
|
||||
}
|
||||
let mut rng: StdRng = rand::SeedableRng::seed_from_u64(0);
|
||||
let mut n = 1000;
|
||||
while n > 0 {
|
||||
let i = rng.gen::<usize>() % size;
|
||||
let j = rng.gen::<usize>() % size;
|
||||
if tree.mov(ids[i], ids[j]).is_ok() {
|
||||
if tree.mov(ids[i], TreeParentId::Node(ids[j])).is_ok() {
|
||||
versions.push(loro.oplog_frontiers());
|
||||
n -= 1;
|
||||
};
|
||||
|
@ -90,11 +91,13 @@ mod tree {
|
|||
let tree = loro.get_tree("tree");
|
||||
let mut ids = vec![];
|
||||
let mut versions = vec![];
|
||||
let id1 = tree.create(None).unwrap();
|
||||
let id1 = tree.create(TreeParentId::Root).unwrap();
|
||||
ids.push(id1);
|
||||
versions.push(loro.oplog_frontiers());
|
||||
for _ in 1..depth {
|
||||
let id = tree.create(*ids.last().unwrap()).unwrap();
|
||||
let id = tree
|
||||
.create(TreeParentId::Node(*ids.last().unwrap()))
|
||||
.unwrap();
|
||||
ids.push(id);
|
||||
versions.push(loro.oplog_frontiers());
|
||||
}
|
||||
|
@ -118,7 +121,7 @@ mod tree {
|
|||
let mut ids = vec![];
|
||||
let size = 1000;
|
||||
for _ in 0..size {
|
||||
ids.push(tree_a.create(None).unwrap())
|
||||
ids.push(tree_a.create(TreeParentId::Root).unwrap())
|
||||
}
|
||||
doc_b.import(&doc_a.export_snapshot()).unwrap();
|
||||
let mut rng: StdRng = rand::SeedableRng::seed_from_u64(0);
|
||||
|
@ -128,10 +131,14 @@ mod tree {
|
|||
let i = rng.gen::<usize>() % size;
|
||||
let j = rng.gen::<usize>() % size;
|
||||
if t % 2 == 0 {
|
||||
tree_a.mov(ids[i], ids[j]).unwrap_or_default();
|
||||
tree_a
|
||||
.mov(ids[i], TreeParentId::Node(ids[j]))
|
||||
.unwrap_or_default();
|
||||
doc_b.import(&doc_a.export_from(&doc_b.oplog_vv())).unwrap();
|
||||
} else {
|
||||
tree_b.mov(ids[i], ids[j]).unwrap_or_default();
|
||||
tree_b
|
||||
.mov(ids[i], TreeParentId::Node(ids[j]))
|
||||
.unwrap_or_default();
|
||||
doc_a.import(&doc_b.export_from(&doc_a.oplog_vv())).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use loro_internal::LoroDoc;
|
||||
use loro_internal::{LoroDoc, TreeParentId};
|
||||
use rand::{rngs::StdRng, Rng};
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -10,11 +10,13 @@ fn checkout() {
|
|||
let tree = loro.get_tree("tree");
|
||||
let mut ids = vec![];
|
||||
let mut versions = vec![];
|
||||
let id1 = tree.create_at(None, 0).unwrap();
|
||||
let id1 = tree.create_at(TreeParentId::Root, 0).unwrap();
|
||||
ids.push(id1);
|
||||
versions.push(loro.oplog_frontiers());
|
||||
for _ in 1..depth {
|
||||
let id = tree.create_at(*ids.last().unwrap(), 0).unwrap();
|
||||
let id = tree
|
||||
.create_at(TreeParentId::Node(*ids.last().unwrap()), 0)
|
||||
.unwrap();
|
||||
ids.push(id);
|
||||
versions.push(loro.oplog_frontiers());
|
||||
}
|
||||
|
@ -34,7 +36,7 @@ fn mov() {
|
|||
let mut ids = vec![];
|
||||
let size = 10000;
|
||||
for _ in 0..size {
|
||||
ids.push(tree.create_at(None, 0).unwrap())
|
||||
ids.push(tree.create_at(TreeParentId::Root, 0).unwrap())
|
||||
}
|
||||
let mut rng: StdRng = rand::SeedableRng::seed_from_u64(0);
|
||||
let n = 100000;
|
||||
|
@ -42,8 +44,8 @@ fn mov() {
|
|||
for _ in 0..n {
|
||||
let i = rng.gen::<usize>() % size;
|
||||
let j = rng.gen::<usize>() % size;
|
||||
let children_num = tree.children_num(Some(ids[j])).unwrap_or(0);
|
||||
tree.move_to(ids[i], ids[j], children_num)
|
||||
let children_num = tree.children_num(&TreeParentId::Node(ids[j])).unwrap_or(0);
|
||||
tree.move_to(ids[i], TreeParentId::Node(ids[j]), children_num)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
println!("encode snapshot size {:?}", loro.export_snapshot().len());
|
||||
|
@ -59,7 +61,7 @@ fn create() {
|
|||
let loro = LoroDoc::default();
|
||||
let tree = loro.get_tree("tree");
|
||||
for _ in 0..size {
|
||||
tree.create_at(None, 0).unwrap();
|
||||
tree.create_at(TreeParentId::Root, 0).unwrap();
|
||||
}
|
||||
println!("encode snapshot size {:?}\n", loro.export_snapshot().len());
|
||||
println!(
|
||||
|
|
|
@ -5,8 +5,6 @@ pub struct Configure {
|
|||
pub(crate) text_style_config: Arc<RwLock<StyleConfigMap>>,
|
||||
record_timestamp: Arc<AtomicBool>,
|
||||
pub(crate) merge_interval: Arc<AtomicI64>,
|
||||
/// do not use `jitter` by default
|
||||
pub(crate) tree_position_jitter: Arc<AtomicU8>,
|
||||
}
|
||||
|
||||
impl Default for Configure {
|
||||
|
@ -15,7 +13,6 @@ impl Default for Configure {
|
|||
text_style_config: Arc::new(RwLock::new(StyleConfigMap::default_rich_text_config())),
|
||||
record_timestamp: Arc::new(AtomicBool::new(false)),
|
||||
merge_interval: Arc::new(AtomicI64::new(1000 * 1000)),
|
||||
tree_position_jitter: Arc::new(AtomicU8::new(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,10 +31,6 @@ impl Configure {
|
|||
self.merge_interval
|
||||
.load(std::sync::atomic::Ordering::Relaxed),
|
||||
)),
|
||||
tree_position_jitter: Arc::new(AtomicU8::new(
|
||||
self.tree_position_jitter
|
||||
.load(std::sync::atomic::Ordering::Relaxed),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,11 +48,6 @@ impl Configure {
|
|||
.store(record, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_fractional_index_jitter(&self, jitter: u8) {
|
||||
self.tree_position_jitter
|
||||
.store(jitter, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn merge_interval(&self) -> i64 {
|
||||
self.merge_interval
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
|
@ -77,7 +65,7 @@ pub struct DefaultRandom;
|
|||
#[cfg(test)]
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, AtomicI64, AtomicU8},
|
||||
atomic::{AtomicBool, AtomicI64},
|
||||
Arc, RwLock,
|
||||
};
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -21,16 +21,21 @@ pub struct TreeDiffItem {
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TreeExternalDiff {
|
||||
Create {
|
||||
parent: Option<TreeID>,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
position: FractionalIndex,
|
||||
},
|
||||
Move {
|
||||
parent: Option<TreeID>,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
position: FractionalIndex,
|
||||
old_parent: TreeParentId,
|
||||
old_index: usize,
|
||||
},
|
||||
Delete {
|
||||
old_parent: TreeParentId,
|
||||
old_index: usize,
|
||||
},
|
||||
Delete,
|
||||
}
|
||||
|
||||
impl TreeDiff {
|
||||
|
|
|
@ -50,7 +50,7 @@ impl DiffCalculatorTrait for TreeDiffCalculator {
|
|||
|
||||
fn apply_change(
|
||||
&mut self,
|
||||
_oplog: &OpLog,
|
||||
oplog: &OpLog,
|
||||
op: crate::op::RichOp,
|
||||
_vv: Option<&crate::VersionVector>,
|
||||
) {
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
diff::{myers_diff, DiffHandler, OperateProxy},
|
||||
event::{Diff, TextDiffItem},
|
||||
op::ListSlice,
|
||||
state::{ContainerState, IndexType, State},
|
||||
state::{ContainerState, IndexType, State, TreeParentId},
|
||||
txn::EventHint,
|
||||
utils::{string_slice::StringSlice, utf16::count_utf16_len},
|
||||
};
|
||||
|
@ -1171,7 +1171,7 @@ impl Handler {
|
|||
index: _,
|
||||
position,
|
||||
} => {
|
||||
if let Some(p) = parent.as_mut() {
|
||||
if let TreeParentId::Node(p) = &mut parent {
|
||||
remap_tree_id(p, container_remap)
|
||||
}
|
||||
remap_tree_id(&mut target, container_remap);
|
||||
|
@ -1206,14 +1206,16 @@ impl Handler {
|
|||
mut parent,
|
||||
index: _,
|
||||
position,
|
||||
old_parent: _,
|
||||
old_index: _,
|
||||
} => {
|
||||
if let Some(p) = parent.as_mut() {
|
||||
if let TreeParentId::Node(p) = &mut parent {
|
||||
remap_tree_id(p, container_remap)
|
||||
}
|
||||
remap_tree_id(&mut target, container_remap);
|
||||
x.move_at_with_target_for_apply_diff(parent, position, target)?;
|
||||
}
|
||||
TreeExternalDiff::Delete => {
|
||||
TreeExternalDiff::Delete { .. } => {
|
||||
remap_tree_id(&mut target, container_remap);
|
||||
if x.contains(target) {
|
||||
x.delete(target)?;
|
||||
|
@ -3939,6 +3941,7 @@ mod test {
|
|||
|
||||
use super::{HandlerTrait, TextDelta};
|
||||
use crate::loro::LoroDoc;
|
||||
use crate::state::TreeParentId;
|
||||
use crate::version::Frontiers;
|
||||
use crate::{fx_map, ToJson};
|
||||
use loro_common::ID;
|
||||
|
@ -4104,7 +4107,7 @@ mod test {
|
|||
loro.set_peer_id(1).unwrap();
|
||||
let tree = loro.get_tree("root");
|
||||
let id = loro
|
||||
.with_txn(|txn| tree.create_with_txn(txn, None, 0))
|
||||
.with_txn(|txn| tree.create_with_txn(txn, TreeParentId::Root, 0))
|
||||
.unwrap();
|
||||
loro.with_txn(|txn| {
|
||||
let meta = tree.get_meta(id)?;
|
||||
|
@ -4134,11 +4137,11 @@ mod test {
|
|||
let tree = loro.get_tree("root");
|
||||
let text = loro.get_text("text");
|
||||
loro.with_txn(|txn| {
|
||||
let id = tree.create_with_txn(txn, None, 0)?;
|
||||
let id = tree.create_with_txn(txn, TreeParentId::Root, 0)?;
|
||||
let meta = tree.get_meta(id)?;
|
||||
meta.insert_with_txn(txn, "a", 1.into())?;
|
||||
text.insert_with_txn(txn, 0, "abc")?;
|
||||
let _id2 = tree.create_with_txn(txn, None, 0)?;
|
||||
let _id2 = tree.create_with_txn(txn, TreeParentId::Root, 0)?;
|
||||
meta.insert_with_txn(txn, "b", 2.into())?;
|
||||
Ok(id)
|
||||
})
|
||||
|
|
|
@ -93,8 +93,8 @@ impl TreeInner {
|
|||
self.children_links.get(&parent).map(|x| x.len())
|
||||
}
|
||||
|
||||
fn is_parent(&self, target: TreeID, parent: Option<TreeID>) -> bool {
|
||||
self.parent_links.get(&target) == Some(&parent)
|
||||
fn is_parent(&self, target: &TreeID, parent: &Option<TreeID>) -> bool {
|
||||
self.parent_links.get(target) == Some(parent)
|
||||
}
|
||||
|
||||
fn get_index_by_tree_id(&self, target: &TreeID) -> Option<usize> {
|
||||
|
@ -164,7 +164,9 @@ impl HandlerTrait for TreeHandler {
|
|||
let mut q = children
|
||||
.map(|c| {
|
||||
VecDeque::from_iter(
|
||||
c.iter().enumerate().zip(std::iter::repeat(None::<TreeID>)),
|
||||
c.iter()
|
||||
.enumerate()
|
||||
.zip(std::iter::repeat(TreeParentId::Root)),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
@ -179,7 +181,7 @@ impl HandlerTrait for TreeHandler {
|
|||
|
||||
if let Some(children) = t.value.children_links.get(&Some(*target)) {
|
||||
for (idx, child) in children.iter().enumerate() {
|
||||
q.push_back(((idx, child), Some(real_id)));
|
||||
q.push_back(((idx, child), TreeParentId::Node(real_id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +212,6 @@ impl HandlerTrait for TreeHandler {
|
|||
self.inner.attached_handler()
|
||||
}
|
||||
|
||||
// TODO:
|
||||
fn get_value(&self) -> LoroValue {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(t) => {
|
||||
|
@ -291,27 +292,25 @@ impl TreeHandler {
|
|||
crate::op::RawOpContent::Tree(Arc::new(TreeOp::Delete { target })),
|
||||
EventHint::Tree(smallvec![TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Delete,
|
||||
action: TreeExternalDiff::Delete {
|
||||
old_parent: self.get_node_parent(&target).unwrap(),
|
||||
old_index: self.get_index_by_tree_id(&target).unwrap(),
|
||||
},
|
||||
}]),
|
||||
&inner.state,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create<T: Into<Option<TreeID>>>(&self, parent: T) -> LoroResult<TreeID> {
|
||||
let parent = parent.into();
|
||||
let index: usize = self.children_num(parent).unwrap_or(0);
|
||||
pub fn create(&self, parent: TreeParentId) -> LoroResult<TreeID> {
|
||||
let index: usize = self.children_num(&parent).unwrap_or(0);
|
||||
self.create_at(parent, index)
|
||||
}
|
||||
|
||||
pub fn create_at<T: Into<Option<TreeID>>>(
|
||||
&self,
|
||||
parent: T,
|
||||
index: usize,
|
||||
) -> LoroResult<TreeID> {
|
||||
pub fn create_at(&self, parent: TreeParentId, index: usize) -> LoroResult<TreeID> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(t) => {
|
||||
let t = &mut t.try_lock().unwrap().value;
|
||||
Ok(t.create(parent.into(), index))
|
||||
Ok(t.create(parent.tree_id(), index))
|
||||
}
|
||||
MaybeDetached::Attached(a) => {
|
||||
a.with_txn(|txn| self.create_with_txn(txn, parent, index))
|
||||
|
@ -322,23 +321,33 @@ impl TreeHandler {
|
|||
/// For undo/redo, Specify the TreeID of the created node
|
||||
pub(crate) fn create_at_with_target_for_apply_diff(
|
||||
&self,
|
||||
parent: Option<TreeID>,
|
||||
parent: TreeParentId,
|
||||
position: FractionalIndex,
|
||||
target: TreeID,
|
||||
) -> LoroResult<bool> {
|
||||
let MaybeDetached::Attached(a) = &self.inner else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if let Some(p) = self.get_node_parent(&target) {
|
||||
if p == parent {
|
||||
return Ok(false);
|
||||
// If parent is deleted, we need to create the node, so this op from move_apply_diff
|
||||
} else if !p.is_some_and(|p| !self.contains(p)) {
|
||||
return self.move_at_with_target_for_apply_diff(parent, position, target);
|
||||
}
|
||||
match p {
|
||||
TreeParentId::Node(p) => {
|
||||
if self.contains(p) {
|
||||
return self.move_at_with_target_for_apply_diff(parent, position, target);
|
||||
}
|
||||
}
|
||||
TreeParentId::Root => {
|
||||
return self.move_at_with_target_for_apply_diff(parent, position, target);
|
||||
}
|
||||
TreeParentId::Deleted | TreeParentId::Unexist => {}
|
||||
}
|
||||
}
|
||||
|
||||
let with_event = !parent.is_some_and(|p| !self.contains(p));
|
||||
let with_event = !parent.tree_id().is_some_and(|p| !self.contains(p));
|
||||
if !with_event {
|
||||
return Ok(false);
|
||||
}
|
||||
|
@ -350,7 +359,7 @@ impl TreeHandler {
|
|||
|
||||
let index = self
|
||||
.get_index_by_fractional_index(
|
||||
parent,
|
||||
&parent,
|
||||
&NodePosition {
|
||||
position: position.clone(),
|
||||
idlp: self.next_idlp(),
|
||||
|
@ -366,7 +375,7 @@ impl TreeHandler {
|
|||
inner.container_idx,
|
||||
crate::op::RawOpContent::Tree(Arc::new(TreeOp::Create {
|
||||
target,
|
||||
parent,
|
||||
parent: parent.tree_id(),
|
||||
position: position.clone(),
|
||||
})),
|
||||
EventHint::Tree(smallvec![TreeDiffItem {
|
||||
|
@ -380,11 +389,13 @@ impl TreeHandler {
|
|||
&inner.state,
|
||||
)?;
|
||||
|
||||
Ok(self.children(Some(target)).unwrap_or_default())
|
||||
Ok(self
|
||||
.children(&TreeParentId::Node(target))
|
||||
.unwrap_or_default())
|
||||
})?;
|
||||
for child in children {
|
||||
let position = self.get_position_by_tree_id(&child).unwrap();
|
||||
self.create_at_with_target_for_apply_diff(Some(target), position, child)?;
|
||||
self.create_at_with_target_for_apply_diff(TreeParentId::Node(target), position, child)?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
@ -392,7 +403,7 @@ impl TreeHandler {
|
|||
/// For undo/redo, Specify the TreeID of the created node
|
||||
pub(crate) fn move_at_with_target_for_apply_diff(
|
||||
&self,
|
||||
parent: Option<TreeID>,
|
||||
parent: TreeParentId,
|
||||
position: FractionalIndex,
|
||||
target: TreeID,
|
||||
) -> LoroResult<bool> {
|
||||
|
@ -413,14 +424,14 @@ impl TreeHandler {
|
|||
|
||||
let index = self
|
||||
.get_index_by_fractional_index(
|
||||
parent,
|
||||
&parent,
|
||||
&NodePosition {
|
||||
position: position.clone(),
|
||||
idlp: self.next_idlp(),
|
||||
},
|
||||
)
|
||||
.unwrap_or(0);
|
||||
let with_event = !parent.is_some_and(|p| !self.contains(p));
|
||||
let with_event = !parent.tree_id().is_some_and(|p| !self.contains(p));
|
||||
|
||||
if !with_event {
|
||||
return Ok(false);
|
||||
|
@ -437,7 +448,7 @@ impl TreeHandler {
|
|||
inner.container_idx,
|
||||
crate::op::RawOpContent::Tree(Arc::new(TreeOp::Move {
|
||||
target,
|
||||
parent,
|
||||
parent: parent.tree_id(),
|
||||
position: position.clone(),
|
||||
})),
|
||||
EventHint::Tree(smallvec![TreeDiffItem {
|
||||
|
@ -446,6 +457,9 @@ impl TreeHandler {
|
|||
parent,
|
||||
index,
|
||||
position: position.clone(),
|
||||
// the old parent should be exist, so we can unwrap
|
||||
old_parent: self.get_node_parent(&target).unwrap(),
|
||||
old_index: self.get_index_by_tree_id(&target).unwrap(),
|
||||
},
|
||||
}]),
|
||||
&inner.state,
|
||||
|
@ -454,17 +468,16 @@ impl TreeHandler {
|
|||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn create_with_txn<T: Into<Option<TreeID>>>(
|
||||
pub(crate) fn create_with_txn(
|
||||
&self,
|
||||
txn: &mut Transaction,
|
||||
parent: T,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
) -> LoroResult<TreeID> {
|
||||
let inner = self.inner.try_attached_state()?;
|
||||
let parent: Option<TreeID> = parent.into();
|
||||
let target = TreeID::from_id(txn.next_id());
|
||||
|
||||
match self.generate_position_at(&target, parent, index) {
|
||||
match self.generate_position_at(&target, &parent, index) {
|
||||
FractionalIndexGenResult::Ok(position) => {
|
||||
self.create_with_position(inner, txn, target, parent, index, position)
|
||||
}
|
||||
|
@ -474,26 +487,25 @@ impl TreeHandler {
|
|||
self.create_with_position(inner, txn, id, parent, index, position)?;
|
||||
continue;
|
||||
}
|
||||
self.mov_with_position(inner, txn, id, parent, index + i, position)?;
|
||||
self.mov_with_position(inner, txn, id, parent, index + i, position, index + i)?;
|
||||
}
|
||||
Ok(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mov<T: Into<Option<TreeID>>>(&self, target: TreeID, parent: T) -> LoroResult<()> {
|
||||
let parent = parent.into();
|
||||
pub fn mov(&self, target: TreeID, parent: TreeParentId) -> LoroResult<()> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => {
|
||||
let mut index: usize = self.children_num(parent).unwrap_or(0);
|
||||
if self.is_parent(target, parent) {
|
||||
let mut index: usize = self.children_num(&parent).unwrap_or(0);
|
||||
if self.is_parent(&target, &parent) {
|
||||
index -= 1;
|
||||
}
|
||||
self.move_to(target, parent, index)
|
||||
}
|
||||
MaybeDetached::Attached(a) => {
|
||||
let mut index = self.children_num(parent).unwrap_or(0);
|
||||
if self.is_parent(target, parent) {
|
||||
let mut index = self.children_num(&parent).unwrap_or(0);
|
||||
if self.is_parent(&target, &parent) {
|
||||
index -= 1;
|
||||
}
|
||||
a.with_txn(|txn| self.mov_with_txn(txn, target, parent, index))
|
||||
|
@ -502,11 +514,11 @@ impl TreeHandler {
|
|||
}
|
||||
|
||||
pub fn mov_after(&self, target: TreeID, other: TreeID) -> LoroResult<()> {
|
||||
let parent: Option<TreeID> = self
|
||||
let parent = self
|
||||
.get_node_parent(&other)
|
||||
.ok_or(LoroTreeError::TreeNodeNotExist(other))?;
|
||||
let mut index = self.get_index_by_tree_id(&other).unwrap() + 1;
|
||||
if self.is_parent(target, parent) && self.get_index_by_tree_id(&target).unwrap() < index {
|
||||
if self.is_parent(&target, &parent) && self.get_index_by_tree_id(&target).unwrap() < index {
|
||||
index -= 1;
|
||||
}
|
||||
self.move_to(target, parent, index)
|
||||
|
@ -517,7 +529,7 @@ impl TreeHandler {
|
|||
.get_node_parent(&other)
|
||||
.ok_or(LoroTreeError::TreeNodeNotExist(other))?;
|
||||
let mut index = self.get_index_by_tree_id(&other).unwrap();
|
||||
if self.is_parent(target, parent)
|
||||
if self.is_parent(&target, &parent)
|
||||
&& index > 1
|
||||
&& self.get_index_by_tree_id(&target).unwrap() < index
|
||||
{
|
||||
|
@ -526,16 +538,11 @@ impl TreeHandler {
|
|||
self.move_to(target, parent, index)
|
||||
}
|
||||
|
||||
pub fn move_to<T: Into<Option<TreeID>>>(
|
||||
&self,
|
||||
target: TreeID,
|
||||
parent: T,
|
||||
index: usize,
|
||||
) -> LoroResult<()> {
|
||||
pub fn move_to(&self, target: TreeID, parent: TreeParentId, index: usize) -> LoroResult<()> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(t) => {
|
||||
let mut t = t.try_lock().unwrap();
|
||||
t.value.mov(target, parent.into(), index)
|
||||
t.value.mov(target, parent.tree_id(), index)
|
||||
}
|
||||
MaybeDetached::Attached(a) => {
|
||||
a.with_txn(|txn| self.mov_with_txn(txn, target, parent, index))
|
||||
|
@ -543,19 +550,18 @@ impl TreeHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mov_with_txn<T: Into<Option<TreeID>>>(
|
||||
pub(crate) fn mov_with_txn(
|
||||
&self,
|
||||
txn: &mut Transaction,
|
||||
target: TreeID,
|
||||
parent: T,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
) -> LoroResult<()> {
|
||||
let parent = parent.into();
|
||||
let inner = self.inner.try_attached_state()?;
|
||||
let mut children_len = self.children_num(parent).unwrap_or(0);
|
||||
let mut children_len = self.children_num(&parent).unwrap_or(0);
|
||||
let mut already_in_parent = false;
|
||||
// check the input is valid
|
||||
if self.is_parent(target, parent) {
|
||||
if self.is_parent(&target, &parent) {
|
||||
// If the position after moving is same as the current position , do nothing
|
||||
if let Some(current_index) = self.get_index_by_tree_id(&target) {
|
||||
if current_index == index {
|
||||
|
@ -574,17 +580,18 @@ impl TreeHandler {
|
|||
}
|
||||
.into());
|
||||
}
|
||||
let old_index = self.get_index_by_tree_id(&target).unwrap();
|
||||
if already_in_parent {
|
||||
self.delete_position(parent, target);
|
||||
self.delete_position(&parent, &target);
|
||||
}
|
||||
|
||||
match self.generate_position_at(&target, parent, index) {
|
||||
match self.generate_position_at(&target, &parent, index) {
|
||||
FractionalIndexGenResult::Ok(position) => {
|
||||
self.mov_with_position(inner, txn, target, parent, index, position)
|
||||
self.mov_with_position(inner, txn, target, parent, index, position, old_index)
|
||||
}
|
||||
FractionalIndexGenResult::Rearrange(ids) => {
|
||||
for (i, (id, position)) in ids.into_iter().enumerate() {
|
||||
self.mov_with_position(inner, txn, id, parent, index + i, position)?;
|
||||
self.mov_with_position(inner, txn, id, parent, index + i, position, old_index)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -597,7 +604,7 @@ impl TreeHandler {
|
|||
inner: &BasicHandler,
|
||||
txn: &mut Transaction,
|
||||
tree_id: TreeID,
|
||||
parent: Option<TreeID>,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
position: FractionalIndex,
|
||||
) -> LoroResult<TreeID> {
|
||||
|
@ -605,7 +612,7 @@ impl TreeHandler {
|
|||
inner.container_idx,
|
||||
crate::op::RawOpContent::Tree(Arc::new(TreeOp::Create {
|
||||
target: tree_id,
|
||||
parent,
|
||||
parent: parent.tree_id(),
|
||||
position: position.clone(),
|
||||
})),
|
||||
EventHint::Tree(smallvec![TreeDiffItem {
|
||||
|
@ -627,15 +634,16 @@ impl TreeHandler {
|
|||
inner: &BasicHandler,
|
||||
txn: &mut Transaction,
|
||||
target: TreeID,
|
||||
parent: Option<TreeID>,
|
||||
parent: TreeParentId,
|
||||
index: usize,
|
||||
position: FractionalIndex,
|
||||
old_index: usize,
|
||||
) -> LoroResult<()> {
|
||||
txn.apply_local_op(
|
||||
inner.container_idx,
|
||||
crate::op::RawOpContent::Tree(Arc::new(TreeOp::Move {
|
||||
target,
|
||||
parent,
|
||||
parent: parent.tree_id(),
|
||||
position: position.clone(),
|
||||
})),
|
||||
EventHint::Tree(smallvec![TreeDiffItem {
|
||||
|
@ -644,6 +652,8 @@ impl TreeHandler {
|
|||
parent,
|
||||
index,
|
||||
position,
|
||||
old_parent: self.get_node_parent(&target).unwrap(),
|
||||
old_index,
|
||||
},
|
||||
}]),
|
||||
&inner.state,
|
||||
|
@ -672,46 +682,42 @@ impl TreeHandler {
|
|||
}
|
||||
|
||||
/// Get the parent of the node, if the node is deleted or does not exist, return None
|
||||
pub fn get_node_parent(&self, target: &TreeID) -> Option<Option<TreeID>> {
|
||||
pub fn get_node_parent(&self, target: &TreeID) -> Option<TreeParentId> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(t) => {
|
||||
let t = t.try_lock().unwrap();
|
||||
t.value.get_parent(target)
|
||||
t.value.get_parent(target).map(TreeParentId::from)
|
||||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state().unwrap();
|
||||
match a.parent(target) {
|
||||
TreeParentId::Root => Some(None),
|
||||
TreeParentId::Node(parent) => Some(Some(parent)),
|
||||
TreeParentId::Deleted | TreeParentId::Unexist => None,
|
||||
}
|
||||
a.parent(target)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: iterator
|
||||
pub fn children(&self, parent: Option<TreeID>) -> Option<Vec<TreeID>> {
|
||||
pub fn children(&self, parent: &TreeParentId) -> Option<Vec<TreeID>> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(t) => {
|
||||
let t = t.try_lock().unwrap();
|
||||
t.value.get_children(parent)
|
||||
t.value.get_children(parent.tree_id())
|
||||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state().unwrap();
|
||||
a.children(&TreeParentId::from(parent))
|
||||
a.children(parent)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn children_num(&self, parent: Option<TreeID>) -> Option<usize> {
|
||||
pub fn children_num(&self, parent: &TreeParentId) -> Option<usize> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(t) => {
|
||||
let t = t.try_lock().unwrap();
|
||||
t.value.children_num(parent)
|
||||
t.value.children_num(parent.tree_id())
|
||||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state().unwrap();
|
||||
a.children_num(&TreeParentId::from(parent))
|
||||
a.children_num(parent)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -742,15 +748,15 @@ impl TreeHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_parent(&self, target: TreeID, parent: Option<TreeID>) -> bool {
|
||||
pub fn is_parent(&self, target: &TreeID, parent: &TreeParentId) -> bool {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(t) => {
|
||||
let t = t.try_lock().unwrap();
|
||||
t.value.is_parent(target, parent)
|
||||
t.value.is_parent(target, &parent.tree_id())
|
||||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state().unwrap();
|
||||
a.is_parent(&TreeParentId::from(parent), &target)
|
||||
a.is_parent(parent, target)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -769,7 +775,7 @@ impl TreeHandler {
|
|||
}
|
||||
|
||||
pub fn roots(&self) -> Vec<TreeID> {
|
||||
self.children(None).unwrap_or_default()
|
||||
self.children(&TreeParentId::Root).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -788,7 +794,7 @@ impl TreeHandler {
|
|||
fn generate_position_at(
|
||||
&self,
|
||||
target: &TreeID,
|
||||
parent: Option<TreeID>,
|
||||
parent: &TreeParentId,
|
||||
index: usize,
|
||||
) -> FractionalIndexGenResult {
|
||||
let MaybeDetached::Attached(a) = &self.inner else {
|
||||
|
@ -796,7 +802,7 @@ impl TreeHandler {
|
|||
};
|
||||
a.with_state(|state| {
|
||||
let a = state.as_tree_state_mut().unwrap();
|
||||
a.generate_position_at(target, &TreeParentId::from(parent), index)
|
||||
a.generate_position_at(target, parent, index)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -826,20 +832,20 @@ impl TreeHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn delete_position(&self, parent: Option<TreeID>, target: TreeID) {
|
||||
fn delete_position(&self, parent: &TreeParentId, target: &TreeID) {
|
||||
let MaybeDetached::Attached(a) = &self.inner else {
|
||||
unreachable!()
|
||||
};
|
||||
a.with_state(|state| {
|
||||
let a = state.as_tree_state_mut().unwrap();
|
||||
a.delete_position(&TreeParentId::from(parent), target)
|
||||
a.delete_position(parent, &target)
|
||||
})
|
||||
}
|
||||
|
||||
// use for apply diff
|
||||
pub(crate) fn get_index_by_fractional_index(
|
||||
&self,
|
||||
parent: Option<TreeID>,
|
||||
parent: &TreeParentId,
|
||||
node_position: &NodePosition,
|
||||
) -> Option<usize> {
|
||||
match &self.inner {
|
||||
|
@ -848,7 +854,7 @@ impl TreeHandler {
|
|||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state().unwrap();
|
||||
a.get_index_by_position(&TreeParentId::from(parent), node_position)
|
||||
a.get_index_by_position(parent, node_position)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -861,4 +867,51 @@ impl TreeHandler {
|
|||
MaybeDetached::Attached(a) => a.with_txn(|txn| Ok(txn.next_idlp())).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_fractional_index_enabled(&self) -> bool {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state().unwrap();
|
||||
a.is_fractional_index_enabled()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether to generate fractional index for Tree Position. The LoroDoc is set to disable fractional index by default.
|
||||
///
|
||||
/// The jitter is used to avoid conflicts when multiple users are creating the node at the same position.
|
||||
/// value 0 is default, which means no jitter, any value larger than 0 will enable jitter.
|
||||
///
|
||||
/// Generally speaking, jitter will affect the growth rate of document size.
|
||||
/// [Read more about it](https://www.loro.dev/blog/movable-tree#implementation-and-encoding-size)
|
||||
pub fn set_enable_fractional_index(&self, jitter: u8) {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state_mut().unwrap();
|
||||
a.enable_generate_fractional_index(jitter);
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Disable the fractional index generation for Tree Position when
|
||||
/// you don't need the Tree's siblings to be sorted. The fractional index will be always default.
|
||||
///
|
||||
/// The LoroDoc is set to disable fractional index by default.
|
||||
pub fn set_disable_fractional_index(&self) {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state_mut().unwrap();
|
||||
a.disable_generate_fractional_index();
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -254,7 +254,7 @@ impl ContainerHistoryCache {
|
|||
id: node.last_move_op,
|
||||
op: Arc::new(TreeOp::Create {
|
||||
target: node.id,
|
||||
parent: node.parent,
|
||||
parent: node.parent.tree_id(),
|
||||
position: node.position.clone(),
|
||||
}),
|
||||
effected: true,
|
||||
|
|
|
@ -21,6 +21,7 @@ pub use loro::LoroDoc;
|
|||
pub use loro_common;
|
||||
pub use oplog::OpLog;
|
||||
pub use state::DocState;
|
||||
pub use state::TreeParentId;
|
||||
pub use undo::UndoManager;
|
||||
pub use utils::subscription::Subscription;
|
||||
pub mod awareness;
|
||||
|
|
|
@ -178,16 +178,6 @@ impl LoroDoc {
|
|||
self.config.set_merge_interval(interval);
|
||||
}
|
||||
|
||||
/// Set the jitter of the tree position(Fractional Index).
|
||||
///
|
||||
/// The jitter is used to avoid conflicts when multiple users are creating the node at the same position.
|
||||
/// value 0 is default, which means no jitter, any value larger than 0 will enable jitter.
|
||||
/// Generally speaking, jitter will affect the growth rate of document size.
|
||||
#[inline]
|
||||
pub fn set_fractional_index_jitter(&self, jitter: u8) {
|
||||
self.config.set_fractional_index_jitter(jitter);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn config_text_style(&self, text_style: StyleConfigMap) {
|
||||
*self.config.text_style_config.try_write().unwrap() = text_style;
|
||||
|
|
|
@ -1475,8 +1475,8 @@ impl ChangesBlockBytes {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
oplog::convert_change_to_remote, ListHandler, LoroDoc, MovableListHandler, TextHandler,
|
||||
TreeHandler,
|
||||
oplog::convert_change_to_remote, state::TreeParentId, ListHandler, LoroDoc,
|
||||
MovableListHandler, TextHandler, TreeHandler,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
@ -1554,10 +1554,10 @@ mod test {
|
|||
let tree = map
|
||||
.insert_container("tree", TreeHandler::new_detached())
|
||||
.unwrap();
|
||||
let node_id = tree.create(None)?;
|
||||
let node_id = tree.create(TreeParentId::Root)?;
|
||||
tree.get_meta(node_id)?.insert("key", "value")?;
|
||||
let node_b = tree.create(None)?;
|
||||
tree.move_to(node_b, None, 0).unwrap();
|
||||
let node_b = tree.create(TreeParentId::Root)?;
|
||||
tree.move_to(node_b, TreeParentId::Root, 0).unwrap();
|
||||
|
||||
let movable_list = map
|
||||
.insert_container("movable", MovableListHandler::new_detached())
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
collections::BTreeMap,
|
||||
io::Write,
|
||||
sync::{
|
||||
atomic::{AtomicU64, AtomicU8, Ordering},
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc, Mutex, RwLock, Weak,
|
||||
},
|
||||
};
|
||||
|
@ -15,7 +14,7 @@ use fxhash::{FxHashMap, FxHashSet};
|
|||
use itertools::Itertools;
|
||||
use loro_common::{ContainerID, LoroError, LoroResult};
|
||||
use loro_delta::DeltaItem;
|
||||
use tracing::{info_span, instrument, trace};
|
||||
use tracing::{info_span, instrument};
|
||||
|
||||
use crate::{
|
||||
configure::{Configure, DefaultRandom, SecureRandomGenerator},
|
||||
|
@ -50,9 +49,8 @@ pub(crate) use container_store::GcStore;
|
|||
pub(crate) use list_state::ListState;
|
||||
pub(crate) use map_state::MapState;
|
||||
pub(crate) use richtext_state::RichtextState;
|
||||
pub(crate) use tree_state::{
|
||||
get_meta_value, FractionalIndexGenResult, NodePosition, TreeParentId, TreeState,
|
||||
};
|
||||
pub use tree_state::TreeParentId;
|
||||
pub(crate) use tree_state::{get_meta_value, FractionalIndexGenResult, NodePosition, TreeState};
|
||||
|
||||
use self::{container_store::ContainerWrapper, unknown_state::UnknownState};
|
||||
|
||||
|
@ -311,8 +309,8 @@ impl State {
|
|||
Self::RichtextState(Box::new(RichtextState::new(idx, config)))
|
||||
}
|
||||
|
||||
pub fn new_tree(idx: ContainerIdx, peer: PeerID, jitter: Arc<AtomicU8>) -> Self {
|
||||
Self::TreeState(Box::new(TreeState::new(idx, peer, jitter)))
|
||||
pub fn new_tree(idx: ContainerIdx, peer: PeerID) -> Self {
|
||||
Self::TreeState(Box::new(TreeState::new(idx, peer)))
|
||||
}
|
||||
|
||||
pub fn new_unknown(idx: ContainerIdx) -> Self {
|
||||
|
@ -494,7 +492,6 @@ impl DocState {
|
|||
panic!("apply_diff should not be called in a transaction");
|
||||
}
|
||||
|
||||
trace!("ApplyDiff {:?}", &diff.new_version);
|
||||
let is_recording = self.is_recording();
|
||||
self.pre_txn(diff.origin.clone(), diff.by);
|
||||
let Cow::Owned(mut diffs) = std::mem::take(&mut diff.diff) else {
|
||||
|
@ -1487,11 +1484,7 @@ fn create_state_(idx: ContainerIdx, config: &Configure, peer: u64) -> State {
|
|||
idx,
|
||||
config.text_style_config.clone(),
|
||||
))),
|
||||
ContainerType::Tree => State::TreeState(Box::new(TreeState::new(
|
||||
idx,
|
||||
peer,
|
||||
config.tree_position_jitter.clone(),
|
||||
))),
|
||||
ContainerType::Tree => State::TreeState(Box::new(TreeState::new(idx, peer))),
|
||||
ContainerType::MovableList => State::MovableListState(Box::new(MovableListState::new(idx))),
|
||||
#[cfg(feature = "counter")]
|
||||
ContainerType::Counter => {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#[cfg(feature = "counter")]
|
||||
use super::counter_state::CounterState;
|
||||
use super::{ContainerCreationContext, MovableListState, State, TreeState};
|
||||
use super::{ContainerCreationContext, State};
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
configure::Configure,
|
||||
|
@ -504,7 +502,7 @@ mod encode {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{ListHandler, LoroDoc, MapHandler, MovableListHandler};
|
||||
use crate::{state::TreeParentId, ListHandler, LoroDoc, MapHandler, MovableListHandler};
|
||||
|
||||
fn decode_container_store(bytes: Bytes) -> ContainerStore {
|
||||
let mut new_store = ContainerStore::new(
|
||||
|
@ -529,8 +527,8 @@ mod test {
|
|||
list.push("item1").unwrap();
|
||||
|
||||
let tree = doc.get_tree("tree");
|
||||
let root = tree.create(None).unwrap();
|
||||
tree.create_at(Some(root), 0).unwrap();
|
||||
let root = tree.create(TreeParentId::Root).unwrap();
|
||||
tree.create_at(TreeParentId::Node(root), 0).unwrap();
|
||||
|
||||
let movable_list = doc.get_movable_list("movable_list");
|
||||
movable_list.insert(0, "movable_item").unwrap();
|
||||
|
|
|
@ -18,8 +18,7 @@ use crate::{
|
|||
handler::ValueOrHandler,
|
||||
op::{ListSlice, Op, RawOp},
|
||||
state::movable_list_state::inner::PushElemInfo,
|
||||
txn::Transaction,
|
||||
ApplyDiff, DocState, ListDiff,
|
||||
txn::Transaction, DocState, ListDiff,
|
||||
};
|
||||
|
||||
use self::{
|
||||
|
|
|
@ -13,7 +13,6 @@ use serde::Serialize;
|
|||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use super::{ContainerState, DiffApplyContext};
|
||||
|
@ -33,6 +32,16 @@ use crate::{
|
|||
op::RawOp,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default, EnumAsInner)]
|
||||
pub enum TreeFractionalIndexConfigInner {
|
||||
GenerateFractionalIndex {
|
||||
jitter: u8,
|
||||
rng: Box<rand::rngs::StdRng>,
|
||||
},
|
||||
#[default]
|
||||
AlwaysDefault,
|
||||
}
|
||||
|
||||
/// The state of movable tree.
|
||||
///
|
||||
/// using flat representation
|
||||
|
@ -41,8 +50,8 @@ pub struct TreeState {
|
|||
idx: ContainerIdx,
|
||||
trees: FxHashMap<TreeID, TreeStateNode>,
|
||||
children: TreeChildrenCache,
|
||||
rng: Option<rand::rngs::StdRng>,
|
||||
jitter: u8,
|
||||
fractional_index_config: TreeFractionalIndexConfigInner,
|
||||
peer_id: PeerID,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -86,8 +95,28 @@ impl From<Option<TreeID>> for TreeParentId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TreeID> for TreeParentId {
|
||||
fn from(id: TreeID) -> Self {
|
||||
if TreeID::is_deleted_root(&id) {
|
||||
TreeParentId::Deleted
|
||||
} else {
|
||||
TreeParentId::Node(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TreeID> for TreeParentId {
|
||||
fn from(id: &TreeID) -> Self {
|
||||
if TreeID::is_deleted_root(id) {
|
||||
TreeParentId::Deleted
|
||||
} else {
|
||||
TreeParentId::Node(*id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeParentId {
|
||||
fn id(&self) -> Option<TreeID> {
|
||||
pub fn tree_id(&self) -> Option<TreeID> {
|
||||
match self {
|
||||
TreeParentId::Node(id) => Some(*id),
|
||||
TreeParentId::Root => None,
|
||||
|
@ -597,16 +626,13 @@ impl NodePosition {
|
|||
}
|
||||
|
||||
impl TreeState {
|
||||
pub fn new(idx: ContainerIdx, peer_id: PeerID, config: Arc<AtomicU8>) -> Self {
|
||||
let jitter = config.load(Ordering::Relaxed);
|
||||
let use_jitter = jitter != 1;
|
||||
|
||||
pub fn new(idx: ContainerIdx, peer_id: PeerID) -> Self {
|
||||
Self {
|
||||
idx,
|
||||
trees: FxHashMap::default(),
|
||||
children: Default::default(),
|
||||
rng: use_jitter.then_some(rand::rngs::StdRng::seed_from_u64(peer_id)),
|
||||
jitter,
|
||||
fractional_index_config: TreeFractionalIndexConfigInner::default(),
|
||||
peer_id,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -630,7 +656,7 @@ impl TreeState {
|
|||
}
|
||||
if let Some(old_parent) = self.trees.get(&target).map(|x| x.parent) {
|
||||
// remove old position
|
||||
self.delete_position(&old_parent, target);
|
||||
self.delete_position(&old_parent, &target);
|
||||
}
|
||||
|
||||
let entry = self.children.entry(parent).or_default();
|
||||
|
@ -677,16 +703,9 @@ impl TreeState {
|
|||
!self.is_node_deleted(&target)
|
||||
}
|
||||
|
||||
pub fn contains_internal(&self, target: &TreeID) -> bool {
|
||||
self.trees.contains_key(target)
|
||||
}
|
||||
|
||||
/// Get the parent of the node, if the node is deleted or does not exist, return None
|
||||
pub fn parent(&self, target: &TreeID) -> TreeParentId {
|
||||
self.trees
|
||||
.get(target)
|
||||
.map(|x| x.parent)
|
||||
.unwrap_or(TreeParentId::Unexist)
|
||||
pub fn parent(&self, target: &TreeID) -> Option<TreeParentId> {
|
||||
self.trees.get(target).map(|x| x.parent)
|
||||
}
|
||||
|
||||
/// If the node exists and is not deleted, return false.
|
||||
|
@ -716,7 +735,11 @@ impl TreeState {
|
|||
let children = self.children.get(&root);
|
||||
let mut q = children
|
||||
.map(|x| {
|
||||
VecDeque::from_iter(x.iter().enumerate().zip(std::iter::repeat(None::<TreeID>)))
|
||||
VecDeque::from_iter(
|
||||
x.iter()
|
||||
.enumerate()
|
||||
.zip(std::iter::repeat(TreeParentId::Root)),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
|
@ -734,7 +757,7 @@ impl TreeState {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (position, this_target))| {
|
||||
((index, (position, this_target)), Some(target))
|
||||
((index, (position, this_target)), TreeParentId::Node(target))
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -755,7 +778,7 @@ impl TreeState {
|
|||
for (index, (position, target)) in children.iter().enumerate() {
|
||||
ans.push(TreeNode {
|
||||
id: *target,
|
||||
parent: root.id(),
|
||||
parent: root,
|
||||
position: position.position.clone(),
|
||||
index,
|
||||
last_move_op: self.trees.get(target).map(|x| x.last_move_op).unwrap(),
|
||||
|
@ -813,9 +836,9 @@ impl TreeState {
|
|||
}
|
||||
|
||||
/// Delete the position cache of the node
|
||||
pub(crate) fn delete_position(&mut self, parent: &TreeParentId, target: TreeID) {
|
||||
pub(crate) fn delete_position(&mut self, parent: &TreeParentId, target: &TreeID) {
|
||||
if let Some(x) = self.children.get_mut(parent) {
|
||||
x.delete_child(&target);
|
||||
x.delete_child(target);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -825,25 +848,57 @@ impl TreeState {
|
|||
parent: &TreeParentId,
|
||||
index: usize,
|
||||
) -> FractionalIndexGenResult {
|
||||
if let Some(rng) = self.rng.as_mut() {
|
||||
self.children
|
||||
.entry(*parent)
|
||||
.or_default()
|
||||
.generate_fi_at_jitter(index, target, rng, self.jitter)
|
||||
} else {
|
||||
self.children
|
||||
.entry(*parent)
|
||||
.or_default()
|
||||
.generate_fi_at(index, target)
|
||||
match &mut self.fractional_index_config {
|
||||
TreeFractionalIndexConfigInner::GenerateFractionalIndex { jitter, rng } => {
|
||||
if *jitter == 0 {
|
||||
self.children
|
||||
.entry(*parent)
|
||||
.or_default()
|
||||
.generate_fi_at(index, target)
|
||||
} else {
|
||||
self.children
|
||||
.entry(*parent)
|
||||
.or_default()
|
||||
.generate_fi_at_jitter(index, target, rng.as_mut(), *jitter)
|
||||
}
|
||||
}
|
||||
TreeFractionalIndexConfigInner::AlwaysDefault => {
|
||||
FractionalIndexGenResult::Ok(FractionalIndex::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_fractional_index_enabled(&self) -> bool {
|
||||
!matches!(
|
||||
self.fractional_index_config,
|
||||
TreeFractionalIndexConfigInner::AlwaysDefault
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn enable_generate_fractional_index(&mut self, jitter: u8) {
|
||||
if let TreeFractionalIndexConfigInner::GenerateFractionalIndex {
|
||||
jitter: old_jitter, ..
|
||||
} = &mut self.fractional_index_config
|
||||
{
|
||||
*old_jitter = jitter;
|
||||
return;
|
||||
}
|
||||
self.fractional_index_config = TreeFractionalIndexConfigInner::GenerateFractionalIndex {
|
||||
jitter,
|
||||
rng: Box::new(rand::rngs::StdRng::seed_from_u64(self.peer_id)),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn disable_generate_fractional_index(&mut self) {
|
||||
self.fractional_index_config = TreeFractionalIndexConfigInner::AlwaysDefault;
|
||||
}
|
||||
|
||||
pub(crate) fn get_position(&self, target: &TreeID) -> Option<FractionalIndex> {
|
||||
self.trees.get(target).and_then(|x| x.position.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn get_index_by_tree_id(&self, target: &TreeID) -> Option<usize> {
|
||||
let parent = self.parent(target);
|
||||
let parent = self.parent(target)?;
|
||||
(!parent.is_deleted())
|
||||
.then(|| {
|
||||
self.children
|
||||
|
@ -916,13 +971,15 @@ impl ContainerState for TreeState {
|
|||
ans.push(TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Create {
|
||||
parent: parent.into_node().ok(),
|
||||
parent: *parent,
|
||||
index,
|
||||
position: position.clone(),
|
||||
},
|
||||
});
|
||||
}
|
||||
TreeInternalDiff::Move { parent, position } => {
|
||||
let old_parent = self.trees.get(&target).unwrap().parent;
|
||||
let old_index = self.get_index_by_tree_id(&target).unwrap();
|
||||
if need_check {
|
||||
let was_alive = !self.is_node_deleted(&target);
|
||||
if self
|
||||
|
@ -934,32 +991,35 @@ impl ContainerState for TreeState {
|
|||
// delete event
|
||||
ans.push(TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Delete,
|
||||
action: TreeExternalDiff::Delete {
|
||||
old_parent,
|
||||
old_index,
|
||||
},
|
||||
});
|
||||
}
|
||||
// Otherwise, it's a normal move inside deleted nodes, no event is needed
|
||||
} else if was_alive {
|
||||
// normal move
|
||||
ans.push(TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Move {
|
||||
parent: *parent,
|
||||
index: self.get_index_by_tree_id(&target).unwrap(),
|
||||
position: position.clone(),
|
||||
old_parent,
|
||||
old_index,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if was_alive {
|
||||
// normal move
|
||||
ans.push(TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Move {
|
||||
parent: parent.into_node().ok(),
|
||||
index: self.get_index_by_tree_id(&target).unwrap(),
|
||||
position: position.clone(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// create event
|
||||
ans.push(TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Create {
|
||||
parent: parent.into_node().ok(),
|
||||
index: self.get_index_by_tree_id(&target).unwrap(),
|
||||
position: position.clone(),
|
||||
},
|
||||
});
|
||||
}
|
||||
// create event
|
||||
ans.push(TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Create {
|
||||
parent: *parent,
|
||||
index: self.get_index_by_tree_id(&target).unwrap(),
|
||||
position: position.clone(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -970,9 +1030,11 @@ impl ContainerState for TreeState {
|
|||
ans.push(TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Move {
|
||||
parent: parent.into_node().ok(),
|
||||
parent: *parent,
|
||||
index,
|
||||
position: position.clone(),
|
||||
old_parent,
|
||||
old_index,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -982,15 +1044,17 @@ impl ContainerState for TreeState {
|
|||
if need_check && self.is_node_deleted(&target) {
|
||||
send_event = false;
|
||||
}
|
||||
|
||||
self.mov(target, *parent, last_move_op, position.clone(), false)
|
||||
.unwrap();
|
||||
if send_event {
|
||||
ans.push(TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Delete,
|
||||
action: TreeExternalDiff::Delete {
|
||||
old_parent: self.trees.get(&target).unwrap().parent,
|
||||
old_index: self.get_index_by_tree_id(&target).unwrap(),
|
||||
},
|
||||
});
|
||||
}
|
||||
self.mov(target, *parent, last_move_op, position.clone(), false)
|
||||
.unwrap();
|
||||
}
|
||||
TreeInternalDiff::MoveInDelete { parent, position } => {
|
||||
self.mov(target, *parent, last_move_op, position.clone(), false)
|
||||
|
@ -1001,7 +1065,10 @@ impl ContainerState for TreeState {
|
|||
if !self.is_node_deleted(&target) {
|
||||
ans.push(TreeDiffItem {
|
||||
target,
|
||||
action: TreeExternalDiff::Delete,
|
||||
action: TreeExternalDiff::Delete {
|
||||
old_parent: self.trees.get(&target).unwrap().parent,
|
||||
old_index: self.get_index_by_tree_id(&target).unwrap(),
|
||||
},
|
||||
});
|
||||
}
|
||||
// delete it from state
|
||||
|
@ -1037,7 +1104,9 @@ impl ContainerState for TreeState {
|
|||
// create associated metadata container
|
||||
match &diff.action {
|
||||
TreeInternalDiff::Create { parent, position }
|
||||
| TreeInternalDiff::Move { parent, position } => {
|
||||
| TreeInternalDiff::Move {
|
||||
parent, position, ..
|
||||
} => {
|
||||
if need_check {
|
||||
self.mov(target, *parent, last_move_op, Some(position.clone()), true)
|
||||
.unwrap_or_default();
|
||||
|
@ -1126,7 +1195,7 @@ impl ContainerState for TreeState {
|
|||
let diff = TreeDiffItem {
|
||||
target: *node,
|
||||
action: TreeExternalDiff::Create {
|
||||
parent: node_parent.into_node().ok(),
|
||||
parent: node_parent,
|
||||
index,
|
||||
position: position.position.clone(),
|
||||
},
|
||||
|
@ -1237,7 +1306,7 @@ pub(crate) fn get_meta_value(nodes: &mut Vec<LoroValue>, state: &mut DocState) {
|
|||
|
||||
pub(crate) struct TreeNode {
|
||||
pub(crate) id: TreeID,
|
||||
pub(crate) parent: Option<TreeID>,
|
||||
pub(crate) parent: TreeParentId,
|
||||
pub(crate) position: FractionalIndex,
|
||||
pub(crate) index: usize,
|
||||
pub(crate) last_move_op: IdFull,
|
||||
|
@ -1249,6 +1318,7 @@ impl TreeNode {
|
|||
t.insert("id".to_string(), self.id.to_string().into());
|
||||
let p = self
|
||||
.parent
|
||||
.tree_id()
|
||||
.map(|p| p.to_string().into())
|
||||
.unwrap_or(LoroValue::Null);
|
||||
t.insert("parent".to_string(), p);
|
||||
|
@ -1428,16 +1498,12 @@ mod snapshot {
|
|||
let n = state.trees.get(&node.id).unwrap();
|
||||
let last_set_id = n.last_move_op;
|
||||
nodes.push(EncodedTreeNode {
|
||||
parent_idx_plus_two: node
|
||||
.parent
|
||||
.map(|p| {
|
||||
if p.is_deleted_root() {
|
||||
1
|
||||
} else {
|
||||
id_to_idx.get(&p).unwrap() + 2
|
||||
}
|
||||
})
|
||||
.unwrap_or(0),
|
||||
parent_idx_plus_two: match node.parent {
|
||||
TreeParentId::Deleted => 1,
|
||||
TreeParentId::Root => 0,
|
||||
TreeParentId::Node(id) => id_to_idx.get(&id).unwrap() + 2,
|
||||
TreeParentId::Unexist => unreachable!(),
|
||||
},
|
||||
last_set_peer_idx: peers.register(&last_set_id.peer),
|
||||
last_set_counter: last_set_id.counter,
|
||||
last_set_lamport: last_set_id.lamport,
|
||||
|
@ -1493,8 +1559,7 @@ mod snapshot {
|
|||
peers.push(PeerID::from_le_bytes(buf));
|
||||
}
|
||||
|
||||
let mut tree =
|
||||
TreeState::new(idx, ctx.peer, ctx.configure.tree_position_jitter.clone());
|
||||
let mut tree = TreeState::new(idx, ctx.peer);
|
||||
let encoded: EncodedTree = serde_columnar::from_bytes(bytes)?;
|
||||
let fractional_indexes = PositionArena::decode(&encoded.fractional_indexes).unwrap();
|
||||
let fractional_indexes = fractional_indexes.parse_to_positions();
|
||||
|
@ -1549,10 +1614,10 @@ mod snapshot {
|
|||
doc.set_peer_id(0).unwrap();
|
||||
doc.start_auto_commit();
|
||||
let tree = doc.get_tree("tree");
|
||||
let a = tree.create(None).unwrap();
|
||||
let b = tree.create(None).unwrap();
|
||||
let _c = tree.create(None).unwrap();
|
||||
tree.mov(b, a).unwrap();
|
||||
let a = tree.create(TreeParentId::Root).unwrap();
|
||||
let b = tree.create(TreeParentId::Root).unwrap();
|
||||
let _c = tree.create(TreeParentId::Root).unwrap();
|
||||
tree.mov(b, TreeParentId::Node(a)).unwrap();
|
||||
let (bytes, value) = {
|
||||
let mut doc_state = doc.app_state().lock().unwrap();
|
||||
let tree_state = doc_state.get_tree("tree").unwrap();
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
use loro_kv_store::{compress::CompressionType, mem_store::MemKvConfig, MemKvStore};
|
||||
use loro_kv_store::{mem_store::MemKvConfig, MemKvStore};
|
||||
|
||||
use crate::kv_store::KvStore;
|
||||
|
||||
|
|
|
@ -558,8 +558,12 @@ pub mod wasm {
|
|||
position,
|
||||
} => {
|
||||
js_sys::Reflect::set(&obj, &"action".into(), &"create".into()).unwrap();
|
||||
js_sys::Reflect::set(&obj, &"parent".into(), &JsValue::from(*parent))
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&"parent".into(),
|
||||
&JsValue::from(parent.tree_id()),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(&obj, &"index".into(), &(*index).into()).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
|
@ -568,18 +572,34 @@ pub mod wasm {
|
|||
)
|
||||
.unwrap();
|
||||
}
|
||||
TreeExternalDiff::Delete { .. } => {
|
||||
TreeExternalDiff::Delete {
|
||||
old_parent,
|
||||
old_index,
|
||||
} => {
|
||||
js_sys::Reflect::set(&obj, &"action".into(), &"delete".into()).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&"old_parent".into(),
|
||||
&JsValue::from(old_parent.tree_id()),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(&obj, &"old_index".into(), &(*old_index).into())
|
||||
.unwrap();
|
||||
}
|
||||
TreeExternalDiff::Move {
|
||||
parent,
|
||||
index,
|
||||
position,
|
||||
..
|
||||
old_parent,
|
||||
old_index,
|
||||
} => {
|
||||
js_sys::Reflect::set(&obj, &"action".into(), &"move".into()).unwrap();
|
||||
js_sys::Reflect::set(&obj, &"parent".into(), &JsValue::from(*parent))
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&"parent".into(),
|
||||
&JsValue::from(parent.tree_id()),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(&obj, &"index".into(), &(*index).into()).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
|
@ -587,6 +607,14 @@ pub mod wasm {
|
|||
&position.to_string().into(),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&"old_parent".into(),
|
||||
&JsValue::from(old_parent.tree_id()),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(&obj, &"old_index".into(), &(*old_index).into())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
array.push(&obj);
|
||||
|
|
|
@ -8,6 +8,7 @@ use loro_internal::{
|
|||
handler::{Handler, TextDelta, ValueOrHandler},
|
||||
version::Frontiers,
|
||||
ApplyDiff, HandlerTrait, ListHandler, LoroDoc, MapHandler, TextHandler, ToJson, TreeHandler,
|
||||
TreeParentId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
|
@ -724,11 +725,11 @@ fn tree_checkout() {
|
|||
doc_a.subscribe_root(Arc::new(|_e| {}));
|
||||
doc_a.set_peer_id(1).unwrap();
|
||||
let tree = doc_a.get_tree("root");
|
||||
let id1 = tree.create(None).unwrap();
|
||||
let id2 = tree.create(id1).unwrap();
|
||||
let id1 = tree.create(TreeParentId::Root).unwrap();
|
||||
let id2 = tree.create(TreeParentId::Node(id1)).unwrap();
|
||||
let v1_state = tree.get_deep_value();
|
||||
let v1 = doc_a.oplog_frontiers();
|
||||
let _id3 = tree.create(id2).unwrap();
|
||||
let _id3 = tree.create(TreeParentId::Node(id2)).unwrap();
|
||||
let v2_state = tree.get_deep_value();
|
||||
let v2 = doc_a.oplog_frontiers();
|
||||
tree.delete(id2).unwrap();
|
||||
|
@ -757,7 +758,7 @@ fn tree_checkout() {
|
|||
);
|
||||
|
||||
doc_a.attach();
|
||||
tree.create(None).unwrap();
|
||||
tree.create(TreeParentId::Root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -853,8 +854,8 @@ fn missing_event_when_checkout() {
|
|||
|
||||
let doc2 = LoroDoc::new_auto_commit();
|
||||
let tree = doc2.get_tree("tree");
|
||||
let node = tree.create_at(None, 0).unwrap();
|
||||
let _ = tree.create_at(None, 0).unwrap();
|
||||
let node = tree.create_at(TreeParentId::Root, 0).unwrap();
|
||||
let _ = tree.create_at(TreeParentId::Root, 0).unwrap();
|
||||
let meta = tree.get_meta(node).unwrap();
|
||||
meta.insert("a", 0).unwrap();
|
||||
doc.import(&doc2.export_from(&doc.oplog_vv())).unwrap();
|
||||
|
@ -930,7 +931,7 @@ fn insert_attach_container() -> LoroResult<()> {
|
|||
#[test]
|
||||
fn tree_attach() {
|
||||
let tree = TreeHandler::new_detached();
|
||||
let id = tree.create(None).unwrap();
|
||||
let id = tree.create(TreeParentId::Root).unwrap();
|
||||
tree.get_meta(id).unwrap().insert("key", "value").unwrap();
|
||||
let doc = LoroDoc::new_auto_commit();
|
||||
doc.get_list("list").insert_container(0, tree).unwrap();
|
||||
|
|
25
crates/loro-internal/tests/tree.rs
Normal file
25
crates/loro-internal/tests/tree.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use loro_internal::{LoroDoc, TreeParentId};
|
||||
|
||||
#[test]
|
||||
fn tree_index() {
|
||||
let doc = LoroDoc::new_auto_commit();
|
||||
doc.set_peer_id(0).unwrap();
|
||||
let tree = doc.get_tree("tree");
|
||||
let root = tree.create(TreeParentId::Root).unwrap();
|
||||
let child = tree.create(root.into()).unwrap();
|
||||
let child2 = tree.create_at(root.into(), 0).unwrap();
|
||||
// sort with OpID
|
||||
assert_eq!(tree.get_index_by_tree_id(&child).unwrap(), 0);
|
||||
assert_eq!(tree.get_index_by_tree_id(&child2).unwrap(), 1);
|
||||
|
||||
let doc = LoroDoc::new_auto_commit();
|
||||
doc.set_peer_id(0).unwrap();
|
||||
let tree = doc.get_tree("tree");
|
||||
tree.set_enable_fractional_index(0);
|
||||
let root = tree.create(TreeParentId::Root).unwrap();
|
||||
let child = tree.create(root.into()).unwrap();
|
||||
let child2 = tree.create_at(root.into(), 0).unwrap();
|
||||
// sort with fractional index
|
||||
assert_eq!(tree.get_index_by_tree_id(&child).unwrap(), 1);
|
||||
assert_eq!(tree.get_index_by_tree_id(&child2).unwrap(), 0);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -13,12 +13,14 @@ use loro_internal::encoding::ImportBlobMetadata;
|
|||
use loro_internal::handler::HandlerTrait;
|
||||
use loro_internal::handler::ValueOrHandler;
|
||||
use loro_internal::json::JsonChange;
|
||||
use loro_internal::loro_common::LoroTreeError;
|
||||
use loro_internal::obs::LocalUpdateCallback;
|
||||
use loro_internal::undo::{OnPop, OnPush};
|
||||
use loro_internal::version::ImVersionVector;
|
||||
use loro_internal::DocState;
|
||||
use loro_internal::LoroDoc as InnerLoroDoc;
|
||||
use loro_internal::OpLog;
|
||||
use loro_internal::TreeParentId;
|
||||
use loro_internal::{
|
||||
handler::Handler as InnerHandler, ListHandler as InnerListHandler,
|
||||
MapHandler as InnerMapHandler, MovableListHandler as InnerMovableListHandler,
|
||||
|
@ -160,16 +162,6 @@ impl LoroDoc {
|
|||
self.doc.set_change_merge_interval(interval);
|
||||
}
|
||||
|
||||
/// Set the jitter of the tree position(Fractional Index).
|
||||
///
|
||||
/// The jitter is used to avoid conflicts when multiple users are creating the node at the same position.
|
||||
/// value 0 is default, which means no jitter, any value larger than 0 will enable jitter.
|
||||
/// Generally speaking, jitter will affect the growth rate of document size.
|
||||
#[inline]
|
||||
pub fn set_fractional_index_jitter(&self, jitter: u8) {
|
||||
self.doc.set_fractional_index_jitter(jitter);
|
||||
}
|
||||
|
||||
/// Set the rich text format configuration of the document.
|
||||
///
|
||||
/// You need to config it if you use rich text `mark` method.
|
||||
|
@ -1465,10 +1457,8 @@ impl LoroTree {
|
|||
/// // create a new child
|
||||
/// let child = tree.create(root).unwrap();
|
||||
/// ```
|
||||
pub fn create<T: Into<Option<TreeID>>>(&self, parent: T) -> LoroResult<TreeID> {
|
||||
let parent = parent.into();
|
||||
let index = self.children_num(parent).unwrap_or(0);
|
||||
self.handler.create_at(parent, index)
|
||||
pub fn create<T: Into<TreeParentId>>(&self, parent: T) -> LoroResult<TreeID> {
|
||||
self.handler.create(parent.into())
|
||||
}
|
||||
|
||||
/// Get the root nodes of the forest.
|
||||
|
@ -1488,17 +1478,18 @@ impl LoroTree {
|
|||
///
|
||||
/// let doc = LoroDoc::new();
|
||||
/// let tree = doc.get_tree("tree");
|
||||
/// // enable generate fractional index
|
||||
/// tree.set_enable_fractional_index(0);
|
||||
/// // create a root
|
||||
/// let root = tree.create(None).unwrap();
|
||||
/// // create a new child at index 0
|
||||
/// let child = tree.create_at(root, 0).unwrap();
|
||||
/// ```
|
||||
pub fn create_at<T: Into<Option<TreeID>>>(
|
||||
&self,
|
||||
parent: T,
|
||||
index: usize,
|
||||
) -> LoroResult<TreeID> {
|
||||
self.handler.create_at(parent, index)
|
||||
pub fn create_at<T: Into<TreeParentId>>(&self, parent: T, index: usize) -> LoroResult<TreeID> {
|
||||
if !self.handler.is_fractional_index_enabled() {
|
||||
return Err(LoroTreeError::FractionalIndexNotEnabled.into());
|
||||
}
|
||||
self.handler.create_at(parent.into(), index)
|
||||
}
|
||||
|
||||
/// Move the `target` node to be a child of the `parent` node.
|
||||
|
@ -1517,10 +1508,8 @@ impl LoroTree {
|
|||
/// // move `root2` to be a child of `root`.
|
||||
/// tree.mov(root2, root).unwrap();
|
||||
/// ```
|
||||
pub fn mov<T: Into<Option<TreeID>>>(&self, target: TreeID, parent: T) -> LoroResult<()> {
|
||||
let parent = parent.into();
|
||||
let index = self.children_num(parent).unwrap_or(0);
|
||||
self.handler.move_to(target, parent, index)
|
||||
pub fn mov<T: Into<TreeParentId>>(&self, target: TreeID, parent: T) -> LoroResult<()> {
|
||||
self.handler.mov(target, parent.into())
|
||||
}
|
||||
|
||||
/// Move the `target` node to be a child of the `parent` node at the given index.
|
||||
|
@ -1533,19 +1522,23 @@ impl LoroTree {
|
|||
///
|
||||
/// let doc = LoroDoc::new();
|
||||
/// let tree = doc.get_tree("tree");
|
||||
/// // enable generate fractional index
|
||||
/// tree.set_enable_fractional_index(0);
|
||||
/// let root = tree.create(None).unwrap();
|
||||
/// let root2 = tree.create(None).unwrap();
|
||||
/// // move `root2` to be a child of `root` at index 0.
|
||||
/// tree.mov_to(root2, root, 0).unwrap();
|
||||
/// ```
|
||||
pub fn mov_to<T: Into<Option<TreeID>>>(
|
||||
pub fn mov_to<T: Into<TreeParentId>>(
|
||||
&self,
|
||||
target: TreeID,
|
||||
parent: T,
|
||||
to: usize,
|
||||
) -> LoroResult<()> {
|
||||
let parent = parent.into();
|
||||
self.handler.move_to(target, parent, to)
|
||||
if !self.handler.is_fractional_index_enabled() {
|
||||
return Err(LoroTreeError::FractionalIndexNotEnabled.into());
|
||||
}
|
||||
self.handler.move_to(target, parent.into(), to)
|
||||
}
|
||||
|
||||
/// Move the `target` node to be a child after the `after` node with the same parent.
|
||||
|
@ -1557,12 +1550,17 @@ impl LoroTree {
|
|||
///
|
||||
/// let doc = LoroDoc::new();
|
||||
/// let tree = doc.get_tree("tree");
|
||||
/// // enable generate fractional index
|
||||
/// tree.set_enable_fractional_index(0);
|
||||
/// let root = tree.create(None).unwrap();
|
||||
/// let root2 = tree.create(None).unwrap();
|
||||
/// // move `root` to be a child after `root2`.
|
||||
/// tree.mov_after(root, root2).unwrap();
|
||||
/// ```
|
||||
pub fn mov_after(&self, target: TreeID, after: TreeID) -> LoroResult<()> {
|
||||
if !self.handler.is_fractional_index_enabled() {
|
||||
return Err(LoroTreeError::FractionalIndexNotEnabled.into());
|
||||
}
|
||||
self.handler.mov_after(target, after)
|
||||
}
|
||||
|
||||
|
@ -1575,12 +1573,17 @@ impl LoroTree {
|
|||
///
|
||||
/// let doc = LoroDoc::new();
|
||||
/// let tree = doc.get_tree("tree");
|
||||
/// // enable generate fractional index
|
||||
/// tree.set_enable_fractional_index(0);
|
||||
/// let root = tree.create(None).unwrap();
|
||||
/// let root2 = tree.create(None).unwrap();
|
||||
/// // move `root` to be a child before `root2`.
|
||||
/// tree.mov_before(root, root2).unwrap();
|
||||
/// ```
|
||||
pub fn mov_before(&self, target: TreeID, before: TreeID) -> LoroResult<()> {
|
||||
if !self.handler.is_fractional_index_enabled() {
|
||||
return Err(LoroTreeError::FractionalIndexNotEnabled.into());
|
||||
}
|
||||
self.handler.mov_before(target, before)
|
||||
}
|
||||
|
||||
|
@ -1625,7 +1628,7 @@ impl LoroTree {
|
|||
///
|
||||
/// - If the target node does not exist, return `None`.
|
||||
/// - If the target node is a root node, return `Some(None)`.
|
||||
pub fn parent(&self, target: TreeID) -> Option<Option<TreeID>> {
|
||||
pub fn parent(&self, target: TreeID) -> Option<TreeParentId> {
|
||||
self.handler.get_node_parent(&target)
|
||||
}
|
||||
|
||||
|
@ -1642,13 +1645,14 @@ impl LoroTree {
|
|||
/// Return all children of the target node.
|
||||
///
|
||||
/// If the parent node does not exist, return `None`.
|
||||
pub fn children(&self, parent: Option<TreeID>) -> Option<Vec<TreeID>> {
|
||||
self.handler.children(parent)
|
||||
pub fn children<T: Into<TreeParentId>>(&self, parent: T) -> Option<Vec<TreeID>> {
|
||||
self.handler.children(&parent.into())
|
||||
}
|
||||
|
||||
/// Return the number of children of the target node.
|
||||
pub fn children_num(&self, parent: Option<TreeID>) -> Option<usize> {
|
||||
self.handler.children_num(parent)
|
||||
pub fn children_num<T: Into<TreeParentId>>(&self, parent: T) -> Option<usize> {
|
||||
let parent: TreeParentId = parent.into();
|
||||
self.handler.children_num(&parent)
|
||||
}
|
||||
|
||||
/// Return container id of the tree.
|
||||
|
@ -1682,6 +1686,30 @@ impl LoroTree {
|
|||
pub fn __internal__next_tree_id(&self) -> TreeID {
|
||||
self.handler.__internal__next_tree_id()
|
||||
}
|
||||
|
||||
/// Whether the fractional index is enabled.
|
||||
pub fn is_fractional_index_enabled(&self) -> bool {
|
||||
self.handler.is_fractional_index_enabled()
|
||||
}
|
||||
|
||||
/// Set whether to generate fractional index for Tree Position.
|
||||
///
|
||||
/// The jitter is used to avoid conflicts when multiple users are creating the node at the same position.
|
||||
/// value 0 is default, which means no jitter, any value larger than 0 will enable jitter.
|
||||
///
|
||||
/// Generally speaking, jitter will affect the growth rate of document size.
|
||||
/// [Read more about it](https://www.loro.dev/blog/movable-tree#implementation-and-encoding-size)
|
||||
#[inline]
|
||||
pub fn set_enable_fractional_index(&self, jitter: u8) {
|
||||
self.handler.set_enable_fractional_index(jitter);
|
||||
}
|
||||
|
||||
/// Disable the fractional index generation for Tree Position when
|
||||
/// you don't need the Tree's siblings to be sorted. The fractional index will be always default.
|
||||
#[inline]
|
||||
pub fn set_disable_fractional_index(&self) {
|
||||
self.handler.set_disable_fractional_index();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoroTree {
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
Container,
|
||||
ContainerID,
|
||||
Delta,
|
||||
Loro,
|
||||
LoroDoc,
|
||||
LoroList,
|
||||
LoroMap,
|
||||
LoroText,
|
||||
|
@ -13,6 +13,11 @@ import {
|
|||
TreeID,
|
||||
Value,
|
||||
} from "loro-wasm";
|
||||
|
||||
/**
|
||||
* @deprecated Please use LoroDoc
|
||||
*/
|
||||
export class Loro extends LoroDoc {}
|
||||
export { Awareness } from "./awareness";
|
||||
|
||||
export type Frontiers = OpId[];
|
||||
|
@ -88,15 +93,17 @@ export type TreeDiffItem =
|
|||
action: "create";
|
||||
parent: TreeID | undefined;
|
||||
index: number;
|
||||
position: string;
|
||||
fractional_index: string;
|
||||
}
|
||||
| { target: TreeID; action: "delete" }
|
||||
| { target: TreeID; action: "delete"; old_parent: TreeID | undefined; old_index: number }
|
||||
| {
|
||||
target: TreeID;
|
||||
action: "move";
|
||||
parent: TreeID | undefined;
|
||||
index: number;
|
||||
position: string;
|
||||
fractional_index: string;
|
||||
old_parent: TreeID | undefined;
|
||||
old_index: number;
|
||||
};
|
||||
|
||||
export type TreeDiff = {
|
||||
|
@ -121,14 +128,14 @@ export function isContainerId(s: string): s is ContainerID {
|
|||
return s.startsWith("cid:");
|
||||
}
|
||||
|
||||
export { Loro };
|
||||
export { LoroDoc };
|
||||
|
||||
/** Whether the value is a container.
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* ```ts
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const map = doc.getMap("map");
|
||||
* const list = doc.getList("list");
|
||||
* const text = doc.getText("text");
|
||||
|
@ -157,7 +164,7 @@ export function isContainer(value: any): value is Container {
|
|||
* # Example
|
||||
*
|
||||
* ```ts
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const map = doc.getMap("map");
|
||||
* const list = doc.getList("list");
|
||||
* const text = doc.getText("text");
|
||||
|
@ -189,7 +196,7 @@ export function getType<T>(
|
|||
}
|
||||
|
||||
declare module "loro-wasm" {
|
||||
interface Loro {
|
||||
interface LoroDoc {
|
||||
subscribe(listener: Listener): number;
|
||||
}
|
||||
|
||||
|
@ -210,7 +217,7 @@ declare module "loro-wasm" {
|
|||
setOnPop(listener?: UndoConfig["onPop"]): void;
|
||||
}
|
||||
|
||||
interface Loro<
|
||||
interface LoroDoc<
|
||||
T extends Record<string, Container> = Record<string, Container>,
|
||||
> {
|
||||
/**
|
||||
|
@ -221,9 +228,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const map = doc.getMap("map");
|
||||
* ```
|
||||
*/
|
||||
|
@ -238,9 +245,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getList("list");
|
||||
* ```
|
||||
*/
|
||||
|
@ -255,9 +262,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getList("list");
|
||||
* ```
|
||||
*/
|
||||
|
@ -272,9 +279,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const tree = doc.getTree("tree");
|
||||
* ```
|
||||
*/
|
||||
|
@ -292,9 +299,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getList("list");
|
||||
* list.insert(0, 100);
|
||||
* list.insert(1, "foo");
|
||||
|
@ -309,9 +316,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro, LoroText } from "loro-crdt";
|
||||
* import { LoroDoc, LoroText } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getList("list");
|
||||
* list.insert(0, 100);
|
||||
* const text = list.insertContainer(1, new LoroText());
|
||||
|
@ -328,9 +335,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getList("list");
|
||||
* list.insert(0, 100);
|
||||
* console.log(list.get(0)); // 100
|
||||
|
@ -343,9 +350,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getList("list");
|
||||
* list.insert(0, 100);
|
||||
* list.insert(1, "foo");
|
||||
|
@ -368,9 +375,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro, LoroText } from "loro-crdt";
|
||||
* import { LoroDoc, LoroText } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getMovableList("list");
|
||||
* list.insert(0, 100);
|
||||
* list.insert(1, "foo");
|
||||
|
@ -385,9 +392,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getMovableList("list");
|
||||
* list.insert(0, 100);
|
||||
* const text = list.insertContainer(1, new LoroText());
|
||||
|
@ -404,10 +411,10 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const list = doc.getMoableList("list");
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getMovableList("list");
|
||||
* list.insert(0, 100);
|
||||
* console.log(list.get(0)); // 100
|
||||
* console.log(list.get(1)); // undefined
|
||||
|
@ -419,9 +426,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getMovableList("list");
|
||||
* list.insert(0, 100);
|
||||
* list.insert(1, "foo");
|
||||
|
@ -447,9 +454,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getList("list");
|
||||
* list.insert(0, 100);
|
||||
* list.insert(1, "foo");
|
||||
|
@ -464,9 +471,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const list = doc.getMovableList("list");
|
||||
* list.insert(0, 100);
|
||||
* const text = list.setContainer(0, new LoroText());
|
||||
|
@ -492,9 +499,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const map = doc.getMap("map");
|
||||
* map.set("foo", "bar");
|
||||
* const bar = map.get("foo");
|
||||
|
@ -506,9 +513,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const map = doc.getMap("map");
|
||||
* map.set("foo", "bar");
|
||||
* const text = map.setContainer("text", new LoroText());
|
||||
|
@ -528,9 +535,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const map = doc.getMap("map");
|
||||
* map.set("foo", "bar");
|
||||
* const bar = map.get("foo");
|
||||
|
@ -544,9 +551,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const map = doc.getMap("map");
|
||||
* map.set("foo", "bar");
|
||||
* map.set("foo", "baz");
|
||||
|
@ -579,9 +586,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* const doc = new Loro();
|
||||
* const doc = new LoroDoc();
|
||||
* const tree = doc.getTree("tree");
|
||||
* const root = tree.createNode();
|
||||
* const node = tree.createNode(undefined, 0);
|
||||
|
@ -617,9 +624,9 @@ declare module "loro-wasm" {
|
|||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Loro } from "loro-crdt";
|
||||
* import { LoroDoc } from "loro-crdt";
|
||||
*
|
||||
* let doc = new Loro();
|
||||
* let doc = new LoroDoc();
|
||||
* let tree = doc.getTree("tree");
|
||||
* let root = tree.createNode();
|
||||
* let node = root.createNode();
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
Container,
|
||||
getType,
|
||||
isContainer,
|
||||
Loro,
|
||||
LoroDoc,
|
||||
LoroList,
|
||||
LoroMap,
|
||||
LoroText,
|
||||
|
@ -11,7 +11,7 @@ import {
|
|||
} from "../src";
|
||||
|
||||
it("basic example", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getList("list");
|
||||
list.insert(0, "A");
|
||||
list.insert(1, "B");
|
||||
|
@ -53,7 +53,7 @@ it("basic example", () => {
|
|||
});
|
||||
|
||||
it("get or create on Map", () => {
|
||||
const docA = new Loro();
|
||||
const docA = new LoroDoc();
|
||||
const map = docA.getMap("map");
|
||||
const container = map.getOrCreateContainer("list", new LoroList());
|
||||
container.insert(0, 1);
|
||||
|
@ -66,8 +66,8 @@ it("get or create on Map", () => {
|
|||
});
|
||||
|
||||
it("basic sync example", () => {
|
||||
const docA = new Loro();
|
||||
const docB = new Loro();
|
||||
const docA = new LoroDoc();
|
||||
const docB = new LoroDoc();
|
||||
const listA = docA.getList("list");
|
||||
listA.insert(0, "A");
|
||||
listA.insert(1, "B");
|
||||
|
@ -91,14 +91,14 @@ it("basic sync example", () => {
|
|||
});
|
||||
|
||||
it("basic events", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.subscribe((event) => {});
|
||||
const list = doc.getList("list");
|
||||
});
|
||||
|
||||
describe("list", () => {
|
||||
it("insert containers", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getList("list");
|
||||
const map = list.insertContainer(0, new LoroMap());
|
||||
map.set("key", "value");
|
||||
|
@ -108,7 +108,7 @@ describe("list", () => {
|
|||
});
|
||||
|
||||
it("toArray", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getList("list");
|
||||
list.insert(0, 1);
|
||||
list.insert(1, 2);
|
||||
|
@ -123,7 +123,7 @@ describe("list", () => {
|
|||
|
||||
describe("map", () => {
|
||||
it("get child container", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
const list = map.setContainer("key", new LoroList());
|
||||
list.insert(0, 1);
|
||||
|
@ -132,7 +132,7 @@ describe("map", () => {
|
|||
});
|
||||
|
||||
it("set large int", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
map.set("key", 2147483699);
|
||||
expect(map.get("key")).toBe(2147483699);
|
||||
|
@ -141,12 +141,12 @@ describe("map", () => {
|
|||
|
||||
describe("import", () => {
|
||||
it("pending", () => {
|
||||
const a = new Loro();
|
||||
const a = new LoroDoc();
|
||||
a.getText("text").insert(0, "a");
|
||||
const b = new Loro();
|
||||
const b = new LoroDoc();
|
||||
b.import(a.exportFrom());
|
||||
b.getText("text").insert(1, "b");
|
||||
const c = new Loro();
|
||||
const c = new LoroDoc();
|
||||
c.import(b.exportFrom());
|
||||
c.getText("text").insert(2, "c");
|
||||
|
||||
|
@ -161,9 +161,9 @@ describe("import", () => {
|
|||
});
|
||||
|
||||
it("import by frontiers", () => {
|
||||
const a = new Loro();
|
||||
const a = new LoroDoc();
|
||||
a.getText("text").insert(0, "a");
|
||||
const b = new Loro();
|
||||
const b = new LoroDoc();
|
||||
b.import(a.exportFrom());
|
||||
b.getText("text").insert(1, "b");
|
||||
b.getList("list").insert(0, [1, 2]);
|
||||
|
@ -173,18 +173,18 @@ describe("import", () => {
|
|||
});
|
||||
|
||||
it("from snapshot", () => {
|
||||
const a = new Loro();
|
||||
const a = new LoroDoc();
|
||||
a.getText("text").insert(0, "hello");
|
||||
const bytes = a.exportSnapshot();
|
||||
const b = Loro.fromSnapshot(bytes);
|
||||
const b = LoroDoc.fromSnapshot(bytes);
|
||||
b.getText("text").insert(0, "123");
|
||||
expect(b.toJSON()).toStrictEqual({ text: "123hello" });
|
||||
});
|
||||
|
||||
it("importBatch Error #181", () => {
|
||||
const docA = new Loro();
|
||||
const docA = new LoroDoc();
|
||||
const updateA = docA.exportSnapshot();
|
||||
const docB = new Loro();
|
||||
const docB = new LoroDoc();
|
||||
docB.importUpdateBatch([updateA]);
|
||||
docB.getText("text").insert(0, "hello");
|
||||
docB.commit();
|
||||
|
@ -193,7 +193,7 @@ describe("import", () => {
|
|||
|
||||
describe("map", () => {
|
||||
it("keys", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
map.set("foo", "bar");
|
||||
map.set("baz", "bar");
|
||||
|
@ -202,7 +202,7 @@ describe("map", () => {
|
|||
});
|
||||
|
||||
it("values", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
map.set("foo", "bar");
|
||||
map.set("baz", "bar");
|
||||
|
@ -211,7 +211,7 @@ describe("map", () => {
|
|||
});
|
||||
|
||||
it("entries", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
map.set("foo", "bar");
|
||||
map.set("baz", "bar");
|
||||
|
@ -225,7 +225,7 @@ describe("map", () => {
|
|||
});
|
||||
|
||||
it("entries should return container handlers", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
map.setContainer("text", new LoroText());
|
||||
map.set("foo", "bar");
|
||||
|
@ -235,7 +235,7 @@ describe("map", () => {
|
|||
});
|
||||
|
||||
it("handlers should still be usable after doc is dropped", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
const list = doc.getList("list");
|
||||
const map = doc.getMap("map");
|
||||
|
@ -249,9 +249,9 @@ it("handlers should still be usable after doc is dropped", () => {
|
|||
});
|
||||
|
||||
it("get change with given lamport", () => {
|
||||
const doc1 = new Loro();
|
||||
const doc1 = new LoroDoc();
|
||||
doc1.setPeerId(1);
|
||||
const doc2 = new Loro();
|
||||
const doc2 = new LoroDoc();
|
||||
doc2.setPeerId(2);
|
||||
doc1.getText("text").insert(0, "01234");
|
||||
doc2.import(doc1.exportFrom());
|
||||
|
@ -304,7 +304,7 @@ it("isContainer", () => {
|
|||
expect(isContainer({})).toBeFalsy();
|
||||
expect(isContainer(undefined)).toBeFalsy();
|
||||
expect(isContainer(null)).toBeFalsy();
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const t = doc.getText("t");
|
||||
expect(isContainer(t)).toBeTruthy();
|
||||
expect(isContainer(doc.getMap("m"))).toBeTruthy();
|
||||
|
@ -318,7 +318,7 @@ it("isContainer", () => {
|
|||
|
||||
it("getValueType", () => {
|
||||
// Type tests
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const t = doc.getText("t");
|
||||
expectTypeOf(getType(t)).toEqualTypeOf<"Text">();
|
||||
expect(getType(t)).toBe("Text");
|
||||
|
@ -346,7 +346,7 @@ it("getValueType", () => {
|
|||
});
|
||||
|
||||
it("enable timestamp", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.setPeerId(1);
|
||||
doc.getText("123").insert(0, "123");
|
||||
doc.commit();
|
||||
|
@ -365,7 +365,7 @@ it("enable timestamp", () => {
|
|||
});
|
||||
|
||||
it("commit with specified timestamp", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.setPeerId(1);
|
||||
doc.getText("123").insert(0, "123");
|
||||
doc.commit(undefined, 111);
|
||||
|
@ -375,7 +375,7 @@ it("commit with specified timestamp", () => {
|
|||
|
||||
it("can control the mergeable interval", () => {
|
||||
{
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.setPeerId(1);
|
||||
doc.getText("123").insert(0, "1");
|
||||
doc.commit(undefined, 110);
|
||||
|
@ -385,7 +385,7 @@ it("can control the mergeable interval", () => {
|
|||
}
|
||||
|
||||
{
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.setPeerId(1);
|
||||
doc.setChangeMergeInterval(10);
|
||||
doc.getText("123").insert(0, "1");
|
||||
|
@ -398,7 +398,7 @@ it("can control the mergeable interval", () => {
|
|||
});
|
||||
|
||||
it("get container parent", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const m = doc.getMap("m");
|
||||
expect(m.parent()).toBeUndefined();
|
||||
const list = m.setContainer("t", new LoroList());
|
||||
|
@ -429,7 +429,7 @@ it("prelim support", () => {
|
|||
text.delete(1, 1);
|
||||
expect(list.toJSON()).toStrictEqual([{ "3": 2, "9": 9, text: "Hello" }]);
|
||||
}
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const rootMap = doc.getMap("map");
|
||||
rootMap.setContainer("test", map); // new way to create sub-container
|
||||
|
||||
|
@ -452,7 +452,7 @@ it("prelim support", () => {
|
|||
});
|
||||
|
||||
it("get elem by path", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
map.set("key", 1);
|
||||
expect(doc.getByPath("map/key")).toBe(1);
|
||||
|
@ -463,7 +463,7 @@ it("get elem by path", () => {
|
|||
});
|
||||
|
||||
it("fork", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
map.set("key", 1);
|
||||
const doc2 = doc.fork();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { Loro } from "../src";
|
||||
import { LoroDoc } from "../src";
|
||||
|
||||
describe("Checkout", () => {
|
||||
it("simple checkout", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.setPeerId(0n);
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "H");
|
||||
|
@ -31,7 +31,7 @@ describe("Checkout", () => {
|
|||
});
|
||||
|
||||
it("Chinese char", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "你好世界");
|
||||
doc.commit();
|
||||
|
@ -55,13 +55,13 @@ describe("Checkout", () => {
|
|||
});
|
||||
|
||||
it("two clients", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "0");
|
||||
doc.commit();
|
||||
|
||||
const v0 = doc.frontiers();
|
||||
const docB = new Loro();
|
||||
const docB = new LoroDoc();
|
||||
docB.import(doc.exportFrom());
|
||||
expect(docB.cmpWithFrontiers(v0)).toBe(0);
|
||||
text.insert(1, "0");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { CounterDiff, Loro } from "../src";
|
||||
import { CounterDiff, LoroDoc } from "../src";
|
||||
|
||||
function oneMs(): Promise<void> {
|
||||
return new Promise((r) => setTimeout(r));
|
||||
|
@ -7,7 +7,7 @@ function oneMs(): Promise<void> {
|
|||
|
||||
describe("counter", () => {
|
||||
it("increment", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const counter = doc.getCounter("counter");
|
||||
counter.increment(1);
|
||||
counter.increment(2);
|
||||
|
@ -16,7 +16,7 @@ describe("counter", () => {
|
|||
});
|
||||
|
||||
it("encode", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const counter = doc.getCounter("counter");
|
||||
counter.increment(1);
|
||||
counter.increment(2);
|
||||
|
@ -25,13 +25,13 @@ describe("counter", () => {
|
|||
const updates = doc.exportFrom();
|
||||
const snapshot = doc.exportSnapshot();
|
||||
const json = doc.exportJsonUpdates();
|
||||
const doc2 = new Loro();
|
||||
const doc2 = new LoroDoc();
|
||||
doc2.import(updates);
|
||||
expect(doc2.toJSON()).toStrictEqual(doc.toJSON());
|
||||
const doc3 = new Loro();
|
||||
const doc3 = new LoroDoc();
|
||||
doc3.import(snapshot);
|
||||
expect(doc3.toJSON()).toStrictEqual(doc.toJSON());
|
||||
const doc4 = new Loro();
|
||||
const doc4 = new LoroDoc();
|
||||
doc4.importJsonUpdates(json);
|
||||
expect(doc4.toJSON()).toStrictEqual(doc.toJSON());
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ describe("counter", () => {
|
|||
|
||||
describe("counter event", () => {
|
||||
it("event", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
let triggered = false;
|
||||
doc.subscribe((e) => {
|
||||
triggered = true;
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
Delta,
|
||||
getType,
|
||||
ListDiff,
|
||||
Loro,
|
||||
LoroDoc,
|
||||
LoroEventBatch,
|
||||
LoroList,
|
||||
LoroMap,
|
||||
|
@ -14,7 +14,7 @@ import {
|
|||
|
||||
describe("event", () => {
|
||||
it("target", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
let lastEvent: undefined | LoroEventBatch;
|
||||
loro.subscribe((event) => {
|
||||
expect(event.by).toBe("local");
|
||||
|
@ -29,7 +29,7 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("path", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
let lastEvent: undefined | LoroEventBatch;
|
||||
loro.subscribe((event) => {
|
||||
lastEvent = event;
|
||||
|
@ -52,7 +52,7 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("text diff", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
let lastEvent: undefined | LoroEventBatch;
|
||||
loro.subscribe((event) => {
|
||||
lastEvent = event;
|
||||
|
@ -75,7 +75,7 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("list diff", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
let lastEvent: undefined | LoroEventBatch;
|
||||
loro.subscribe((event) => {
|
||||
lastEvent = event;
|
||||
|
@ -98,7 +98,7 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("map diff", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
let lastEvent: undefined | LoroEventBatch;
|
||||
loro.subscribe((event) => {
|
||||
lastEvent = event;
|
||||
|
@ -129,7 +129,7 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("tree", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
let lastEvent: undefined | LoroEventBatch;
|
||||
loro.subscribe((event) => {
|
||||
lastEvent = event;
|
||||
|
@ -144,7 +144,7 @@ describe("event", () => {
|
|||
|
||||
describe("subscribe container events", () => {
|
||||
it("text", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const text = loro.getText("text");
|
||||
let ran = 0;
|
||||
const sub = text.subscribe((event) => {
|
||||
|
@ -179,7 +179,7 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("map subscribe deep", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const map = loro.getMap("map");
|
||||
let times = 0;
|
||||
const sub = map.subscribe((event) => {
|
||||
|
@ -208,7 +208,7 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("list subscribe deep", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const list = loro.getList("list");
|
||||
let times = 0;
|
||||
const sub = list.subscribe((event) => {
|
||||
|
@ -236,7 +236,7 @@ describe("event", () => {
|
|||
|
||||
describe("text event length should be utf16", () => {
|
||||
it("test", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const text = loro.getText("text");
|
||||
let string = "";
|
||||
text.subscribe((event) => {
|
||||
|
@ -285,7 +285,7 @@ describe("event", () => {
|
|||
|
||||
describe("handler in event", () => {
|
||||
it("test", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const list = loro.getList("list");
|
||||
let first = true;
|
||||
loro.subscribe((e) => {
|
||||
|
@ -304,7 +304,7 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("diff can contain containers", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getList("list");
|
||||
let ran = false;
|
||||
doc.subscribe((event) => {
|
||||
|
@ -330,11 +330,11 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("remote event", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getList("list");
|
||||
list.insert(0, 123);
|
||||
{
|
||||
const doc2 = new Loro();
|
||||
const doc2 = new LoroDoc();
|
||||
let triggered = false;
|
||||
doc2.subscribe((event) => {
|
||||
expect(event.by).toBe("import");
|
||||
|
@ -345,7 +345,7 @@ describe("event", () => {
|
|||
expect(triggered).toBeTruthy();
|
||||
}
|
||||
{
|
||||
const doc2 = new Loro();
|
||||
const doc2 = new LoroDoc();
|
||||
let triggered = false;
|
||||
doc2.subscribe((event) => {
|
||||
expect(event.by).toBe("import");
|
||||
|
@ -358,7 +358,7 @@ describe("event", () => {
|
|||
});
|
||||
|
||||
it("checkout event", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getList("list");
|
||||
list.insert(0, 123);
|
||||
doc.commit();
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||
import { Loro } from "../src";
|
||||
import { LoroDoc } from "../src";
|
||||
import { Container, LoroText, OpId } from "../src";
|
||||
import { setDebug } from "loro-wasm";
|
||||
|
||||
it("#211", () => {
|
||||
const loro1 = new Loro();
|
||||
const loro1 = new LoroDoc();
|
||||
loro1.setPeerId(0n);
|
||||
const text1 = loro1.getText("text");
|
||||
|
||||
const loro2 = new Loro();
|
||||
const loro2 = new LoroDoc();
|
||||
loro2.setPeerId(1n);
|
||||
const text2 = loro2.getText("text");
|
||||
|
||||
|
@ -59,7 +59,7 @@ it("#211", () => {
|
|||
show(text1, loro1, text2, loro2);
|
||||
});
|
||||
|
||||
function show(text1: LoroText, loro1: Loro, text2: LoroText, loro2: Loro) {
|
||||
function show(text1: LoroText, loro1: LoroDoc, text2: LoroText, loro2: LoroDoc) {
|
||||
// console.log(` #0 has content: ${JSON.stringify(text1.toString())}`);
|
||||
// console.log(` #0 has frontiers: ${showFrontiers(loro1.frontiers())}`);
|
||||
// console.log(` #1 has content: ${JSON.stringify(text2.toString())}`);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { expect, it } from "vitest";
|
||||
import {
|
||||
Loro,
|
||||
LoroDoc,
|
||||
LoroMap,
|
||||
TextOp,
|
||||
} from "../src";
|
||||
|
||||
it("json encoding", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "123");
|
||||
const map = doc.getMap("map");
|
||||
|
@ -26,7 +26,7 @@ it("json encoding", () => {
|
|||
text.mark({ start: 0, end: 3 }, "bold", true);
|
||||
const json = doc.exportJsonUpdates();
|
||||
// console.log(json.changes[0].ops);
|
||||
const doc2 = new Loro();
|
||||
const doc2 = new LoroDoc();
|
||||
doc2.importJsonUpdates(json);
|
||||
});
|
||||
|
||||
|
@ -134,13 +134,13 @@ it("json decoding", () => {
|
|||
}
|
||||
]
|
||||
}`;
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.importJsonUpdates(v15Json);
|
||||
// console.log(doc.exportJsonUpdates());
|
||||
});
|
||||
|
||||
it("test some type correctness", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.setPeerId(0);
|
||||
doc.getText("text").insert(0, "123");
|
||||
doc.commit();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { Loro, LoroList, LoroMap, LoroText, VersionVector } from "../src";
|
||||
import { LoroDoc, LoroList, LoroMap, LoroText, VersionVector } from "../src";
|
||||
import { expectTypeOf } from "vitest";
|
||||
|
||||
function assertEquals(a: any, b: any) {
|
||||
|
@ -8,7 +8,7 @@ function assertEquals(a: any, b: any) {
|
|||
|
||||
describe("transaction", () => {
|
||||
it("transaction", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const text = loro.getText("text");
|
||||
let count = 0;
|
||||
const sub = loro.subscribe(() => {
|
||||
|
@ -26,7 +26,7 @@ describe("transaction", () => {
|
|||
});
|
||||
|
||||
it("transaction origin", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const text = loro.getText("text");
|
||||
let count = 0;
|
||||
const sub = loro.subscribe((event: { origin: string }) => {
|
||||
|
@ -48,7 +48,7 @@ describe("transaction", () => {
|
|||
|
||||
describe("subscribe", () => {
|
||||
it("subscribe_lock", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const text = loro.getText("text");
|
||||
const list = loro.getList("list");
|
||||
let count = 0;
|
||||
|
@ -80,7 +80,7 @@ describe("subscribe", () => {
|
|||
});
|
||||
|
||||
it("subscribe_lock2", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const text = loro.getText("text");
|
||||
let count = 0;
|
||||
const sub = loro.subscribe(() => {
|
||||
|
@ -101,7 +101,7 @@ describe("subscribe", () => {
|
|||
});
|
||||
|
||||
it("subscribe", async () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const text = loro.getText("text");
|
||||
let count = 0;
|
||||
const sub = loro.subscribe(() => {
|
||||
|
@ -125,8 +125,8 @@ describe("subscribe", () => {
|
|||
|
||||
describe("sync", () => {
|
||||
it("two insert at beginning", async () => {
|
||||
const a = new Loro();
|
||||
const b = new Loro();
|
||||
const a = new LoroDoc();
|
||||
const b = new LoroDoc();
|
||||
let a_version: undefined | VersionVector = undefined;
|
||||
let b_version: undefined | VersionVector = undefined;
|
||||
a.subscribe((e) => {
|
||||
|
@ -153,11 +153,11 @@ describe("sync", () => {
|
|||
});
|
||||
|
||||
it("sync", () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const text = loro.getText("text");
|
||||
text.insert(0, "hello world");
|
||||
|
||||
const loro_bk = new Loro();
|
||||
const loro_bk = new LoroDoc();
|
||||
loro_bk.import(loro.exportFrom(undefined));
|
||||
assertEquals(loro_bk.toJSON(), loro.toJSON());
|
||||
const text_bk = loro_bk.getText("text");
|
||||
|
@ -172,7 +172,7 @@ describe("sync", () => {
|
|||
});
|
||||
|
||||
describe("wasm", () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const a = loro.getText("ha");
|
||||
a.insert(0, "hello world");
|
||||
a.delete(6, 5);
|
||||
|
@ -208,14 +208,14 @@ describe("wasm", () => {
|
|||
|
||||
describe("type", () => {
|
||||
it("test map type", () => {
|
||||
const loro = new Loro<{ map: LoroMap<{ name: "he" }> }>();
|
||||
const loro = new LoroDoc<{ map: LoroMap<{ name: "he" }> }>();
|
||||
const map = loro.getMap("map");
|
||||
const v = map.get("name");
|
||||
expectTypeOf(v).toEqualTypeOf<"he">();
|
||||
});
|
||||
|
||||
it("test recursive map type", () => {
|
||||
const loro = new Loro<{ map: LoroMap<{ map: LoroMap<{ name: "he" }> }> }>();
|
||||
const loro = new LoroDoc<{ map: LoroMap<{ map: LoroMap<{ name: "he" }> }> }>();
|
||||
const map = loro.getMap("map");
|
||||
map.setContainer("map", new LoroMap());
|
||||
|
||||
|
@ -225,7 +225,7 @@ describe("type", () => {
|
|||
});
|
||||
|
||||
it("works for list type", () => {
|
||||
const loro = new Loro<{ list: LoroList<string> }>();
|
||||
const loro = new LoroDoc<{ list: LoroList<string> }>();
|
||||
const list = loro.getList("list");
|
||||
list.insert(0, "123");
|
||||
const v0 = list.get(0);
|
||||
|
@ -233,7 +233,7 @@ describe("type", () => {
|
|||
});
|
||||
|
||||
it("test binary type", () => {
|
||||
const loro = new Loro<{ list: LoroList<Uint8Array> }>();
|
||||
const loro = new LoroDoc<{ list: LoroList<Uint8Array> }>();
|
||||
const list = loro.getList("list");
|
||||
list.insert(0, new Uint8Array(10));
|
||||
const v0 = list.get(0);
|
||||
|
@ -245,7 +245,7 @@ describe("type", () => {
|
|||
|
||||
describe("list stable position", () => {
|
||||
it("basic tests", () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const list = loro.getList("list");
|
||||
list.insert(0, "a");
|
||||
const pos0 = list.getCursor(0);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { describe, expect, expectTypeOf, it } from "vitest";
|
|||
import {
|
||||
Delta,
|
||||
ListDiff,
|
||||
Loro,
|
||||
LoroDoc,
|
||||
LoroList,
|
||||
LoroMap,
|
||||
LoroMovableList,
|
||||
|
@ -12,7 +12,7 @@ import {
|
|||
|
||||
describe("movable list", () => {
|
||||
it("should work like list", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getMovableList("list");
|
||||
expect(list.length).toBe(0);
|
||||
list.push("a");
|
||||
|
@ -24,7 +24,7 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("can be synced", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getMovableList("list");
|
||||
list.push("a");
|
||||
list.push("b");
|
||||
|
@ -32,7 +32,7 @@ describe("movable list", () => {
|
|||
expect(list.toArray()).toEqual(["a", "b", "c"]);
|
||||
list.set(2, "d");
|
||||
list.move(0, 1);
|
||||
const doc2 = new Loro();
|
||||
const doc2 = new LoroDoc();
|
||||
const list2 = doc2.getMovableList("list");
|
||||
expect(list2.length).toBe(0);
|
||||
doc2.import(doc.exportFrom());
|
||||
|
@ -43,7 +43,7 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("should support move", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getMovableList("list");
|
||||
list.push("a");
|
||||
list.push("b");
|
||||
|
@ -54,7 +54,7 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("should support set", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getMovableList("list");
|
||||
list.push("a");
|
||||
list.push("b");
|
||||
|
@ -65,7 +65,7 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it.todo("should support get cursor", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.setPeerId(1);
|
||||
const list = doc.getMovableList("list");
|
||||
list.push("a");
|
||||
|
@ -88,7 +88,7 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("inserts sub-container", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getMovableList("list");
|
||||
list.push("a");
|
||||
list.push("b");
|
||||
|
@ -105,7 +105,7 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("can be inserted into a list as an attached container", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getMovableList("list");
|
||||
list.push("a");
|
||||
list.push("b");
|
||||
|
@ -121,12 +121,12 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("length should be correct when there are concurrent move", () => {
|
||||
const docA = new Loro();
|
||||
const docA = new LoroDoc();
|
||||
const list = docA.getMovableList("list");
|
||||
list.push("a");
|
||||
list.push("b");
|
||||
list.push("c");
|
||||
const docB = new Loro();
|
||||
const docB = new LoroDoc();
|
||||
const listB = docB.getMovableList("list");
|
||||
docB.import(docA.exportFrom());
|
||||
listB.move(0, 1);
|
||||
|
@ -137,13 +137,13 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("concurrent set the one with larger peer id win", () => {
|
||||
const docA = new Loro();
|
||||
const docA = new LoroDoc();
|
||||
docA.setPeerId(0);
|
||||
const listA = docA.getMovableList("list");
|
||||
listA.push("a");
|
||||
listA.push("b");
|
||||
listA.push("c");
|
||||
const docB = new Loro();
|
||||
const docB = new LoroDoc();
|
||||
docB.setPeerId(1);
|
||||
const listB = docB.getMovableList("list");
|
||||
docB.import(docA.exportFrom());
|
||||
|
@ -158,7 +158,7 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("can be subscribe", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getMovableList("list");
|
||||
list.push("a");
|
||||
list.push("b");
|
||||
|
@ -198,7 +198,7 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("has the right type", () => {
|
||||
const doc = new Loro<
|
||||
const doc = new LoroDoc<
|
||||
{ list: LoroMovableList<LoroMap<{ name: string }>> }
|
||||
>();
|
||||
const list = doc.getMovableList("list");
|
||||
|
@ -209,7 +209,7 @@ describe("movable list", () => {
|
|||
});
|
||||
|
||||
it("set container", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getMovableList("list");
|
||||
list.insert(0, 100);
|
||||
const text = list.setContainer(0, new LoroText());
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { Delta, Loro, TextDiff } from "../src";
|
||||
import { Delta, LoroDoc, TextDiff } from "../src";
|
||||
import { Cursor, OpId, PeerID, setDebug } from "loro-wasm";
|
||||
|
||||
describe("richtext", () => {
|
||||
it("mark", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.configTextStyle({
|
||||
bold: { expand: "after" },
|
||||
link: { expand: "before" },
|
||||
|
@ -26,7 +26,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("insert after emoji", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "👨👩👦");
|
||||
text.insert(8, "a");
|
||||
|
@ -34,7 +34,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("emit event correctly", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
let triggered = false;
|
||||
text.subscribe((e) => {
|
||||
|
@ -63,7 +63,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("emit event from merging doc correctly", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
let called = false;
|
||||
text.subscribe((event) => {
|
||||
|
@ -83,7 +83,7 @@ describe("richtext", () => {
|
|||
}
|
||||
});
|
||||
|
||||
const docB = new Loro();
|
||||
const docB = new LoroDoc();
|
||||
const textB = docB.getText("text");
|
||||
textB.insert(0, "Hello World!");
|
||||
textB.mark({ start: 0, end: 5 }, "bold", true);
|
||||
|
@ -93,7 +93,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("Delete emoji", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "012345👨👩👦6789");
|
||||
doc.commit();
|
||||
|
@ -116,13 +116,13 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("apply delta", async () => {
|
||||
const doc1 = new Loro();
|
||||
const doc1 = new LoroDoc();
|
||||
doc1.configTextStyle({
|
||||
link: { expand: "none" },
|
||||
bold: { expand: "after" },
|
||||
});
|
||||
const text1 = doc1.getText("text");
|
||||
const doc2 = new Loro();
|
||||
const doc2 = new LoroDoc();
|
||||
doc2.configTextStyle({
|
||||
link: { expand: "none" },
|
||||
bold: { expand: "after" },
|
||||
|
@ -154,7 +154,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("custom richtext type", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.configTextStyle({
|
||||
myStyle: {
|
||||
expand: "none",
|
||||
|
@ -178,7 +178,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("allow overlapped styles", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.configTextStyle({
|
||||
comment: { expand: "none" },
|
||||
});
|
||||
|
@ -206,7 +206,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("Cursor example", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "123");
|
||||
const pos0 = text.getCursor(0, 0);
|
||||
|
@ -222,7 +222,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("Get and query cursor", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
doc.setPeerId("1");
|
||||
text.insert(0, "123");
|
||||
|
@ -243,7 +243,7 @@ describe("richtext", () => {
|
|||
const bytes = pos0!.encode();
|
||||
// Sending pos0 over the network
|
||||
const pos0decoded = Cursor.decode(bytes);
|
||||
const docA = new Loro();
|
||||
const docA = new LoroDoc();
|
||||
docA.import(doc.exportFrom());
|
||||
{
|
||||
const ans = docA.getCursorPos(pos0decoded!);
|
||||
|
@ -272,7 +272,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("Styles should not affect cursor pos", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "Hello");
|
||||
const pos3 = text.getCursor(3);
|
||||
|
@ -282,13 +282,13 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("Insert cursed str", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, `“aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`);
|
||||
});
|
||||
|
||||
it("Insert/delete by utf8 index", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText('t');
|
||||
text.insert(0, "你好");
|
||||
text.insertUtf8(3, "a");
|
||||
|
@ -303,28 +303,28 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("Slice", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText('t');
|
||||
text.insert(0, "你好");
|
||||
expect(text.slice(0, 1)).toStrictEqual("你");
|
||||
});
|
||||
|
||||
it("Slice emoji", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText('t');
|
||||
text.insert(0, "😡😡😡");
|
||||
expect(text.slice(0, 2)).toStrictEqual("😡");
|
||||
});
|
||||
|
||||
it("CharAt", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText('t');
|
||||
text.insert(0, "你好");
|
||||
expect(text.charAt(1)).toStrictEqual("好");
|
||||
});
|
||||
|
||||
it("Splice", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText('t');
|
||||
text.insert(0, "你好");
|
||||
expect(text.splice(1, 1, "我")).toStrictEqual("好");
|
||||
|
@ -332,7 +332,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("Text iter", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText('t');
|
||||
text.insert(0, "你好");
|
||||
let str = "";
|
||||
|
@ -344,7 +344,7 @@ describe("richtext", () => {
|
|||
});
|
||||
|
||||
it("Text update", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const text = doc.getText('t');
|
||||
text.insert(0, "Hello 😊Bro");
|
||||
text.update("Hello World Bro😊");
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { assert, describe, expect, it} from "vitest";
|
||||
import { Loro, LoroTree, LoroTreeNode } from "../src";
|
||||
import { LoroDoc, LoroTree, LoroTreeNode, TreeDiff } from "../src";
|
||||
|
||||
function assertEquals(a: any, b: any) {
|
||||
expect(a).toStrictEqual(b);
|
||||
}
|
||||
|
||||
describe("loro tree", () => {
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const tree = loro.getTree("root");
|
||||
tree.setEnableFractionalIndex(0);
|
||||
|
||||
it("create", () => {
|
||||
const root = tree.createNode();
|
||||
|
@ -76,7 +77,7 @@ describe("loro tree", () => {
|
|||
});
|
||||
|
||||
it("toArray", ()=>{
|
||||
const loro2 = new Loro();
|
||||
const loro2 = new LoroDoc();
|
||||
const tree2 = loro2.getTree("root");
|
||||
const root = tree2.createNode();
|
||||
tree2.createNode(root.id);
|
||||
|
@ -119,8 +120,9 @@ describe("loro tree", () => {
|
|||
});
|
||||
|
||||
describe("loro tree node", ()=>{
|
||||
const loro = new Loro();
|
||||
const loro = new LoroDoc();
|
||||
const tree = loro.getTree("root");
|
||||
tree.setEnableFractionalIndex(0);
|
||||
|
||||
it("create", () => {
|
||||
const root = tree.createNode();
|
||||
|
@ -180,8 +182,28 @@ describe("loro tree node", ()=>{
|
|||
assertEquals(child.index(), 1);
|
||||
assertEquals(child2.index(), 0);
|
||||
});
|
||||
|
||||
it("old parent", () => {
|
||||
const root = tree.createNode();
|
||||
const child = root.createNode();
|
||||
const child2 = root.createNode();
|
||||
loro.commit();
|
||||
const subID = tree.subscribe((e)=>{
|
||||
if(e.events[0].diff.type == "tree"){
|
||||
const diff = e.events[0].diff as TreeDiff;
|
||||
if (diff.diff[0].action == "move"){
|
||||
assertEquals(diff.diff[0].old_parent, root.id);
|
||||
assertEquals(diff.diff[0].old_index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
child2.move(child);
|
||||
loro.commit();
|
||||
tree.unsubscribe(subID);
|
||||
assertEquals(child2.parent()!.id, child.id);
|
||||
});
|
||||
});
|
||||
|
||||
function one_ms(): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, 1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
Loro,
|
||||
LoroDoc,
|
||||
LoroList,
|
||||
LoroMap,
|
||||
LoroMovableList,
|
||||
|
@ -16,7 +16,7 @@ test("Container should not match Value", () => {
|
|||
});
|
||||
|
||||
test("A non-numeric string is not a valid peer id", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
expectTypeOf(doc.peerIdStr).toMatchTypeOf<PeerID>();
|
||||
expectTypeOf("123" as const).toMatchTypeOf<PeerID>();
|
||||
expectTypeOf("a123" as const).not.toMatchTypeOf<PeerID>();
|
||||
|
@ -34,7 +34,7 @@ test("Expect container type", () => {
|
|||
});
|
||||
|
||||
test("doc type and container type", () => {
|
||||
const doc = new Loro<{
|
||||
const doc = new LoroDoc<{
|
||||
text: LoroText;
|
||||
map: LoroMap<{
|
||||
name?: string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Cursor, Loro, UndoManager } from "../src";
|
||||
import { Cursor, LoroDoc, UndoManager } from "../src";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
describe("undo", () => {
|
||||
test("basic text undo", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.setPeerId(1);
|
||||
const undo = new UndoManager(doc, { maxUndoSteps: 100, mergeInterval: 0 });
|
||||
expect(undo.canRedo()).toBeFalsy();
|
||||
|
@ -41,7 +41,7 @@ describe("undo", () => {
|
|||
});
|
||||
|
||||
test("merge", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const undo = new UndoManager(doc, { maxUndoSteps: 100, mergeInterval: 50 });
|
||||
for (let i = 0; i < 10; i++) {
|
||||
doc.getText("text").insert(i, i.toString());
|
||||
|
@ -66,7 +66,7 @@ describe("undo", () => {
|
|||
});
|
||||
|
||||
test("max undo steps", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const undo = new UndoManager(doc, { maxUndoSteps: 100, mergeInterval: 0 });
|
||||
for (let i = 0; i < 200; i++) {
|
||||
doc.getText("text").insert(0, "0");
|
||||
|
@ -80,7 +80,7 @@ describe("undo", () => {
|
|||
});
|
||||
|
||||
test("Skip chosen events", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const undo = new UndoManager(doc, {
|
||||
maxUndoSteps: 100,
|
||||
mergeInterval: 0,
|
||||
|
@ -130,7 +130,7 @@ describe("undo", () => {
|
|||
});
|
||||
|
||||
test("undo event's origin", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
let undoing = false;
|
||||
let ran = false;
|
||||
doc.subscribe((e) => {
|
||||
|
@ -151,7 +151,7 @@ describe("undo", () => {
|
|||
});
|
||||
|
||||
test("undo event listener", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
let pushReturn: null | number = null;
|
||||
let expectedValue: null | number = null;
|
||||
|
||||
|
@ -199,7 +199,7 @@ describe("undo", () => {
|
|||
});
|
||||
|
||||
test("undo cursor transform", async () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
let cursors: Cursor[] = [];
|
||||
let poppedCursors: Cursor[] = [];
|
||||
const undo = new UndoManager(doc , {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
decodeImportBlobMeta,
|
||||
Loro,
|
||||
LoroDoc,
|
||||
LoroMap,
|
||||
OpId,
|
||||
VersionVector,
|
||||
|
@ -9,14 +9,14 @@ import {
|
|||
|
||||
describe("Frontiers", () => {
|
||||
it("two clients", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
doc.setPeerId(0);
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "0");
|
||||
doc.commit();
|
||||
|
||||
const v0 = doc.frontiers();
|
||||
const docB = new Loro();
|
||||
const docB = new LoroDoc();
|
||||
docB.setPeerId(1);
|
||||
docB.import(doc.exportFrom());
|
||||
expect(docB.cmpWithFrontiers(v0)).toBe(0);
|
||||
|
@ -34,9 +34,9 @@ describe("Frontiers", () => {
|
|||
});
|
||||
|
||||
it("cmp frontiers", () => {
|
||||
const doc1 = new Loro();
|
||||
const doc1 = new LoroDoc();
|
||||
doc1.setPeerId(1);
|
||||
const doc2 = new Loro();
|
||||
const doc2 = new LoroDoc();
|
||||
doc2.setPeerId(2n);
|
||||
|
||||
doc1.getText("text").insert(0, "01234");
|
||||
|
@ -85,7 +85,7 @@ describe("Frontiers", () => {
|
|||
});
|
||||
|
||||
it("peer id repr should be consistent", () => {
|
||||
const doc = new Loro();
|
||||
const doc = new LoroDoc();
|
||||
const id = doc.peerIdStr;
|
||||
doc.getText("text").insert(0, "hello");
|
||||
doc.commit();
|
||||
|
@ -105,9 +105,9 @@ it("peer id repr should be consistent", () => {
|
|||
});
|
||||
|
||||
describe("Version", () => {
|
||||
const a = new Loro();
|
||||
const a = new LoroDoc();
|
||||
a.setPeerId(0n);
|
||||
const b = new Loro();
|
||||
const b = new LoroDoc();
|
||||
b.setPeerId(1n);
|
||||
a.getText("text").insert(0, "ha");
|
||||
b.getText("text").insert(0, "yo");
|
||||
|
@ -155,7 +155,7 @@ describe("Version", () => {
|
|||
});
|
||||
|
||||
it("get import blob metadata", () => {
|
||||
const doc0 = new Loro();
|
||||
const doc0 = new LoroDoc();
|
||||
doc0.setPeerId(0n);
|
||||
const text = doc0.getText("text");
|
||||
text.insert(0, "0");
|
||||
|
@ -172,7 +172,7 @@ it("get import blob metadata", () => {
|
|||
expect(meta.startFrontiers.length).toBe(0);
|
||||
}
|
||||
|
||||
const doc1 = new Loro();
|
||||
const doc1 = new LoroDoc();
|
||||
doc1.setPeerId(1);
|
||||
doc1.getText("text").insert(0, "123");
|
||||
doc1.import(doc0.exportFrom());
|
||||
|
|
Loading…
Reference in a new issue