yrby 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +90 -0
- data/Cargo.lock +33 -11
- data/README.md +18 -3
- data/ext/yrby/Cargo.toml +3 -1
- data/ext/yrby/crates/html-core/Cargo.toml +2 -2
- data/ext/yrby/crates/html-core/src/lib.rs +34 -3
- data/ext/yrby/crates/lexical-html/Cargo.toml +3 -3
- data/ext/yrby/crates/lexical-html/src/lib.rs +46 -16
- data/ext/yrby/crates/prosemirror-html/Cargo.toml +3 -3
- data/ext/yrby/crates/prosemirror-html/src/lib.rs +30 -5
- data/ext/yrby/src/array.rs +396 -0
- data/ext/yrby/src/lib.rs +945 -36
- data/ext/yrby/src/map.rs +374 -0
- data/ext/yrby/src/read.rs +3 -3
- data/ext/yrby/src/shared.rs +561 -0
- data/ext/yrby/src/text.rs +294 -0
- data/ext/yrby/src/xml_text.rs +749 -0
- data/lib/y/anchor.rb +34 -0
- data/lib/y/collaborative/attribute.rb +36 -0
- data/lib/y/collaborative/helper.rb +13 -10
- data/lib/y/collaborative.rb +14 -11
- data/lib/y/doc.rb +36 -0
- data/lib/y/lexical_markdown.rb +175 -0
- data/lib/y/lexical_writer.rb +142 -0
- data/lib/y/lexxy.rb +25 -0
- data/lib/y/rendering.rb +3 -3
- data/lib/y/text.rb +13 -0
- data/lib/y/version.rb +1 -1
- data/lib/y/xml_text.rb +36 -0
- data/lib/y.rb +6 -0
- metadata +12 -1
data/ext/yrby/src/map.rs
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
//! Live `Y::Map` handles over a yrs `Map`, exposing read + write of actual
|
|
2
|
+
//! shared data (not just opaque CRDT sync).
|
|
3
|
+
//!
|
|
4
|
+
//! Thread safety mirrors `Y::Doc` exactly: every operation opens its own
|
|
5
|
+
//! transaction inside `nogvl` (GVL released) and holds no lock across the GVL
|
|
6
|
+
//! boundary. Ruby values are read/built only with the GVL held (before/after the
|
|
7
|
+
//! `nogvl` block); the closure works purely on `Send` data.
|
|
8
|
+
//!
|
|
9
|
+
//! A handle keeps its map's identity and resolves it per operation (see
|
|
10
|
+
//! `shared`). Once the map is deleted, reads return empty and writes raise.
|
|
11
|
+
|
|
12
|
+
use magnus::{prelude::*, typed_data::Obj, Error, Ruby, Value};
|
|
13
|
+
use yrs::branch::BranchID;
|
|
14
|
+
use yrs::{Any, Doc, Map, MapRef, Transact};
|
|
15
|
+
|
|
16
|
+
use crate::array::RbArray;
|
|
17
|
+
use crate::nogvl;
|
|
18
|
+
use crate::read::out_to_any;
|
|
19
|
+
use crate::shared::{
|
|
20
|
+
any_to_ruby, guarded, identity, key_to_string, resolve, ruby_to_invalue, to_in, Child, ORIGIN,
|
|
21
|
+
};
|
|
22
|
+
use crate::text::RbText;
|
|
23
|
+
|
|
24
|
+
/// A live handle to a yrs `Map` inside a `Doc`. `Send + Sync` (all fields are),
|
|
25
|
+
/// so it satisfies the same thread-safety assertion as `Y::Doc`.
|
|
26
|
+
#[magnus::wrap(class = "Y::Map", free_immediately, size)]
|
|
27
|
+
pub struct RbMap {
|
|
28
|
+
doc: Doc,
|
|
29
|
+
id: BranchID,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl RbMap {
|
|
33
|
+
pub fn new(doc: Doc, id: BranchID) -> Self {
|
|
34
|
+
RbMap { doc, id }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// The shared value at `key`, resolved now: what `get_map`, `get_array`
|
|
38
|
+
/// and `get_text` narrow to a handle.
|
|
39
|
+
fn child(&self, key: Value) -> Result<Child, Error> {
|
|
40
|
+
let key = key_to_string(key)?;
|
|
41
|
+
let (doc, id) = (&self.doc, &self.id);
|
|
42
|
+
nogvl(move || {
|
|
43
|
+
let txn = doc.transact();
|
|
44
|
+
Child::of(resolve::<MapRef, _>(&txn, id).and_then(|m| m.get(&txn, &key)))
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- reads ---
|
|
49
|
+
|
|
50
|
+
/// `map[key]`: a snapshot Ruby value (primitives; nested map/array become a
|
|
51
|
+
/// deep `Hash`/`Array`). Use `get_map` for a live nested handle.
|
|
52
|
+
fn get(&self, key: Value) -> Result<Value, Error> {
|
|
53
|
+
let key = key_to_string(key)?;
|
|
54
|
+
let (doc, id) = (&self.doc, &self.id);
|
|
55
|
+
let got: Option<Any> = nogvl(move || {
|
|
56
|
+
let txn = doc.transact();
|
|
57
|
+
let m = resolve::<MapRef, _>(&txn, id)?;
|
|
58
|
+
m.get(&txn, &key).map(|v| out_to_any(&txn, &v))
|
|
59
|
+
})?;
|
|
60
|
+
let ruby = Ruby::get().unwrap();
|
|
61
|
+
Ok(match got {
|
|
62
|
+
Some(a) => any_to_ruby(&ruby, &a),
|
|
63
|
+
None => ruby.qnil().as_value(),
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// A live `Y::Map` for a nested map at `key`, or `nil` if `key` is absent or
|
|
68
|
+
/// not a map. Mutating it mutates the document.
|
|
69
|
+
fn get_map(&self, key: Value) -> Result<Option<Self>, Error> {
|
|
70
|
+
Ok(match self.child(key)? {
|
|
71
|
+
Child::Map(id) => Some(RbMap::new(self.doc.clone(), id)),
|
|
72
|
+
_ => None,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// A live `Y::Array` for an array stored at `key`.
|
|
77
|
+
fn get_array(&self, key: Value) -> Result<Option<RbArray>, Error> {
|
|
78
|
+
Ok(match self.child(key)? {
|
|
79
|
+
Child::Array(id) => Some(RbArray::new(self.doc.clone(), id)),
|
|
80
|
+
_ => None,
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// A live `Y::Text` for text stored at `key`.
|
|
85
|
+
fn get_text(&self, key: Value) -> Result<Option<RbText>, Error> {
|
|
86
|
+
Ok(match self.child(key)? {
|
|
87
|
+
Child::Text(id) => Some(RbText::new(self.doc.clone(), id)),
|
|
88
|
+
_ => None,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn has_key(&self, key: Value) -> Result<bool, Error> {
|
|
93
|
+
let key = key_to_string(key)?;
|
|
94
|
+
let (doc, id) = (&self.doc, &self.id);
|
|
95
|
+
nogvl(move || {
|
|
96
|
+
let txn = doc.transact();
|
|
97
|
+
resolve::<MapRef, _>(&txn, id)
|
|
98
|
+
.map(|m| m.contains_key(&txn, &key))
|
|
99
|
+
.unwrap_or(false)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn size(&self) -> Result<usize, Error> {
|
|
104
|
+
let (doc, id) = (&self.doc, &self.id);
|
|
105
|
+
nogvl(move || {
|
|
106
|
+
let txn = doc.transact();
|
|
107
|
+
resolve::<MapRef, _>(&txn, id)
|
|
108
|
+
.map(|m| m.len(&txn) as usize)
|
|
109
|
+
.unwrap_or(0)
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn keys(&self) -> Result<Vec<String>, Error> {
|
|
114
|
+
let (doc, id) = (&self.doc, &self.id);
|
|
115
|
+
nogvl(move || {
|
|
116
|
+
let txn = doc.transact();
|
|
117
|
+
match resolve::<MapRef, _>(&txn, id) {
|
|
118
|
+
Some(m) => m.keys(&txn).map(|k| k.to_string()).collect(),
|
|
119
|
+
None => Vec::new(),
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fn snapshot(&self) -> Result<Vec<(String, Any)>, Error> {
|
|
125
|
+
let (doc, id) = (&self.doc, &self.id);
|
|
126
|
+
nogvl(move || {
|
|
127
|
+
let txn = doc.transact();
|
|
128
|
+
match resolve::<MapRef, _>(&txn, id) {
|
|
129
|
+
Some(m) => m
|
|
130
|
+
.iter(&txn)
|
|
131
|
+
.map(|(k, v)| (k.to_string(), out_to_any(&txn, &v)))
|
|
132
|
+
.collect(),
|
|
133
|
+
None => Vec::new(),
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn to_h(&self) -> Result<Value, Error> {
|
|
139
|
+
let ruby = Ruby::get().unwrap();
|
|
140
|
+
let h = ruby.hash_new();
|
|
141
|
+
for (k, v) in &self.snapshot()? {
|
|
142
|
+
h.aset(k.as_str(), any_to_ruby(&ruby, v))?;
|
|
143
|
+
}
|
|
144
|
+
Ok(h.as_value())
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// `each { |key, value| }`: yields a snapshot of each entry (read under
|
|
148
|
+
/// `nogvl`, yielded with the GVL held so the block can call back into
|
|
149
|
+
/// Ruby), and returns the map. Without a block, an Enumerator.
|
|
150
|
+
fn each(ruby: &Ruby, rb_self: Obj<Self>) -> Result<Value, Error> {
|
|
151
|
+
if !ruby.block_given() {
|
|
152
|
+
return Ok(rb_self.enumeratorize("each", ()).as_value());
|
|
153
|
+
}
|
|
154
|
+
let block = ruby.block_proc()?;
|
|
155
|
+
for (k, v) in &rb_self.snapshot()? {
|
|
156
|
+
let _: Value = block.call((k.as_str(), any_to_ruby(ruby, v)))?;
|
|
157
|
+
}
|
|
158
|
+
Ok(rb_self.as_value())
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- writes ---
|
|
162
|
+
|
|
163
|
+
/// `map[key] = value`: store a value. Ruby `Hash` creates a live nested map;
|
|
164
|
+
/// `Array` an embedded array; primitives their `Any` counterpart. Returns the
|
|
165
|
+
/// value assigned (so `map[k] = v` yields `v`, as Ruby expects).
|
|
166
|
+
fn set(&self, key: Value, value: Value) -> Result<Value, Error> {
|
|
167
|
+
let ruby = Ruby::get().unwrap();
|
|
168
|
+
let key = key_to_string(key)?;
|
|
169
|
+
let iv = ruby_to_invalue(&ruby, value)?;
|
|
170
|
+
let (doc, id) = (&self.doc, &self.id);
|
|
171
|
+
guarded(move || {
|
|
172
|
+
let mut txn = doc.transact_mut_with(ORIGIN);
|
|
173
|
+
let m = resolve::<MapRef, _>(&txn, id).ok_or("map no longer exists")?;
|
|
174
|
+
m.insert(&mut txn, key, to_in(iv));
|
|
175
|
+
Ok(())
|
|
176
|
+
})?;
|
|
177
|
+
Ok(value)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Remove `key`, returning its previous snapshot value (or `nil`).
|
|
181
|
+
fn delete(&self, key: Value) -> Result<Value, Error> {
|
|
182
|
+
let key = key_to_string(key)?;
|
|
183
|
+
let (doc, id) = (&self.doc, &self.id);
|
|
184
|
+
let prev: Option<Any> = guarded(move || {
|
|
185
|
+
let mut txn = doc.transact_mut_with(ORIGIN);
|
|
186
|
+
let Some(m) = resolve::<MapRef, _>(&txn, id) else {
|
|
187
|
+
return Ok(None);
|
|
188
|
+
};
|
|
189
|
+
// Convert before removing (a removed shared ref would dangle).
|
|
190
|
+
let prev = m.get(&txn, &key).map(|v| out_to_any(&txn, &v));
|
|
191
|
+
m.remove(&mut txn, &key);
|
|
192
|
+
Ok(prev)
|
|
193
|
+
})?;
|
|
194
|
+
let ruby = Ruby::get().unwrap();
|
|
195
|
+
Ok(match prev {
|
|
196
|
+
Some(a) => any_to_ruby(&ruby, &a),
|
|
197
|
+
None => ruby.qnil().as_value(),
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fn clear(&self) -> Result<(), Error> {
|
|
202
|
+
let (doc, id) = (&self.doc, &self.id);
|
|
203
|
+
guarded(move || {
|
|
204
|
+
let mut txn = doc.transact_mut_with(ORIGIN);
|
|
205
|
+
if let Some(m) = resolve::<MapRef, _>(&txn, id) {
|
|
206
|
+
m.clear(&mut txn);
|
|
207
|
+
}
|
|
208
|
+
Ok(())
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// Ensure a root map exists and return a live handle to it. Called by
|
|
214
|
+
/// `Y::Doc#get_map`.
|
|
215
|
+
pub fn root_map(doc: &Doc, name: String) -> Result<RbMap, Error> {
|
|
216
|
+
let d = doc.clone();
|
|
217
|
+
let id = nogvl(move || identity(&d.get_or_insert_map(name.as_str())))?;
|
|
218
|
+
Ok(RbMap::new(doc.clone(), id))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
pub fn define(ruby: &Ruby, module: magnus::RModule) -> Result<(), Error> {
|
|
222
|
+
let class = module.define_class("Map", ruby.class_object())?;
|
|
223
|
+
class.define_method("[]", magnus::method!(RbMap::get, 1))?;
|
|
224
|
+
class.define_method("get", magnus::method!(RbMap::get, 1))?;
|
|
225
|
+
class.define_method("get_map", magnus::method!(RbMap::get_map, 1))?;
|
|
226
|
+
class.define_method("get_array", magnus::method!(RbMap::get_array, 1))?;
|
|
227
|
+
class.define_method("get_text", magnus::method!(RbMap::get_text, 1))?;
|
|
228
|
+
class.define_method("[]=", magnus::method!(RbMap::set, 2))?;
|
|
229
|
+
class.define_method("set", magnus::method!(RbMap::set, 2))?;
|
|
230
|
+
class.define_method("delete", magnus::method!(RbMap::delete, 1))?;
|
|
231
|
+
class.define_method("clear", magnus::method!(RbMap::clear, 0))?;
|
|
232
|
+
class.define_method("key?", magnus::method!(RbMap::has_key, 1))?;
|
|
233
|
+
class.define_method("has_key?", magnus::method!(RbMap::has_key, 1))?;
|
|
234
|
+
class.define_method("size", magnus::method!(RbMap::size, 0))?;
|
|
235
|
+
class.define_method("length", magnus::method!(RbMap::size, 0))?;
|
|
236
|
+
class.define_method("keys", magnus::method!(RbMap::keys, 0))?;
|
|
237
|
+
class.define_method("to_h", magnus::method!(RbMap::to_h, 0))?;
|
|
238
|
+
class.define_method("each", magnus::method!(RbMap::each, 0))?;
|
|
239
|
+
Ok(())
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#[cfg(test)]
|
|
243
|
+
mod tests {
|
|
244
|
+
//! What a handle's identity resolves to, then each method on a handle,
|
|
245
|
+
//! called as Ruby calls it.
|
|
246
|
+
use super::*;
|
|
247
|
+
use crate::shared::test_support::{doc, hash, int, rb};
|
|
248
|
+
use rb_sys_test_helpers::ruby_test;
|
|
249
|
+
use yrs::{ArrayPrelim, MapPrelim, TextPrelim, Transact};
|
|
250
|
+
|
|
251
|
+
#[test]
|
|
252
|
+
fn a_root_map_resolves() {
|
|
253
|
+
let doc = Doc::new();
|
|
254
|
+
let id = identity(&doc.get_or_insert_map("state"));
|
|
255
|
+
let txn = doc.transact();
|
|
256
|
+
assert!(resolve::<MapRef, _>(&txn, &id).is_some());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[test]
|
|
260
|
+
fn a_nested_map_resolves_until_it_is_deleted() {
|
|
261
|
+
let doc = Doc::new();
|
|
262
|
+
let root = doc.get_or_insert_map("state");
|
|
263
|
+
let mut txn = doc.transact_mut();
|
|
264
|
+
let inner = root.insert(&mut txn, "user", MapPrelim::default());
|
|
265
|
+
inner.insert(&mut txn, "name", "Ada");
|
|
266
|
+
let id = identity(&inner);
|
|
267
|
+
assert!(resolve::<MapRef, _>(&txn, &id).is_some());
|
|
268
|
+
root.remove(&mut txn, "user");
|
|
269
|
+
assert!(resolve::<MapRef, _>(&txn, &id).is_none());
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fn state() -> RbMap {
|
|
273
|
+
root_map(&doc(), "state".into()).unwrap()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
#[ruby_test]
|
|
277
|
+
fn set_returns_the_value_and_get_reads_it_back() {
|
|
278
|
+
let map = state();
|
|
279
|
+
assert_eq!(map.set(rb("n"), rb(1)).unwrap().inspect(), "1");
|
|
280
|
+
assert_eq!(map.get(rb("n")).unwrap().inspect(), "1");
|
|
281
|
+
assert_eq!(map.get(rb("missing")).unwrap().inspect(), "nil");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#[ruby_test]
|
|
285
|
+
fn a_symbol_key_is_its_string() {
|
|
286
|
+
let map = state();
|
|
287
|
+
let ruby = Ruby::get().unwrap();
|
|
288
|
+
map.set(ruby.to_symbol("n").as_value(), rb(1)).unwrap();
|
|
289
|
+
assert!(map.has_key(rb("n")).unwrap());
|
|
290
|
+
assert_eq!(map.keys().unwrap(), vec!["n".to_string()]);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
#[ruby_test]
|
|
294
|
+
fn size_keys_and_to_h_read_every_entry() {
|
|
295
|
+
let map = state();
|
|
296
|
+
map.set(rb("a"), rb(1)).unwrap();
|
|
297
|
+
map.set(rb("b"), rb("two")).unwrap();
|
|
298
|
+
assert_eq!(map.size().unwrap(), 2);
|
|
299
|
+
let mut keys = map.keys().unwrap();
|
|
300
|
+
keys.sort();
|
|
301
|
+
assert_eq!(keys, vec!["a", "b"]);
|
|
302
|
+
let mut entries = map.snapshot().unwrap();
|
|
303
|
+
entries.sort_by(|x, y| x.0.cmp(&y.0));
|
|
304
|
+
assert_eq!(
|
|
305
|
+
entries,
|
|
306
|
+
vec![("a".into(), int(1)), ("b".into(), Any::from("two"))]
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#[ruby_test]
|
|
311
|
+
fn delete_returns_the_previous_value_or_nil() {
|
|
312
|
+
let map = state();
|
|
313
|
+
map.set(rb("n"), rb(1)).unwrap();
|
|
314
|
+
assert_eq!(map.delete(rb("n")).unwrap().inspect(), "1");
|
|
315
|
+
assert_eq!(map.delete(rb("n")).unwrap().inspect(), "nil");
|
|
316
|
+
assert!(!map.has_key(rb("n")).unwrap());
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#[ruby_test]
|
|
320
|
+
fn clear_removes_every_entry() {
|
|
321
|
+
let map = state();
|
|
322
|
+
map.set(rb("a"), rb(1)).unwrap();
|
|
323
|
+
map.set(rb("b"), rb(2)).unwrap();
|
|
324
|
+
map.clear().unwrap();
|
|
325
|
+
assert_eq!(map.size().unwrap(), 0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#[ruby_test]
|
|
329
|
+
fn a_hash_value_becomes_a_live_nested_map() {
|
|
330
|
+
let map = state();
|
|
331
|
+
map.set(rb("user"), hash(&[("name", rb("Ada"))]).as_value())
|
|
332
|
+
.unwrap();
|
|
333
|
+
assert!(matches!(map.child(rb("user")).unwrap(), Child::Map(_)));
|
|
334
|
+
let user = map.get_map(rb("user")).unwrap().expect("a map at user");
|
|
335
|
+
user.set(rb("age"), rb(36)).unwrap();
|
|
336
|
+
let mut entries = user.snapshot().unwrap();
|
|
337
|
+
entries.sort_by(|x, y| x.0.cmp(&y.0));
|
|
338
|
+
assert_eq!(
|
|
339
|
+
entries,
|
|
340
|
+
vec![("age".into(), int(36)), ("name".into(), Any::from("Ada"))]
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#[ruby_test]
|
|
345
|
+
fn get_map_get_text_and_get_array_narrow_by_type() {
|
|
346
|
+
let map = state();
|
|
347
|
+
map.set(rb("n"), rb(1)).unwrap();
|
|
348
|
+
{
|
|
349
|
+
let mut txn = map.doc.transact_mut();
|
|
350
|
+
let m = resolve::<MapRef, _>(&txn, &map.id).unwrap();
|
|
351
|
+
m.insert(&mut txn, "notes", TextPrelim::new("hi"));
|
|
352
|
+
m.insert(&mut txn, "list", ArrayPrelim::default());
|
|
353
|
+
}
|
|
354
|
+
assert!(matches!(map.child(rb("missing")).unwrap(), Child::Other));
|
|
355
|
+
assert!(map.get_map(rb("n")).unwrap().is_none());
|
|
356
|
+
assert!(map.get_text(rb("notes")).unwrap().is_some());
|
|
357
|
+
assert!(map.get_text(rb("list")).unwrap().is_none());
|
|
358
|
+
assert!(map.get_array(rb("list")).unwrap().is_some());
|
|
359
|
+
assert!(map.get_array(rb("notes")).unwrap().is_none());
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#[ruby_test]
|
|
363
|
+
fn a_handle_to_a_deleted_map_reads_empty_and_refuses_writes() {
|
|
364
|
+
let map = state();
|
|
365
|
+
map.set(rb("user"), hash(&[("name", rb("Ada"))]).as_value())
|
|
366
|
+
.unwrap();
|
|
367
|
+
let user = map.get_map(rb("user")).unwrap().unwrap();
|
|
368
|
+
map.delete(rb("user")).unwrap();
|
|
369
|
+
assert_eq!(user.size().unwrap(), 0);
|
|
370
|
+
assert_eq!(user.get(rb("name")).unwrap().inspect(), "nil");
|
|
371
|
+
let err = user.set(rb("name"), rb("Grace")).unwrap_err();
|
|
372
|
+
assert!(err.to_string().contains("map no longer exists"), "{err}");
|
|
373
|
+
}
|
|
374
|
+
}
|
data/ext/yrby/src/read.rs
CHANGED
|
@@ -51,7 +51,7 @@ pub fn xml_blocks_text<T: ReadTxn>(txn: &T, fragment: &XmlFragmentRef) -> String
|
|
|
51
51
|
/// Lexical node `__type`s whose text belongs on the surrounding line rather than
|
|
52
52
|
/// a new block (e.g. a link inside a paragraph). Everything else with embedded
|
|
53
53
|
/// child `Y.XmlText`s is treated as a block and recursed.
|
|
54
|
-
fn is_inline_lexical_type(ty: &str) -> bool {
|
|
54
|
+
pub(crate) fn is_inline_lexical_type(ty: &str) -> bool {
|
|
55
55
|
matches!(
|
|
56
56
|
ty,
|
|
57
57
|
"text" | "link" | "autolink" | "linebreak" | "tab" | "hashtag" | "mark" | "overflow"
|
|
@@ -59,7 +59,7 @@ fn is_inline_lexical_type(ty: &str) -> bool {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/// A Lexical node's `__type` (stored as an XML attribute on its `Y.XmlText`).
|
|
62
|
-
fn lexical_type<T: ReadTxn>(txn: &T, t: &XmlTextRef) -> String {
|
|
62
|
+
pub(crate) fn lexical_type<T: ReadTxn>(txn: &T, t: &XmlTextRef) -> String {
|
|
63
63
|
match t.get_attribute(txn, "__type") {
|
|
64
64
|
Some(Out::Any(Any::String(s))) => s.to_string(),
|
|
65
65
|
_ => String::new(),
|
|
@@ -229,7 +229,7 @@ fn scalar_json(a: &Any) -> String {
|
|
|
229
229
|
|
|
230
230
|
/// Convert a yrs output value to an `Any` (which knows how to JSON-serialize),
|
|
231
231
|
/// recursing through nested shared collections.
|
|
232
|
-
fn out_to_any<T: ReadTxn>(txn: &T, out: &Out) -> Any {
|
|
232
|
+
pub(crate) fn out_to_any<T: ReadTxn>(txn: &T, out: &Out) -> Any {
|
|
233
233
|
match out {
|
|
234
234
|
Out::Any(a) => a.clone(),
|
|
235
235
|
Out::YText(v) => Any::from(v.get_string(txn)),
|