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