perl_semantic_analyzer/analysis/
index.rs1use crate::symbol::{SymbolKind, SymbolTable};
7use std::collections::{HashMap, HashSet};
8
9#[derive(Clone, Debug)]
11pub struct SymbolDef {
12 pub name: String,
14 pub kind: SymbolKind,
16 pub uri: String,
18 pub start: usize,
20 pub end: usize,
22}
23
24#[derive(Default)]
26pub struct WorkspaceIndex {
27 by_name: HashMap<String, Vec<SymbolDef>>,
29 by_uri: HashMap<String, HashSet<String>>,
31}
32
33impl WorkspaceIndex {
34 pub fn new() -> Self {
36 Self::default()
37 }
38
39 pub fn update_from_document(&mut self, uri: &str, _content: &str, symtab: &SymbolTable) {
41 self.remove_document(uri);
43
44 let mut names_in_file = HashSet::new();
46
47 for symbols in symtab.symbols.values() {
49 for symbol in symbols {
50 let name = symbol.name.clone();
51 names_in_file.insert(name.clone());
52
53 let def = SymbolDef {
54 name: symbol.name.clone(),
55 kind: symbol.kind,
56 uri: uri.to_string(),
57 start: symbol.location.start,
58 end: symbol.location.end,
59 };
60
61 self.by_name.entry(name).or_default().push(def);
62 }
63 }
64
65 self.by_uri.insert(uri.to_string(), names_in_file);
67 }
68
69 pub fn remove_document(&mut self, uri: &str) {
71 if let Some(names) = self.by_uri.remove(uri) {
72 for name in names {
73 if let Some(defs) = self.by_name.get_mut(&name) {
74 defs.retain(|d| d.uri != uri);
75 if defs.is_empty() {
76 self.by_name.remove(&name);
77 }
78 }
79 }
80 }
81 }
82
83 pub fn find_defs(&self, name: &str) -> &[SymbolDef] {
85 static EMPTY: Vec<SymbolDef> = Vec::new();
86 self.by_name.get(name).map(|v| v.as_slice()).unwrap_or(&EMPTY[..])
87 }
88
89 pub fn find_refs(&self, name: &str) -> Vec<SymbolDef> {
92 self.find_defs(name).to_vec()
95 }
96
97 pub fn search_symbols(&self, query: &str) -> Vec<SymbolDef> {
99 let query_lower = query.to_lowercase();
100 let mut results = Vec::new();
101
102 for (name, defs) in &self.by_name {
103 if name.to_lowercase().contains(&query_lower) {
104 results.extend(defs.clone());
105 }
106 }
107
108 results
109 }
110
111 pub fn symbol_count(&self) -> usize {
113 self.by_name.values().map(|v| v.len()).sum()
114 }
115
116 pub fn file_count(&self) -> usize {
118 self.by_uri.len()
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::SourceLocation;
126 use crate::symbol::Symbol;
127
128 #[test]
129 fn test_workspace_index() {
130 let mut index = WorkspaceIndex::new();
131
132 let mut symtab = SymbolTable::new();
134
135 let symbol = Symbol {
137 name: "test_func".to_string(),
138 qualified_name: "main::test_func".to_string(),
139 kind: SymbolKind::Subroutine,
140 location: SourceLocation { start: 0, end: 10 },
141 scope_id: 0,
142 declaration: Some("sub".to_string()),
143 documentation: None,
144 attributes: Vec::new(),
145 };
146
147 symtab.symbols.entry("test_func".to_string()).or_default().push(symbol);
148
149 index.update_from_document("file:///test.pl", "", &symtab);
151
152 let defs = index.find_defs("test_func");
154 assert_eq!(defs.len(), 1);
155 assert_eq!(defs[0].name, "test_func");
156 assert_eq!(defs[0].uri, "file:///test.pl");
157
158 index.remove_document("file:///test.pl");
160 assert_eq!(index.find_defs("test_func").len(), 0);
161 }
162}