Skip to main content

lattice_protocol/
ids.rs

1//! Newtype identifiers for editor entities.
2//!
3//! Every id is a `u64` newtype, so it fits one register, round-trips through a
4//! WIT `u64` without precision loss, and serializes as the bare number
5//! (`#[serde(transparent)]`). The point of the newtypes is nominal: a
6//! [`DocumentId`] and a [`BufferId`] with the same raw value are different
7//! types and cannot be compared or passed for one another by accident.
8//!
9//! This module only *defines* the types; it issues nothing. Each id is minted
10//! by the subsystem that owns the entity, which decides uniqueness and
11//! lifetime (documented per type below). The convention those minters follow:
12//! monotonically increasing values, never reused within a process. `0` is the
13//! [`Default`] and is conventionally "none / not yet assigned".
14//!
15//! Several ids are declared ahead of their consumers: the pane, tab, window
16//! and plugin subsystems currently mint their own narrower ids (in
17//! `lattice-core` and `lattice-plugin-host`), and the ids here are the
18//! planned wire-level spelling. Each type's doc says which case it is.
19//!
20//! # Examples
21//!
22//! ```
23//! use lattice_protocol::{BufferId, DocumentId};
24//!
25//! let doc = DocumentId::new(42);
26//! assert_eq!(doc.raw(), 42);
27//! assert_eq!(DocumentId::new(doc.raw()), doc); // lossless round-trip
28//! assert_eq!(doc.to_string(), "DocumentId#42"); // Display names the type
29//!
30//! // Ids order by their raw value, and serialize as the bare number.
31//! assert!(BufferId::new(1) < BufferId::new(2));
32//! assert_eq!(serde_json::to_string(&BufferId::new(7)).unwrap(), "7");
33//! ```
34
35use serde::{Deserialize, Serialize};
36
37macro_rules! id {
38    ($(#[$meta:meta])* $name:ident) => {
39        $(#[$meta])*
40        #[derive(
41            Debug,
42            Clone,
43            Copy,
44            Default,
45            PartialEq,
46            Eq,
47            PartialOrd,
48            Ord,
49            Hash,
50            Serialize,
51            Deserialize,
52        )]
53        #[serde(transparent)]
54        pub struct $name(pub u64);
55
56        impl $name {
57            /// Wrap a raw value. Does not mint or reserve anything: callers
58            /// that need a fresh id get one from the owning subsystem.
59            pub const fn new(raw: u64) -> Self {
60                Self(raw)
61            }
62
63            /// The underlying `u64`, e.g. for crossing the WIT boundary.
64            pub const fn raw(self) -> u64 {
65                self.0
66            }
67        }
68
69        impl std::fmt::Display for $name {
70            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71                write!(f, "{}#{}", stringify!($name), self.0)
72            }
73        }
74    };
75}
76
77id!(
78    /// Identifies one `lattice_core::Document` — a text buffer with its
79    /// undo history, selections and path — for its whole lifetime.
80    ///
81    /// Minted by `lattice-core` from a process-wide counter starting at 1, so
82    /// ids are unique within a process and never reused. This is the id
83    /// document events ([`Event::DocumentChanged`], [`Event::DocumentSaved`],
84    /// [`Event::SelectionsChanged`], ...) carry.
85    ///
86    /// [`Event::DocumentChanged`]: crate::Event::DocumentChanged
87    /// [`Event::DocumentSaved`]: crate::Event::DocumentSaved
88    /// [`Event::SelectionsChanged`]: crate::Event::SelectionsChanged
89    DocumentId
90);
91id!(
92    /// Identifies one entry of the host's buffer registry (a document, file
93    /// tree, help view, multibuffer, terminal, ...) at the protocol level.
94    ///
95    /// The registry mints a narrower `lattice_core::BufferId(u32)`; crossing
96    /// into this crate widens it (`BufferId::new(id.0 as u64)`), so the raw
97    /// values agree. Mode-lifecycle events ([`Event::MajorEntered`] and
98    /// peers) and [`Event::BufferOptionOverrideRequested`] address buffers
99    /// with it.
100    ///
101    /// [`Event::MajorEntered`]: crate::Event::MajorEntered
102    /// [`Event::BufferOptionOverrideRequested`]: crate::Event::BufferOptionOverrideRequested
103    BufferId
104);
105id!(
106    /// Identifies an OS-level editor window. Declared for the protocol; no
107    /// subsystem mints it yet (the editor runs one window).
108    WindowId
109);
110id!(
111    /// Identifies a tab page. Declared for the protocol; the tab subsystem
112    /// currently mints its own `lattice_core::ui::TabId(u32)`.
113    TabId
114);
115id!(
116    /// Identifies a pane (a split showing one buffer). Declared for the
117    /// protocol; the layout currently mints its own `lattice_core::ui::PaneId(u32)`.
118    PaneId
119);
120id!(
121    /// Identifies a loaded plugin instance. Declared for the protocol; the
122    /// plugin host currently mints its own `lattice_plugin_host::PluginId`,
123    /// and plugin events carry it as a bare `u32`
124    /// ([`Event::PluginCrashed`](crate::Event::PluginCrashed)).
125    PluginId
126);
127id!(
128    /// Identifies a registered command — the `command` of a
129    /// `lattice_grammar::CommandInvocation`, and what a keymap binding
130    /// resolves to. Minted at registration by `lattice-grammar`'s command
131    /// registry from a process-wide counter starting at 1. (The completion
132    /// registry keeps a separate counter of its own, so an id is only
133    /// meaningful together with the registry that issued it.)
134    CommandId
135);
136id!(
137    /// Identifies a major mode. Declared for the protocol; modes are
138    /// currently identified by `lattice_mode::ModeId`, and events carry the
139    /// mode's canonical name as a `String`.
140    MajorModeId
141);
142id!(
143    /// Identifies a minor mode. Declared for the protocol; see
144    /// [`MajorModeId`] for how modes are identified today.
145    MinorModeId
146);
147
148#[cfg(test)]
149mod tests {
150    #![allow(clippy::unwrap_used, clippy::panic)]
151    use super::*;
152
153    #[test]
154    fn raw_round_trips() {
155        let id = DocumentId::new(42);
156        assert_eq!(id.raw(), 42);
157        assert_eq!(DocumentId::new(id.raw()), id);
158    }
159
160    #[test]
161    fn display_includes_type_name() {
162        assert_eq!(format!("{}", PaneId::new(7)), "PaneId#7");
163        assert_eq!(format!("{}", PluginId::new(123)), "PluginId#123");
164    }
165
166    #[test]
167    fn equal_raw_means_equal_id() {
168        assert_eq!(DocumentId::new(5), DocumentId::new(5));
169        assert_ne!(DocumentId::new(5), DocumentId::new(6));
170    }
171
172    #[test]
173    fn ordering_follows_raw() {
174        assert!(DocumentId::new(1) < DocumentId::new(2));
175        assert!(WindowId::new(10) > WindowId::new(9));
176    }
177
178    #[test]
179    fn distinct_id_types_do_not_alias() {
180        // Compile-time check: a `DocumentId` and a `BufferId` with the same
181        // raw value are nominally distinct types and cannot be compared.
182        let _doc = DocumentId::new(1);
183        let _buf = BufferId::new(1);
184        // (No assertion needed; the test exists to anchor the invariant.)
185    }
186
187    #[test]
188    fn ids_serialize_as_their_raw_u64() {
189        let id = TabId::new(99);
190        let json = serde_json::to_string(&id).unwrap();
191        assert_eq!(json, "99");
192        let back: TabId = serde_json::from_str(&json).unwrap();
193        assert_eq!(back, id);
194    }
195}