use super::{
FileSpan, InvalidSymbolId, RegistrationError, RenameError, SymbolId, SymbolKind, SymbolPath,
SymbolRegistry, Visibility,
};
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum RegistryUpdate {
Add {
path: SymbolPath,
kind: SymbolKind,
span: FileSpan,
},
Remove {
id: SymbolId,
},
Rename {
id: SymbolId,
new_path: SymbolPath,
},
UpdateSpan {
id: SymbolId,
new_span: FileSpan,
},
UpdateVisibility {
id: SymbolId,
new_visibility: Visibility,
},
UpdateKind {
id: SymbolId,
new_kind: SymbolKind,
},
}
impl RegistryUpdate {
pub fn target_id(&self) -> Option<SymbolId> {
match self {
RegistryUpdate::Add { .. } => None,
RegistryUpdate::Remove { id }
| RegistryUpdate::Rename { id, .. }
| RegistryUpdate::UpdateSpan { id, .. }
| RegistryUpdate::UpdateVisibility { id, .. }
| RegistryUpdate::UpdateKind { id, .. } => Some(*id),
}
}
pub fn is_destructive(&self) -> bool {
matches!(
self,
RegistryUpdate::Remove { .. } | RegistryUpdate::Rename { .. }
)
}
}
#[derive(Debug, Clone, Default)]
pub struct RegistryUpdateBatch {
updates: Vec<RegistryUpdate>,
}
impl RegistryUpdateBatch {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
updates: Vec::with_capacity(capacity),
}
}
pub fn push(&mut self, update: RegistryUpdate) {
self.updates.push(update);
}
pub fn add_symbol(&mut self, path: SymbolPath, kind: SymbolKind, span: FileSpan) {
self.push(RegistryUpdate::Add { path, kind, span });
}
pub fn remove_symbol(&mut self, id: SymbolId) {
self.push(RegistryUpdate::Remove { id });
}
pub fn rename_symbol(&mut self, id: SymbolId, new_path: SymbolPath) {
self.push(RegistryUpdate::Rename { id, new_path });
}
pub fn update_span(&mut self, id: SymbolId, new_span: FileSpan) {
self.push(RegistryUpdate::UpdateSpan { id, new_span });
}
pub fn updates(&self) -> &[RegistryUpdate] {
&self.updates
}
pub fn into_updates(self) -> Vec<RegistryUpdate> {
self.updates
}
pub fn is_empty(&self) -> bool {
self.updates.is_empty()
}
pub fn len(&self) -> usize {
self.updates.len()
}
pub fn apply(self, registry: &mut SymbolRegistry) -> Result<usize, ApplyError> {
let mut applied = 0;
for update in self.updates {
update.apply(registry)?;
applied += 1;
}
Ok(applied)
}
}
#[derive(Debug, thiserror::Error)]
pub enum ApplyError {
#[error("registration failed: {0}")]
Registration(#[from] RegistrationError),
#[error("invalid symbol id: {0}")]
InvalidId(#[from] InvalidSymbolId),
#[error("rename failed: {0}")]
Rename(#[from] RenameError),
}
impl RegistryUpdate {
pub fn apply(self, registry: &mut SymbolRegistry) -> Result<(), ApplyError> {
match self {
RegistryUpdate::Add { path, kind, span } => {
let id = registry.register(path, kind)?;
registry.set_span(id, span)?;
}
RegistryUpdate::Remove { id } => {
let _ = registry.remove(id);
}
RegistryUpdate::Rename { id, new_path } => {
registry.rename(id, new_path)?;
}
RegistryUpdate::UpdateSpan { id, new_span } => {
registry.set_span(id, new_span)?;
}
RegistryUpdate::UpdateVisibility { id, new_visibility } => {
registry.set_visibility(id, new_visibility)?;
}
RegistryUpdate::UpdateKind { id, new_kind } => {
registry.set_kind(id, new_kind)?;
}
}
Ok(())
}
}
impl IntoIterator for RegistryUpdateBatch {
type Item = RegistryUpdate;
type IntoIter = std::vec::IntoIter<RegistryUpdate>;
fn into_iter(self) -> Self::IntoIter {
self.updates.into_iter()
}
}
impl<'a> IntoIterator for &'a RegistryUpdateBatch {
type Item = &'a RegistryUpdate;
type IntoIter = std::slice::Iter<'a, RegistryUpdate>;
fn into_iter(self) -> Self::IntoIter {
self.updates.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use ryo_symbol::WorkspaceFilePath;
fn test_span() -> FileSpan {
FileSpan::new(
WorkspaceFilePath::new_for_test("test/file.rs", "/", "test"),
0,
10,
)
}
#[test]
fn test_target_id() {
use slotmap::KeyData;
let id = SymbolId::from(KeyData::from_ffi(1));
let path = SymbolPath::parse("foo::bar").unwrap();
let add = RegistryUpdate::Add {
path: path.clone(),
kind: SymbolKind::Function,
span: test_span(),
};
assert!(add.target_id().is_none());
let remove = RegistryUpdate::Remove { id };
assert_eq!(remove.target_id(), Some(id));
let rename = RegistryUpdate::Rename { id, new_path: path };
assert_eq!(rename.target_id(), Some(id));
}
#[test]
fn test_is_destructive() {
use slotmap::KeyData;
let id = SymbolId::from(KeyData::from_ffi(1));
let path = SymbolPath::parse("foo::bar").unwrap();
assert!(!RegistryUpdate::Add {
path: path.clone(),
kind: SymbolKind::Function,
span: test_span(),
}
.is_destructive());
assert!(RegistryUpdate::Remove { id }.is_destructive());
assert!(RegistryUpdate::Rename { id, new_path: path }.is_destructive());
assert!(!RegistryUpdate::UpdateSpan {
id,
new_span: test_span()
}
.is_destructive());
}
#[test]
fn test_batch_builder() {
use slotmap::KeyData;
let id = SymbolId::from(KeyData::from_ffi(1));
let path = SymbolPath::parse("foo::bar").unwrap();
let mut batch = RegistryUpdateBatch::new();
assert!(batch.is_empty());
batch.add_symbol(path.clone(), SymbolKind::Function, test_span());
batch.remove_symbol(id);
assert_eq!(batch.len(), 2);
assert!(!batch.is_empty());
}
#[test]
fn test_batch_apply_add() {
let mut registry = SymbolRegistry::new();
let path = SymbolPath::parse("test::NewSymbol").unwrap();
let mut batch = RegistryUpdateBatch::new();
batch.add_symbol(path.clone(), SymbolKind::Function, test_span());
let applied = batch.apply(&mut registry).unwrap();
assert_eq!(applied, 1);
let id = registry.lookup(&path).expect("symbol should exist");
assert_eq!(registry.kind(id), Some(SymbolKind::Function));
assert!(registry.span(id).is_some());
}
#[test]
fn test_batch_apply_remove() {
let mut registry = SymbolRegistry::new();
let path = SymbolPath::parse("test::ToRemove").unwrap();
let id = registry.register(path.clone(), SymbolKind::Struct).unwrap();
let mut batch = RegistryUpdateBatch::new();
batch.remove_symbol(id);
let applied = batch.apply(&mut registry).unwrap();
assert_eq!(applied, 1);
assert!(registry.lookup(&path).is_none());
assert!(!registry.contains(id));
}
#[test]
fn test_batch_apply_rename() {
let mut registry = SymbolRegistry::new();
let old_path = SymbolPath::parse("test::OldName").unwrap();
let new_path = SymbolPath::parse("test::NewName").unwrap();
let id = registry
.register(old_path.clone(), SymbolKind::Struct)
.unwrap();
let mut batch = RegistryUpdateBatch::new();
batch.rename_symbol(id, new_path.clone());
let applied = batch.apply(&mut registry).unwrap();
assert_eq!(applied, 1);
assert!(registry.lookup(&old_path).is_none());
assert_eq!(registry.lookup(&new_path), Some(id));
}
#[test]
fn test_batch_apply_multiple() {
use super::Visibility;
let mut registry = SymbolRegistry::new();
let path1 = SymbolPath::parse("test::First").unwrap();
let path2 = SymbolPath::parse("test::Second").unwrap();
let id1 = registry
.register(path1.clone(), SymbolKind::Struct)
.unwrap();
let mut batch = RegistryUpdateBatch::new();
batch.add_symbol(path2.clone(), SymbolKind::Function, test_span());
batch.push(RegistryUpdate::UpdateVisibility {
id: id1,
new_visibility: Visibility::Public,
});
let applied = batch.apply(&mut registry).unwrap();
assert_eq!(applied, 2);
assert!(registry.lookup(&path2).is_some());
assert_eq!(registry.visibility(id1), Some(&Visibility::Public));
}
#[test]
fn test_batch_apply_empty() {
let mut registry = SymbolRegistry::new();
let batch = RegistryUpdateBatch::new();
let applied = batch.apply(&mut registry).unwrap();
assert_eq!(applied, 0);
}
}