lattice_terminal/
modes.rs1use std::sync::Arc;
2
3use lattice_config::OptionOverrideSet;
4use lattice_core::{BufferId, BufferKind};
5use lattice_mode::{
6 CapabilitySet, LifecycleFuture, Mode, ModeContext, ModeId, ModeKind, ModeRegistry,
7};
8
9use crate::synthetic::TerminalStoreHandle;
10
11pub struct TerminalMode;
12
13impl TerminalMode {
14 pub fn mode_id() -> ModeId {
15 ModeId::new("terminal-mode")
16 }
17}
18
19impl Mode for TerminalMode {
20 type Guard = ();
21 fn id(&self) -> ModeId {
22 Self::mode_id()
23 }
24 fn kind(&self) -> ModeKind {
25 ModeKind::Major
26 }
27 fn target_buffer_kind(&self) -> Option<BufferKind> {
30 Some(BufferKind::Terminal)
31 }
32 fn options(&self) -> OptionOverrideSet {
33 lattice_config::overrides! {
41 lattice_config::ReadOnly = true,
42 lattice_config::NoFile = true,
43 }
44 }
45 fn required_capabilities(&self) -> CapabilitySet {
46 CapabilitySet::empty()
47 }
48 fn invocation_runner(&self) -> Option<ModeId> {
54 Some(Self::mode_id())
55 }
56 fn on_activate(&self, _ctx: ModeContext) -> LifecycleFuture<'_, ()> {
57 Box::pin(async { Ok(()) })
58 }
59}
60
61pub struct TerminalInsertMode;
76
77impl TerminalInsertMode {
78 pub fn mode_id() -> ModeId {
79 ModeId::new("terminal-insert-mode")
80 }
81}
82
83impl Mode for TerminalInsertMode {
84 type Guard = ();
85 fn id(&self) -> ModeId {
86 Self::mode_id()
87 }
88 fn kind(&self) -> ModeKind {
89 ModeKind::Minor
90 }
91 fn options(&self) -> OptionOverrideSet {
92 OptionOverrideSet::default()
96 }
97 fn required_capabilities(&self) -> CapabilitySet {
98 CapabilitySet::empty()
99 }
100 fn on_activate(&self, _ctx: ModeContext) -> LifecycleFuture<'_, ()> {
101 Box::pin(async { Ok(()) })
102 }
103}
104
105pub struct TerminalNormalMode;
131
132impl TerminalNormalMode {
133 pub fn mode_id() -> ModeId {
134 ModeId::new("terminal-normal-mode")
135 }
136}
137
138impl Mode for TerminalNormalMode {
139 type Guard = TerminalNormalModeGuard;
140 fn id(&self) -> ModeId {
141 Self::mode_id()
142 }
143 fn kind(&self) -> ModeKind {
144 ModeKind::Minor
145 }
146 fn options(&self) -> OptionOverrideSet {
147 OptionOverrideSet::default()
150 }
151 fn required_capabilities(&self) -> CapabilitySet {
152 CapabilitySet::empty()
153 }
154 fn on_activate(&self, ctx: ModeContext) -> LifecycleFuture<'_, Self::Guard> {
155 Box::pin(async move {
156 let buf_id = BufferId(ctx.buffer_id().0 as u32);
157 let store = ctx.service::<TerminalStoreHandle>();
158 if let Some(store) = store.as_ref() {
159 store.install_synthetic(buf_id);
165 }
166 Ok(TerminalNormalModeGuard {
167 store,
168 buffer_id: buf_id,
169 })
170 })
171 }
172}
173
174pub struct TerminalNormalModeGuard {
181 store: Option<Arc<TerminalStoreHandle>>,
182 buffer_id: BufferId,
183}
184
185impl Drop for TerminalNormalModeGuard {
186 fn drop(&mut self) {
187 if let Some(store) = &self.store {
188 store.clear_synthetic(self.buffer_id);
189 }
190 }
191}
192
193pub fn register_terminal_modes(registry: &mut ModeRegistry) {
194 registry
195 .register(TerminalMode)
196 .expect("terminal-mode register");
197 registry
198 .register(TerminalInsertMode)
199 .expect("terminal-insert-mode register");
200 registry
201 .register(TerminalNormalMode)
202 .expect("terminal-normal-mode register");
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn terminal_mode_id_kind() {
211 assert_eq!(TerminalMode.id(), TerminalMode::mode_id());
212 assert_eq!(TerminalMode::mode_id().as_str(), "terminal-mode");
213 assert_eq!(TerminalMode.kind(), ModeKind::Major);
214 }
215
216 #[test]
217 fn terminal_insert_mode_id_kind() {
218 assert_eq!(TerminalInsertMode.id(), TerminalInsertMode::mode_id());
219 assert_eq!(
220 TerminalInsertMode::mode_id().as_str(),
221 "terminal-insert-mode",
222 );
223 assert_eq!(TerminalInsertMode.kind(), ModeKind::Minor);
224 }
225
226 #[test]
227 fn register_terminal_modes_populates_both() {
228 let mut registry = ModeRegistry::new();
229 register_terminal_modes(&mut registry);
230 assert!(registry.is_registered(TerminalMode::mode_id()));
231 assert!(registry.is_registered(TerminalInsertMode::mode_id()));
232 assert!(registry.is_registered(TerminalNormalMode::mode_id()));
234 }
235
236 #[test]
237 fn terminal_normal_mode_id_kind() {
238 assert_eq!(TerminalNormalMode.id(), TerminalNormalMode::mode_id());
239 assert_eq!(
240 TerminalNormalMode::mode_id().as_str(),
241 "terminal-normal-mode",
242 );
243 assert_eq!(TerminalNormalMode.kind(), ModeKind::Minor);
244 }
245
246 struct RecordingStore {
251 installs: std::sync::atomic::AtomicUsize,
252 clears: std::sync::atomic::AtomicUsize,
253 }
254
255 impl RecordingStore {
256 fn new() -> Self {
257 Self {
258 installs: std::sync::atomic::AtomicUsize::new(0),
259 clears: std::sync::atomic::AtomicUsize::new(0),
260 }
261 }
262 fn installs(&self) -> usize {
263 self.installs.load(std::sync::atomic::Ordering::SeqCst)
264 }
265 fn clears(&self) -> usize {
266 self.clears.load(std::sync::atomic::Ordering::SeqCst)
267 }
268 }
269
270 impl crate::synthetic::TerminalStore for RecordingStore {
271 fn install_synthetic(&self, _id: BufferId) -> bool {
272 self.installs
273 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
274 true
275 }
276 fn clear_synthetic(&self, _id: BufferId) -> bool {
277 self.clears
278 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
279 true
280 }
281 }
282
283 #[tokio::test]
284 async fn on_activate_installs_synthetic_via_store() {
285 let recording = Arc::new(RecordingStore::new());
286 let store_dyn: Arc<dyn crate::synthetic::TerminalStore> = recording.clone();
287 let handle = crate::synthetic::TerminalStoreHandle::new(store_dyn);
288
289 let mut services = lattice_mode::ServiceRegistry::new();
290 services.register(handle);
291 let services = Arc::new(services);
292
293 let ctx = ModeContext::new(
294 lattice_protocol::ids::BufferId::new(7),
295 TerminalNormalMode::mode_id(),
296 Arc::new(lattice_config::ConfigRegistry::new()),
297 Arc::new(lattice_runtime::EventBus::new()),
298 services,
299 );
300
301 let _guard = TerminalNormalMode
302 .on_activate(ctx)
303 .await
304 .expect("activate ok");
305 assert_eq!(
306 recording.installs(),
307 1,
308 "install_synthetic should fire once on activate"
309 );
310 assert_eq!(recording.clears(), 0, "no clear before guard drop");
311 drop(_guard);
313 assert_eq!(
314 recording.clears(),
315 1,
316 "clear_synthetic should fire on guard drop"
317 );
318 }
319
320 #[tokio::test]
321 async fn on_activate_succeeds_with_no_store_service() {
322 let services = Arc::new(lattice_mode::ServiceRegistry::new());
326 let ctx = ModeContext::new(
327 lattice_protocol::ids::BufferId::new(9),
328 TerminalNormalMode::mode_id(),
329 Arc::new(lattice_config::ConfigRegistry::new()),
330 Arc::new(lattice_runtime::EventBus::new()),
331 services,
332 );
333 let guard = TerminalNormalMode
334 .on_activate(ctx)
335 .await
336 .expect("activate ok without store");
337 drop(guard); }
339}