use super::FileId;
use ryo_symbol::WorkspaceFilePath;
use serde::Serialize;
use slotmap::SlotMap;
use std::collections::HashMap;
#[derive(Debug, Clone, thiserror::Error)]
#[error("invalid file id: {0:?}")]
pub struct InvalidFileId(pub FileId);
#[derive(Clone, Serialize)]
pub struct FileRegistry {
id_to_path: SlotMap<FileId, WorkspaceFilePath>,
path_to_id: HashMap<WorkspaceFilePath, FileId>,
}
impl FileRegistry {
pub fn new() -> Self {
Self {
id_to_path: SlotMap::with_key(),
path_to_id: HashMap::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
id_to_path: SlotMap::with_capacity_and_key(capacity),
path_to_id: HashMap::with_capacity(capacity),
}
}
pub fn register(&mut self, path: WorkspaceFilePath) -> FileId {
if let Some(&existing_id) = self.path_to_id.get(&path) {
return existing_id;
}
let id = self.id_to_path.insert(path.clone());
self.path_to_id.insert(path, id);
id
}
pub fn remove(&mut self, id: FileId) -> Option<WorkspaceFilePath> {
let path = self.id_to_path.remove(id)?;
self.path_to_id.remove(&path);
Some(path)
}
#[inline]
pub fn lookup(&self, path: &WorkspaceFilePath) -> Option<FileId> {
self.path_to_id.get(path).copied()
}
#[inline]
pub fn path(&self, id: FileId) -> Option<&WorkspaceFilePath> {
self.id_to_path.get(id)
}
#[inline]
pub fn crate_name(&self, id: FileId) -> Option<&str> {
self.id_to_path.get(id).map(|p| p.crate_name().as_str())
}
#[inline]
pub fn contains(&self, id: FileId) -> bool {
self.id_to_path.contains_key(id)
}
pub fn update_path(
&mut self,
id: FileId,
new_path: WorkspaceFilePath,
) -> Result<WorkspaceFilePath, InvalidFileId> {
let old_path = self.id_to_path.get(id).ok_or(InvalidFileId(id))?.clone();
self.path_to_id.remove(&old_path);
self.path_to_id.insert(new_path.clone(), id);
self.id_to_path[id] = new_path;
Ok(old_path)
}
pub fn iter(&self) -> impl Iterator<Item = (FileId, &WorkspaceFilePath)> {
self.id_to_path.iter()
}
pub fn iter_in_crate<'a>(&'a self, crate_name: &'a str) -> impl Iterator<Item = FileId> + 'a {
self.id_to_path
.iter()
.filter(move |(_, path)| path.crate_name().as_str() == crate_name)
.map(|(id, _)| id)
}
#[inline]
pub fn len(&self) -> usize {
self.id_to_path.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.id_to_path.is_empty()
}
}
impl Default for FileRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_path(relative: &str, crate_name: &str) -> WorkspaceFilePath {
WorkspaceFilePath::new_for_test(relative, "/project", crate_name)
}
#[test]
fn test_register_and_lookup() {
let mut registry = FileRegistry::new();
let path = test_path("src/lib.rs", "my-crate");
let id = registry.register(path.clone());
assert!(registry.contains(id));
assert_eq!(registry.lookup(&path), Some(id));
assert_eq!(registry.path(id), Some(&path));
assert_eq!(registry.crate_name(id), Some("my-crate"));
}
#[test]
fn test_register_duplicate() {
let mut registry = FileRegistry::new();
let path = test_path("src/lib.rs", "my-crate");
let id1 = registry.register(path.clone());
let id2 = registry.register(path);
assert_eq!(id1, id2);
}
#[test]
fn test_remove() {
let mut registry = FileRegistry::new();
let path = test_path("src/lib.rs", "my-crate");
let id = registry.register(path.clone());
assert!(registry.contains(id));
let removed = registry.remove(id);
assert!(removed.is_some());
assert!(!registry.contains(id));
assert!(registry.lookup(&path).is_none());
}
#[test]
fn test_update_path() {
let mut registry = FileRegistry::new();
let old_path = test_path("src/old.rs", "my-crate");
let new_path = test_path("src/new.rs", "my-crate");
let id = registry.register(old_path.clone());
let returned_old = registry.update_path(id, new_path.clone()).unwrap();
assert_eq!(returned_old, old_path);
assert!(registry.lookup(&old_path).is_none());
assert_eq!(registry.lookup(&new_path), Some(id));
assert_eq!(registry.path(id), Some(&new_path));
}
#[test]
fn test_iter_in_crate() {
let mut registry = FileRegistry::new();
registry.register(test_path("src/lib.rs", "crate-a"));
registry.register(test_path("src/foo.rs", "crate-a"));
registry.register(test_path("src/lib.rs", "crate-b"));
let crate_a_files: Vec<_> = registry.iter_in_crate("crate-a").collect();
assert_eq!(crate_a_files.len(), 2);
let crate_b_files: Vec<_> = registry.iter_in_crate("crate-b").collect();
assert_eq!(crate_b_files.len(), 1);
}
}