use crate::symbol::{SymbolKind, SymbolTable};
use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug)]
pub struct SymbolDef {
pub name: String,
pub kind: SymbolKind,
pub uri: String,
pub start: usize,
pub end: usize,
}
#[derive(Default)]
pub struct WorkspaceIndex {
by_name: HashMap<String, Vec<SymbolDef>>,
by_uri: HashMap<String, HashSet<String>>,
}
impl WorkspaceIndex {
pub fn new() -> Self {
Self::default()
}
pub fn update_from_document(&mut self, uri: &str, _content: &str, symtab: &SymbolTable) {
self.remove_document(uri);
let mut names_in_file = HashSet::new();
for symbols in symtab.symbols.values() {
for symbol in symbols {
let name = symbol.name.clone();
names_in_file.insert(name.clone());
let def = SymbolDef {
name: symbol.name.clone(),
kind: symbol.kind,
uri: uri.to_string(),
start: symbol.location.start,
end: symbol.location.end,
};
self.by_name.entry(name).or_default().push(def);
}
}
self.by_uri.insert(uri.to_string(), names_in_file);
}
pub fn remove_document(&mut self, uri: &str) {
if let Some(names) = self.by_uri.remove(uri) {
for name in names {
if let Some(defs) = self.by_name.get_mut(&name) {
defs.retain(|d| d.uri != uri);
if defs.is_empty() {
self.by_name.remove(&name);
}
}
}
}
}
pub fn find_defs(&self, name: &str) -> &[SymbolDef] {
static EMPTY: Vec<SymbolDef> = Vec::new();
self.by_name.get(name).map(|v| v.as_slice()).unwrap_or(&EMPTY[..])
}
pub fn find_refs(&self, name: &str) -> Vec<SymbolDef> {
self.find_defs(name).to_vec()
}
pub fn search_symbols(&self, query: &str) -> Vec<SymbolDef> {
let query_lower = query.to_lowercase();
let mut results = Vec::new();
for (name, defs) in &self.by_name {
if name.to_lowercase().contains(&query_lower) {
results.extend(defs.clone());
}
}
results
}
pub fn symbol_count(&self) -> usize {
self.by_name.values().map(|v| v.len()).sum()
}
pub fn file_count(&self) -> usize {
self.by_uri.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SourceLocation;
use crate::symbol::Symbol;
#[test]
fn test_workspace_index() {
let mut index = WorkspaceIndex::new();
let mut symtab = SymbolTable::new();
let symbol = Symbol {
name: "test_func".to_string(),
qualified_name: "main::test_func".to_string(),
kind: SymbolKind::Subroutine,
location: SourceLocation { start: 0, end: 10 },
scope_id: 0,
declaration: Some("sub".to_string()),
documentation: None,
attributes: Vec::new(),
};
symtab.symbols.entry("test_func".to_string()).or_default().push(symbol);
index.update_from_document("file:///test.pl", "", &symtab);
let defs = index.find_defs("test_func");
assert_eq!(defs.len(), 1);
assert_eq!(defs[0].name, "test_func");
assert_eq!(defs[0].uri, "file:///test.pl");
index.remove_document("file:///test.pl");
assert_eq!(index.find_defs("test_func").len(), 0);
}
}