Skip to main content

strop_core/
id.rs

1//! Stable identities and typed coordinates (0014 wave 2).
2//!
3//! Two families:
4//! - **IDs**: generational arena keys (`DocumentId`, `ViewId`, …). A
5//!   closed document's id fails lookup instead of silently resolving to
6//!   whatever moved into its old vector slot.
7//! - **Coordinates**: newtypes so byte offsets, line indexes, and the
8//!   three column kinds (bytes / UTF-16 / display) can't mix silently.
9
10mod seed;
11pub use seed::{ArenaSeed, ArenaSeedError};
12
13/// A generational-arena key: the index names the slot, the generation
14/// names the occupant. Stale keys fail lookup.
15#[derive(
16    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
17)]
18#[serde(bound = "")]
19pub struct Id<K> {
20    #[serde(rename = "slot", alias = "index")]
21    index: u32,
22    generation: u32,
23    #[serde(skip)]
24    _kind: std::marker::PhantomData<K>,
25}
26
27impl<K> Id<K> {
28    pub fn index(self) -> usize {
29        self.index as usize
30    }
31
32    pub fn generation(self) -> u32 {
33        self.generation
34    }
35}
36
37/// Marker kinds for the arena's identities.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct DocumentKind;
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub struct ViewKind;
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43pub struct PaneKind;
44/// Registry keys for bound workspace contexts (0042 slice 2).
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub struct WorkspaceKind;
47
48pub type DocumentId = Id<DocumentKind>;
49pub type ViewId = Id<ViewKind>;
50pub type PaneId = Id<PaneKind>;
51pub type WorkspaceId = Id<WorkspaceKind>;
52
53/// A minimal generational arena (house rule: 40 boring lines beat a
54/// dependency). Slots are reused; each reuse bumps the generation.
55pub struct Arena<K, T> {
56    slots: Vec<Slot<T>>,
57    free: Vec<u32>,
58    _kind: std::marker::PhantomData<K>,
59}
60
61#[derive(Debug)]
62struct Slot<T> {
63    generation: u32,
64    value: Option<T>,
65}
66
67impl<K, T> Default for Arena<K, T> {
68    fn default() -> Self {
69        Self {
70            slots: Vec::new(),
71            free: Vec::new(),
72            _kind: std::marker::PhantomData,
73        }
74    }
75}
76
77impl<K, T> Arena<K, T> {
78    pub fn insert(&mut self, value: T) -> Id<K> {
79        if let Some(index) = self.free.pop() {
80            let slot = &mut self.slots[index as usize];
81            slot.generation += 1;
82            slot.value = Some(value);
83            return Id {
84                index,
85                generation: slot.generation,
86                _kind: std::marker::PhantomData,
87            };
88        }
89        let index = self.slots.len() as u32;
90        self.slots.push(Slot {
91            generation: 0,
92            value: Some(value),
93        });
94        Id {
95            index,
96            generation: 0,
97            _kind: std::marker::PhantomData,
98        }
99    }
100
101    /// None for a stale id — never the wrong document.
102    pub fn get(&self, id: Id<K>) -> Option<&T> {
103        self.slots
104            .get(id.index as usize)
105            .filter(|s| s.generation == id.generation)
106            .and_then(|s| s.value.as_ref())
107    }
108
109    pub fn get_mut(&mut self, id: Id<K>) -> Option<&mut T> {
110        self.slots
111            .get_mut(id.index as usize)
112            .filter(|s| s.generation == id.generation)
113            .and_then(|s| s.value.as_mut())
114    }
115
116    /// Remove and return the value; stale ids get None.
117    pub fn remove(&mut self, id: Id<K>) -> Option<T> {
118        let slot = self.slots.get_mut(id.index as usize)?;
119        if slot.generation != id.generation {
120            return None;
121        }
122        let value = slot.value.take()?;
123        self.free.push(id.index);
124        Some(value)
125    }
126
127    pub fn iter(&self) -> impl Iterator<Item = (Id<K>, &T)> {
128        self.slots.iter().enumerate().filter_map(|(i, s)| {
129            s.value.as_ref().map(|v| {
130                (
131                    Id {
132                        index: i as u32,
133                        generation: s.generation,
134                        _kind: std::marker::PhantomData,
135                    },
136                    v,
137                )
138            })
139        })
140    }
141
142    pub fn len(&self) -> usize {
143        self.slots.iter().filter(|s| s.value.is_some()).count()
144    }
145
146    /// Drop every live value (slot reuse still bumps generations).
147    pub fn clear(&mut self) {
148        let live: Vec<u32> = self
149            .slots
150            .iter()
151            .enumerate()
152            .filter(|(_, s)| s.value.is_some())
153            .map(|(i, _)| i as u32)
154            .collect();
155        for s in &mut self.slots {
156            s.value = None;
157        }
158        self.free.extend(live);
159    }
160    pub fn is_empty(&self) -> bool {
161        self.len() == 0
162    }
163}
164
165/// A coordinate newtype: the inner value is bytes (ByteOffset), lines
166/// (LineIndex), or columns in a named unit (ByteColumn/Utf16Column/
167/// DisplayColumn). Copy, ordered, hashable; arithmetic is explicit.
168macro_rules! coordinate {
169    ($name:ident, $unit:literal) => {
170        #[derive(
171            Debug,
172            Clone,
173            Copy,
174            PartialEq,
175            Eq,
176            PartialOrd,
177            Ord,
178            Hash,
179            Default,
180            serde::Serialize,
181            serde::Deserialize,
182        )]
183        #[repr(transparent)]
184        #[serde(transparent)]
185        pub struct $name(usize);
186
187        impl $name {
188            #[inline]
189            pub fn new(v: usize) -> Self {
190                Self(v)
191            }
192            /// Raw units (bytes / lines / columns per the type) — escape
193            /// hatch for arithmetic; naming the unit is the point.
194            #[inline]
195            pub fn get(self) -> usize {
196                self.0
197            }
198            #[inline]
199            pub fn saturating_sub(self, n: usize) -> Self {
200                Self(self.0.saturating_sub(n))
201            }
202        }
203
204        impl std::ops::AddAssign<usize> for $name {
205            #[inline]
206            fn add_assign(&mut self, n: usize) {
207                self.0 += n;
208            }
209        }
210        impl std::ops::SubAssign<usize> for $name {
211            #[inline]
212            fn sub_assign(&mut self, n: usize) {
213                self.0 -= n;
214            }
215        }
216        /// Raw-unit comparison: `offset > 0` reads naturally; the type
217        /// system still stops offset-vs-line mixes (the bug class).
218        impl PartialEq<usize> for $name {
219            #[inline]
220            fn eq(&self, other: &usize) -> bool {
221                self.0 == *other
222            }
223        }
224        impl PartialOrd<usize> for $name {
225            #[inline]
226            fn partial_cmp(&self, other: &usize) -> Option<std::cmp::Ordering> {
227                self.0.partial_cmp(other)
228            }
229        }
230        impl std::ops::Add<usize> for $name {
231            type Output = $name;
232            #[inline]
233            fn add(self, n: usize) -> $name {
234                $name(self.0 + n)
235            }
236        }
237        impl std::ops::Sub<usize> for $name {
238            type Output = $name;
239            #[inline]
240            fn sub(self, n: usize) -> $name {
241                $name(self.0 - n)
242            }
243        }
244        impl std::ops::Sub<$name> for $name {
245            type Output = usize; // a length
246            #[inline]
247            fn sub(self, other: $name) -> usize {
248                self.0 - other.0
249            }
250        }
251        impl std::fmt::Display for $name {
252            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253                write!(f, "{} {}", self.0, $unit)
254            }
255        }
256        impl From<$name> for usize {
257            #[inline]
258            fn from(v: $name) -> usize {
259                v.0
260            }
261        }
262        impl From<usize> for $name {
263            #[inline]
264            fn from(v: usize) -> $name {
265                $name(v)
266            }
267        }
268    };
269}
270
271coordinate!(ByteOffset, "B");
272coordinate!(LineIndex, "L");
273coordinate!(ByteColumn, "col:B");
274coordinate!(Utf16Column, "col:u16");
275coordinate!(DisplayColumn, "col:dsp");
276
277/// A document's content clock, not an LSP version, request ID or history node.
278#[derive(
279    Debug,
280    Clone,
281    Copy,
282    PartialEq,
283    Eq,
284    PartialOrd,
285    Ord,
286    Hash,
287    Default,
288    serde::Serialize,
289    serde::Deserialize,
290)]
291#[serde(transparent)]
292pub struct BufferRevision(u64);
293
294impl BufferRevision {
295    pub const fn new(value: u64) -> Self {
296        Self(value)
297    }
298    pub const fn get(self) -> u64 {
299        self.0
300    }
301    pub fn checked_next(self) -> Option<Self> {
302        self.0.checked_add(1).map(Self)
303    }
304}
305
306impl std::fmt::Display for BufferRevision {
307    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        self.0.fmt(formatter)
309    }
310}
311
312impl From<u64> for BufferRevision {
313    fn from(value: u64) -> Self {
314        Self(value)
315    }
316}
317
318// NOTE: full newtype wrappers (ByteOffset(usize), Utf16Column(u32), …)
319// are the target; the pragmatic cutover is to name the domains first
320// (this module + the conversion functions) and tighten the Buffer API
321// per call-site cluster as waves 2–4 touch them. A big-bang usize→newtype
322// rewrite of every arithmetic site would be unreviewable.
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn stale_ids_fail_lookup() {
330        let mut a: Arena<DocumentKind, String> = Arena::default();
331        let one = a.insert("one".into());
332        let two = a.insert("two".into());
333        assert_eq!(a.get(one).map(String::as_str), Some("one"));
334        a.remove(one);
335        assert_eq!(a.get(one), None, "removed");
336        let three = a.insert("three".into()); // reuses the slot
337        assert_eq!(a.get(one), None, "stale generation must not resolve");
338        assert_eq!(a.get(three).map(String::as_str), Some("three"));
339        assert_eq!(a.get(two).map(String::as_str), Some("two"));
340        assert_eq!(a.len(), 2);
341    }
342}