strop_engine/editor/
workspaces.rs1use std::collections::HashMap;
12use std::path::PathBuf;
13
14use strop_core::id::{Arena, WorkspaceId, WorkspaceKind};
15use strop_workspace::Filesystem;
16
17pub struct WorkspaceContext {
19 pub filesystem: Filesystem,
20 pub root: Option<PathBuf>,
24 pub incarnation: u64,
26}
27
28#[derive(Default)]
29pub struct WorkspaceRegistry {
30 arena: Arena<WorkspaceKind, WorkspaceContext>,
31 by_filesystem: HashMap<Filesystem, WorkspaceId>,
32}
33
34impl WorkspaceRegistry {
35 pub fn bind(&mut self, filesystem: Filesystem, root: Option<PathBuf>) -> WorkspaceId {
38 if let Some(id) = self.by_filesystem.get(&filesystem) {
39 return *id;
40 }
41 let id = self.arena.insert(WorkspaceContext {
42 filesystem: filesystem.clone(),
43 root,
44 incarnation: 0,
45 });
46 self.by_filesystem.insert(filesystem, id);
47 id
48 }
49
50 pub fn note_disconnect(&mut self, filesystem: &Filesystem) {
53 if let Some(id) = self.by_filesystem.get(filesystem) {
54 if let Some(context) = self.arena.get_mut(*id) {
55 context.incarnation += 1;
56 }
57 }
58 }
59
60 pub fn iter(&self) -> impl Iterator<Item = (WorkspaceId, &WorkspaceContext)> {
61 self.arena.iter()
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use strop_workspace::RemoteEndpoint;
69
70 #[test]
71 fn rebinding_keeps_identity_and_disconnect_bumps_incarnation() {
72 let mut registry = WorkspaceRegistry::default();
73 let local = registry.bind(Filesystem::Local, Some(PathBuf::from("/work")));
74 assert_eq!(registry.bind(Filesystem::Local, None), local, "idempotent");
75 let endpoint = RemoteEndpoint::parse("ssh://dev@example.com:2222").unwrap();
76 let remote = registry.bind(Filesystem::Remote(endpoint.clone()), None);
77 assert_ne!(local, remote, "namespaces never share a slot");
78 let remote_fs = Filesystem::Remote(endpoint.clone());
79 let incarnation = |registry: &WorkspaceRegistry| {
80 registry
81 .iter()
82 .find(|(_, context)| context.filesystem == remote_fs)
83 .map(|(_, context)| context.incarnation)
84 };
85 assert_eq!(incarnation(®istry), Some(0));
86 registry.note_disconnect(&remote_fs);
87 assert_eq!(incarnation(®istry), Some(1));
88 assert_eq!(
89 registry.bind(Filesystem::Remote(endpoint), None),
90 remote,
91 "reconnect reuses the slot with the bumped incarnation"
92 );
93 let other = RemoteEndpoint::parse("ssh://other.example.com").unwrap();
94 registry.note_disconnect(&Filesystem::Remote(other));
95 }
96}