lattice_mode/services.rs
1//! `ServiceRegistry`: typed lookup for subsystem handles that
2//! modes need access to from their lifecycle hooks (Phase 3 of
3//! the mode-architecture `ModeContext` extension).
4//!
5//! Used by `lsp-mode`: its `on_deactivate` needs the LSP
6//! supervisor handle to send `textDocument/didClose`, plus a
7//! buffer-id → URI resolver to know which URI to close. The
8//! App registers these at boot via [`ServiceRegistry::register`];
9//! modes pull them at activation/deactivation via
10//! [`crate::ModeContext::service`].
11//!
12//! Why typed (not enum / not free-form Any): subsystem handles
13//! are stable across renderers. A TUI host and a future GPUI
14//! host register the same `LspSupervisorHandle` type; modes pull
15//! by type without caring which host installed it. Adding a new
16//! service is one `register::<NewService>(...)` call at boot
17//! plus a `ctx.service::<NewService>()` consumption — no enum
18//! to extend, no breaking change to the trait surface.
19
20use std::any::{Any, TypeId};
21use std::collections::HashMap;
22use std::sync::Arc;
23
24/// Typed map keyed by [`TypeId`]. Stores `Arc<dyn Any + Send +
25/// Sync>` per slot; clones on lookup (cheap — services are
26/// `Arc` internally). Built at boot, read-only thereafter.
27///
28/// **Look up with exactly the type you registered.** The key is
29/// `TypeId::of::<T>()` of the value passed to [`register`](Self::register),
30/// so registering an `Arc<X>` stores it under `Arc<X>`, and a later
31/// `get::<X>()` silently returns `None`. The convention for an
32/// already-shared handle is a named alias (`type XHandle = Arc<X>`) used on
33/// both sides — see [`BufferStoreHandle`](crate::BufferStoreHandle),
34/// [`ModeRegistryHandle`](crate::ModeRegistryHandle) and the other
35/// `*Handle` aliases in this crate.
36///
37/// # Examples
38///
39/// ```
40/// use std::sync::Arc;
41/// use lattice_mode::ServiceRegistry;
42///
43/// struct Clock(u64);
44/// type ClockHandle = Arc<Clock>;
45///
46/// let mut services = ServiceRegistry::new();
47/// services.register::<ClockHandle>(Arc::new(Clock(42)));
48///
49/// // Same type on both sides: found (an `Arc<ClockHandle>`, i.e. two layers).
50/// assert_eq!(services.get::<ClockHandle>().unwrap().0, 42);
51/// // The TypeId pitfall: `Clock` was never registered, only `Arc<Clock>`.
52/// assert!(services.get::<Clock>().is_none());
53/// ```
54#[derive(Default)]
55pub struct ServiceRegistry {
56 services: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
57}
58
59impl ServiceRegistry {
60 /// An empty registry. The host fills it at boot; tests build one
61 /// with only the services the code under test reads.
62 pub fn new() -> Self {
63 Self::default()
64 }
65
66 /// Register a service. Overwrites if a service of the same
67 /// type was already registered (last-write-wins — typically
68 /// called once per type at boot).
69 pub fn register<T: Any + Send + Sync>(&mut self, service: T) {
70 self.services.insert(TypeId::of::<T>(), Arc::new(service));
71 }
72
73 /// Look up a service by type. Returns a fresh `Arc<T>` clone
74 /// when found; `None` when no service of that type is
75 /// registered (calling renderer hasn't wired this subsystem
76 /// yet, or the mode is running in a stripped-down test
77 /// harness).
78 pub fn get<T: Any + Send + Sync>(&self) -> Option<Arc<T>> {
79 let entry = self.services.get(&TypeId::of::<T>())?;
80 Arc::clone(entry).downcast::<T>().ok()
81 }
82}
83
84impl std::fmt::Debug for ServiceRegistry {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 f.debug_struct("ServiceRegistry")
87 .field("registered_count", &self.services.len())
88 .finish_non_exhaustive()
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 #![allow(clippy::unwrap_used)]
95 use super::*;
96
97 #[derive(Debug)]
98 struct ServiceA {
99 value: i32,
100 }
101
102 #[derive(Debug)]
103 struct ServiceB {
104 label: &'static str,
105 }
106
107 #[test]
108 fn register_and_lookup_by_type() {
109 let mut r = ServiceRegistry::new();
110 r.register(ServiceA { value: 42 });
111 r.register(ServiceB { label: "hello" });
112 let a = r.get::<ServiceA>().unwrap();
113 assert_eq!(a.value, 42);
114 let b = r.get::<ServiceB>().unwrap();
115 assert_eq!(b.label, "hello");
116 }
117
118 #[test]
119 fn missing_service_returns_none() {
120 let r = ServiceRegistry::new();
121 assert!(r.get::<ServiceA>().is_none());
122 }
123
124 #[test]
125 fn register_overwrites_previous_of_same_type() {
126 let mut r = ServiceRegistry::new();
127 r.register(ServiceA { value: 1 });
128 r.register(ServiceA { value: 2 });
129 assert_eq!(r.get::<ServiceA>().unwrap().value, 2);
130 }
131}