Editor for papermario-dx mods
1// SPDX-FileCopyrightText: 2026 Alex Bates <alex@bates64.com>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5#![allow(
6 clippy::missing_panics_doc,
7 clippy::unwrap_used,
8 reason = "construction asserts containers are attached"
9)]
10
11use std::fmt;
12use std::marker::PhantomData;
13
14use loro::{LoroList, LoroMap, LoroMovableList, LoroTree, LoroValue};
15
16use crate::FromLoroMap;
17
18/// A typed string-keyed map where every value is of type `V`.
19///
20/// Use this as a field type in a `#[loroscope]` struct:
21///
22/// ```
23/// use loroscope::loroscope;
24///
25/// #[loroscope]
26/// struct Settings {
27/// pub values: Map<i64>,
28/// }
29///
30/// let settings = Settings::new();
31/// settings.values().insert("volume", 80);
32/// assert_eq!(settings.values().get("volume"), Some(80));
33/// ```
34///
35/// `V` can be a `#[loroscope]` struct, a primitive (`f64`, `i64`, `bool`,
36/// `String`), a Loro container type (`LoroText`, `LoroCounter`, `LoroTree`),
37/// or another collection ([`List<T>`](crate::List),
38/// [`MovableList<T>`](crate::MovableList), `Map<V>`).
39pub struct Map<V> {
40 map: LoroMap,
41 _phantom: PhantomData<V>,
42}
43
44impl<V> Clone for Map<V> {
45 fn clone(&self) -> Self {
46 Self {
47 map: self.map.clone(),
48 _phantom: PhantomData,
49 }
50 }
51}
52
53impl<V> fmt::Debug for Map<V> {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 f.debug_struct("Map").field("len", &self.len()).finish()
56 }
57}
58
59impl<V> Map<V> {
60 /// Creates a typed view from a [`LoroMap`].
61 ///
62 /// # Panics
63 ///
64 /// Panics if `map` is not attached to a [`LoroDoc`](loro::LoroDoc).
65 #[must_use]
66 pub fn new(map: LoroMap) -> Self {
67 assert!(
68 map.is_attached(),
69 "Map requires an attached LoroMap (one that belongs to a LoroDoc)"
70 );
71 Self {
72 map,
73 _phantom: PhantomData,
74 }
75 }
76
77 /// Returns the number of entries.
78 #[must_use]
79 pub fn len(&self) -> usize {
80 self.map.len()
81 }
82
83 /// Returns `true` if the map is empty.
84 #[must_use]
85 pub fn is_empty(&self) -> bool {
86 self.map.is_empty()
87 }
88
89 /// Returns a reference to the underlying [`LoroMap`].
90 #[must_use]
91 pub fn raw(&self) -> &LoroMap {
92 &self.map
93 }
94
95 /// Deletes the entry for `key`.
96 pub fn delete(&self, key: &str) {
97 self.map.delete(key).unwrap();
98 }
99
100 /// Removes all entries from the map.
101 pub fn clear(&self) {
102 self.map.clear().unwrap();
103 }
104
105 /// Returns an iterator over the keys in the map.
106 pub fn keys(&self) -> impl Iterator<Item = String> + '_ {
107 self.map.keys().map(|s| s.to_string())
108 }
109}
110
111impl<V: FromLoroMap> Map<V> {
112 /// Returns the value for `key`, or `None` if the key is missing.
113 pub fn get(&self, key: &str) -> Option<V> {
114 self.map
115 .get(key)
116 .and_then(|v| v.into_container().ok())
117 .and_then(|c| c.into_map().ok())
118 .map(V::from_loro_map)
119 }
120
121 /// Returns the value for `key`, creating it if it doesn't exist.
122 pub fn get_or_create(&self, key: &str) -> V {
123 V::from_loro_map(
124 self.map
125 .get_or_create_container(key, LoroMap::new())
126 .unwrap(),
127 )
128 }
129
130 /// Returns an iterator over all key-value pairs.
131 pub fn iter(&self) -> impl Iterator<Item = (String, V)> {
132 let mut items = Vec::with_capacity(self.len());
133 self.map.for_each(|key, voc| {
134 if let Some(map) = voc.into_container().ok().and_then(|c| c.into_map().ok()) {
135 items.push((key.to_owned(), V::from_loro_map(map)));
136 }
137 });
138 items.into_iter()
139 }
140}
141
142macro_rules! impl_primitive {
143 ($ty:ty, $variant:pat => $expr:expr) => {
144 impl Map<$ty> {
145 /// Returns the value for `key`, or `None` if missing.
146 pub fn get(&self, key: &str) -> Option<$ty> {
147 self.map
148 .get(key)
149 .and_then(|v| v.into_value().ok())
150 .and_then(|v| match v {
151 $variant => Some($expr),
152 _ => None,
153 })
154 }
155
156 /// Returns an iterator over all key-value pairs.
157 pub fn iter(&self) -> impl Iterator<Item = (String, $ty)> {
158 let mut items = Vec::with_capacity(self.len());
159 self.map.for_each(|key, voc| {
160 if let Some(v) = voc.into_value().ok().and_then(|v| match v {
161 $variant => Some($expr),
162 _ => None,
163 }) {
164 items.push((key.to_owned(), v));
165 }
166 });
167 items.into_iter()
168 }
169 }
170 };
171}
172
173impl_primitive!(f64, LoroValue::Double(d) => d);
174impl_primitive!(i64, LoroValue::I64(i) => i);
175impl_primitive!(bool, LoroValue::Bool(b) => b);
176impl_primitive!(String, LoroValue::String(s) => s.to_string());
177
178macro_rules! impl_insert {
179 ($($value_ty:ty => $param_ty:ty),* $(,)?) => {$(
180 impl Map<$value_ty> {
181 /// Inserts a value for the given key, overwriting any previous value.
182 pub fn insert(&self, key: &str, val: $param_ty) {
183 self.map.insert(key, val).unwrap();
184 }
185 }
186 )*};
187}
188
189impl_insert!(f64 => f64, i64 => i64, bool => bool, String => &str);
190
191macro_rules! impl_container {
192 ($ty:ty, $variant:ident) => {
193 impl Map<$ty> {
194 /// Returns the container for `key`, or `None` if missing.
195 pub fn get(&self, key: &str) -> Option<$ty> {
196 self.map
197 .get(key)
198 .and_then(|v| v.into_container().ok())
199 .and_then(|c| c.$variant().ok())
200 }
201
202 /// Returns the container for `key`, creating it if it doesn't exist.
203 pub fn get_or_create(&self, key: &str) -> $ty {
204 self.map.get_or_create_container(key, <$ty>::new()).unwrap()
205 }
206 }
207 };
208}
209
210impl_container!(loro::LoroText, into_text);
211impl_container!(loro::LoroCounter, into_counter);
212impl_container!(loro::LoroTree, into_tree);
213
214macro_rules! impl_wrapper {
215 ($wrapper:ident < $($param:ident),+ >, $loro_ty:ty, $variant:ident) => {
216 impl<$($param),+> Map<crate::$wrapper<$($param),+>> {
217 /// Returns the value for `key`, or `None` if missing.
218 pub fn get(&self, key: &str) -> Option<crate::$wrapper<$($param),+>> {
219 self.map
220 .get(key)
221 .and_then(|v| v.into_container().ok())
222 .and_then(|c| c.$variant().ok())
223 .map(crate::$wrapper::new)
224 }
225
226 /// Returns the value for `key`, creating it if it doesn't exist.
227 pub fn get_or_create(&self, key: &str) -> crate::$wrapper<$($param),+> {
228 crate::$wrapper::new(
229 self.map
230 .get_or_create_container(key, <$loro_ty>::new())
231 .unwrap(),
232 )
233 }
234 }
235 };
236}
237
238impl_wrapper!(List<T>, LoroList, into_list);
239impl_wrapper!(MovableList<T>, LoroMovableList, into_movable_list);
240impl_wrapper!(Map<V>, LoroMap, into_map);
241
242// Tree needs a manual impl because Tree<T> requires T: FromLoroMap.
243impl<T: FromLoroMap> Map<crate::Tree<T>> {
244 /// Returns the value for `key`, or `None` if missing.
245 pub fn get(&self, key: &str) -> Option<crate::Tree<T>> {
246 self.map
247 .get(key)
248 .and_then(|v| v.into_container().ok())
249 .and_then(|c| c.into_tree().ok())
250 .map(crate::Tree::new)
251 }
252
253 /// Returns the value for `key`, creating it if it doesn't exist.
254 pub fn get_or_create(&self, key: &str) -> crate::Tree<T> {
255 crate::Tree::new(
256 self.map
257 .get_or_create_container(key, LoroTree::new())
258 .unwrap(),
259 )
260 }
261}