1use crate::Result;
7use forge_core::{
8 types::SymbolId,
9 Forge,
10};
11use std::collections::HashMap;
12use std::sync::Arc;
13
14#[derive(Clone)]
18pub struct Observer {
19 forge: Arc<Forge>,
21 cache: Arc<tokio::sync::RwLock<HashMap<String, Observation>>>,
23}
24
25impl Observer {
26 pub fn new(forge: Forge) -> Self {
28 Self {
29 forge: Arc::new(forge),
30 cache: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
31 }
32 }
33
34 pub async fn gather(&self, query: &str) -> Result<Observation> {
36 {
38 let cache = self.cache.read().await;
39 if let Some(cached) = cache.get(query) {
40 return Ok(cached.clone());
41 }
42 }
43
44 let symbols = self.gather_symbols(query).await?;
46
47 let observation = crate::Observation {
48 query: query.to_string(),
49 symbols,
50 };
51
52 {
54 let mut cache = self.cache.write().await;
55 cache.insert(query.to_string(), observation.clone());
56 }
57
58 Ok(observation)
59 }
60
61 async fn gather_symbols(&self, query: &str) -> Result<Vec<ObservedSymbol>> {
63 let _graph = self.forge.as_ref();
64 let mut symbols = Vec::new();
65
66 let query_lower = query.to_lowercase();
68
69 if query_lower.contains("find") && query_lower.contains("named") {
70 if let Some(pos) = query_lower.find("named") {
72 let remaining = &query[pos + 6..];
73 let name = remaining.trim().trim_end_matches('?').trim().to_string();
74 if !name.is_empty() {
75 symbols.push(ObservedSymbol {
78 id: SymbolId(0),
79 name: name.clone(),
80 kind: forge_core::types::SymbolKind::Function,
81 location: forge_core::types::Location {
82 file_path: std::path::PathBuf::from("<unknown>"),
83 byte_start: 0,
84 byte_end: 0,
85 line_number: 0,
86 },
87 });
88 }
89 }
90 }
91
92 Ok(symbols)
93 }
94
95 pub async fn clear_cache(&self) {
97 let mut cache = self.cache.write().await;
98 cache.clear();
99 }
100}
101
102#[derive(Clone, Debug)]
106pub struct Observation {
107 pub query: String,
109 pub symbols: Vec<ObservedSymbol>,
111}
112
113#[derive(Clone, Debug)]
115pub struct ObservedSymbol {
116 pub id: SymbolId,
118 pub name: String,
120 pub kind: forge_core::types::SymbolKind,
122 pub location: forge_core::types::Location,
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use tempfile::TempDir;
130 use forge_core::Forge;
131
132 async fn create_test_observer() -> (Observer, TempDir) {
133 let temp_dir = TempDir::new().unwrap();
134 let forge = Forge::open(temp_dir.path()).await.unwrap();
135 let observer = Observer::new(forge);
136 (observer, temp_dir)
137 }
138
139 #[tokio::test]
140 async fn test_observer_creation() {
141 let temp_dir = TempDir::new().unwrap();
142 let forge = Forge::open(temp_dir.path()).await.unwrap();
143 let observer = Observer::new(forge);
144
145 let cache = observer.cache.read().await;
147 assert!(cache.is_empty());
148 }
149
150 #[tokio::test]
151 async fn test_observation_caching() {
152 let (observer, _temp_dir) = create_test_observer().await;
153
154 let result1 = observer.gather("test query").await;
156 assert!(result1.is_ok());
157
158 let result2 = observer.gather("test query").await;
160 assert!(result2.is_ok());
161
162 assert_eq!(result1.unwrap().query, result2.unwrap().query);
164 }
165
166 #[tokio::test]
167 async fn test_clear_cache() {
168 let (observer, _temp_dir) = create_test_observer().await;
169
170 let _ = observer.gather("test query").await;
172
173 observer.clear_cache().await;
175
176 let cache = observer.cache.read().await;
178 assert!(cache.is_empty());
179 }
180}