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}