ryo_analysis/symbol/
file_registry.rs1use super::FileId;
11use ryo_symbol::WorkspaceFilePath;
12use serde::Serialize;
13use slotmap::SlotMap;
14use std::collections::HashMap;
15
16#[derive(Debug, Clone, thiserror::Error)]
20#[error("invalid file id: {0:?}")]
21pub struct InvalidFileId(pub FileId);
22
23#[derive(Clone, Serialize)]
33pub struct FileRegistry {
34 id_to_path: SlotMap<FileId, WorkspaceFilePath>,
36
37 path_to_id: HashMap<WorkspaceFilePath, FileId>,
39}
40
41impl FileRegistry {
42 pub fn new() -> Self {
44 Self {
45 id_to_path: SlotMap::with_key(),
46 path_to_id: HashMap::new(),
47 }
48 }
49
50 pub fn with_capacity(capacity: usize) -> Self {
52 Self {
53 id_to_path: SlotMap::with_capacity_and_key(capacity),
54 path_to_id: HashMap::with_capacity(capacity),
55 }
56 }
57
58 pub fn register(&mut self, path: WorkspaceFilePath) -> FileId {
65 if let Some(&existing_id) = self.path_to_id.get(&path) {
67 return existing_id;
68 }
69
70 let id = self.id_to_path.insert(path.clone());
72 self.path_to_id.insert(path, id);
73
74 id
75 }
76
77 pub fn remove(&mut self, id: FileId) -> Option<WorkspaceFilePath> {
83 let path = self.id_to_path.remove(id)?;
84 self.path_to_id.remove(&path);
85 Some(path)
86 }
87
88 #[inline]
92 pub fn lookup(&self, path: &WorkspaceFilePath) -> Option<FileId> {
93 self.path_to_id.get(path).copied()
94 }
95
96 #[inline]
98 pub fn path(&self, id: FileId) -> Option<&WorkspaceFilePath> {
99 self.id_to_path.get(id)
100 }
101
102 #[inline]
104 pub fn crate_name(&self, id: FileId) -> Option<&str> {
105 self.id_to_path.get(id).map(|p| p.crate_name().as_str())
106 }
107
108 #[inline]
110 pub fn contains(&self, id: FileId) -> bool {
111 self.id_to_path.contains_key(id)
112 }
113
114 pub fn update_path(
120 &mut self,
121 id: FileId,
122 new_path: WorkspaceFilePath,
123 ) -> Result<WorkspaceFilePath, InvalidFileId> {
124 let old_path = self.id_to_path.get(id).ok_or(InvalidFileId(id))?.clone();
125
126 self.path_to_id.remove(&old_path);
128 self.path_to_id.insert(new_path.clone(), id);
129 self.id_to_path[id] = new_path;
130
131 Ok(old_path)
132 }
133
134 pub fn iter(&self) -> impl Iterator<Item = (FileId, &WorkspaceFilePath)> {
138 self.id_to_path.iter()
139 }
140
141 pub fn iter_in_crate<'a>(&'a self, crate_name: &'a str) -> impl Iterator<Item = FileId> + 'a {
143 self.id_to_path
144 .iter()
145 .filter(move |(_, path)| path.crate_name().as_str() == crate_name)
146 .map(|(id, _)| id)
147 }
148
149 #[inline]
153 pub fn len(&self) -> usize {
154 self.id_to_path.len()
155 }
156
157 #[inline]
159 pub fn is_empty(&self) -> bool {
160 self.id_to_path.is_empty()
161 }
162}
163
164impl Default for FileRegistry {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 fn test_path(relative: &str, crate_name: &str) -> WorkspaceFilePath {
175 WorkspaceFilePath::new_for_test(relative, "/project", crate_name)
176 }
177
178 #[test]
179 fn test_register_and_lookup() {
180 let mut registry = FileRegistry::new();
181 let path = test_path("src/lib.rs", "my-crate");
182
183 let id = registry.register(path.clone());
184
185 assert!(registry.contains(id));
186 assert_eq!(registry.lookup(&path), Some(id));
187 assert_eq!(registry.path(id), Some(&path));
188 assert_eq!(registry.crate_name(id), Some("my-crate"));
189 }
190
191 #[test]
192 fn test_register_duplicate() {
193 let mut registry = FileRegistry::new();
194 let path = test_path("src/lib.rs", "my-crate");
195
196 let id1 = registry.register(path.clone());
197 let id2 = registry.register(path);
198
199 assert_eq!(id1, id2);
200 }
201
202 #[test]
203 fn test_remove() {
204 let mut registry = FileRegistry::new();
205 let path = test_path("src/lib.rs", "my-crate");
206
207 let id = registry.register(path.clone());
208 assert!(registry.contains(id));
209
210 let removed = registry.remove(id);
211 assert!(removed.is_some());
212 assert!(!registry.contains(id));
213 assert!(registry.lookup(&path).is_none());
214 }
215
216 #[test]
217 fn test_update_path() {
218 let mut registry = FileRegistry::new();
219 let old_path = test_path("src/old.rs", "my-crate");
220 let new_path = test_path("src/new.rs", "my-crate");
221
222 let id = registry.register(old_path.clone());
223
224 let returned_old = registry.update_path(id, new_path.clone()).unwrap();
226 assert_eq!(returned_old, old_path);
227
228 assert!(registry.lookup(&old_path).is_none());
230 assert_eq!(registry.lookup(&new_path), Some(id));
231 assert_eq!(registry.path(id), Some(&new_path));
232 }
233
234 #[test]
235 fn test_iter_in_crate() {
236 let mut registry = FileRegistry::new();
237
238 registry.register(test_path("src/lib.rs", "crate-a"));
239 registry.register(test_path("src/foo.rs", "crate-a"));
240 registry.register(test_path("src/lib.rs", "crate-b"));
241
242 let crate_a_files: Vec<_> = registry.iter_in_crate("crate-a").collect();
243 assert_eq!(crate_a_files.len(), 2);
244
245 let crate_b_files: Vec<_> = registry.iter_in_crate("crate-b").collect();
246 assert_eq!(crate_b_files.len(), 1);
247 }
248}