1use std::{collections::HashMap, path::PathBuf, sync::Arc};
7
8use anyhow::{Context, Result};
9use serde::{Deserialize, Serialize};
10use tokio::sync::RwLock;
11use crate::dev_log;
12
13use crate::Host::{
14 ActivationResult,
15 ExtensionManager::{ExtensionManagerImpl, ExtensionState},
16 HostConfig,
17};
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub enum ActivationEvent {
22 Startup,
24 Command(String),
26 Language(String),
28 WorkspaceContains(String),
30 OnView(String),
32 OnUri(String),
34 OnFiles(String),
36 Custom(String),
38 Star,
40}
41
42impl ActivationEvent {
43 pub fn from_str(event_str:&str) -> Result<Self> {
45 match event_str {
46 "*" => Ok(Self::Star),
47 e if e.starts_with("onCommand:") => Ok(Self::Command(e.trim_start_matches("onCommand:").to_string())),
48 e if e.starts_with("onLanguage:") => Ok(Self::Language(e.trim_start_matches("onLanguage:").to_string())),
49 e if e.starts_with("workspaceContains:") => {
50 Ok(Self::WorkspaceContains(e.trim_start_matches("workspaceContains:").to_string()))
51 },
52 e if e.starts_with("onView:") => Ok(Self::OnView(e.trim_start_matches("onView:").to_string())),
53 e if e.starts_with("onUri:") => Ok(Self::OnUri(e.trim_start_matches("onUri:").to_string())),
54 e if e.starts_with("onFiles:") => Ok(Self::OnFiles(e.trim_start_matches("onFiles:").to_string())),
55 _ => Ok(Self::Custom(event_str.to_string())),
56 }
57 }
58
59 pub fn to_string(&self) -> String {
61 match self {
62 Self::Startup => "onStartup".to_string(),
63 Self::Star => "*".to_string(),
64 Self::Command(cmd) => format!("onCommand:{}", cmd),
65 Self::Language(lang) => format!("onLanguage:{}", lang),
66 Self::WorkspaceContains(pattern) => format!("workspaceContains:{}", pattern),
67 Self::OnView(view) => format!("onView:{}", view),
68 Self::OnUri(uri) => format!("onUri:{}", uri),
69 Self::OnFiles(pattern) => format!("onFiles:{}", pattern),
70 Self::Custom(s) => s.clone(),
71 }
72 }
73}
74
75impl std::str::FromStr for ActivationEvent {
76 type Err = anyhow::Error;
77
78 fn from_str(s:&str) -> Result<Self, Self::Err> { Self::from_str(s) }
79}
80
81pub struct ActivationEngine {
83 extension_manager:Arc<ExtensionManagerImpl>,
85 #[allow(dead_code)]
87 config:HostConfig,
88 event_handlers:Arc<RwLock<HashMap<String, ActivationHandler>>>,
90 activation_history:Arc<RwLock<Vec<ActivationRecord>>>,
92}
93
94#[derive(Debug, Clone)]
96struct ActivationHandler {
97 #[allow(dead_code)]
99 extension_id:String,
100 events:Vec<ActivationEvent>,
102 #[allow(dead_code)]
104 activation_function:String,
105 is_active:bool,
107 #[allow(dead_code)]
109 last_activation:Option<u64>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ActivationRecord {
115 pub extension_id:String,
117 pub events:Vec<String>,
119 pub timestamp:u64,
121 pub duration_ms:u64,
123 pub success:bool,
125 pub error:Option<String>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ActivationContext {
132 pub workspace_path:Option<PathBuf>,
134 pub current_file:Option<PathBuf>,
136 pub language_id:Option<String>,
138 pub active_editor:bool,
140 pub environment:HashMap<String, String>,
142 pub additional_data:serde_json::Value,
144}
145
146impl Default for ActivationContext {
147 fn default() -> Self {
148 Self {
149 workspace_path:None,
150 current_file:None,
151 language_id:None,
152 active_editor:false,
153 environment:HashMap::new(),
154 additional_data:serde_json::Value::Null,
155 }
156 }
157}
158
159impl ActivationEngine {
160 pub fn new(extension_manager:Arc<ExtensionManagerImpl>, config:HostConfig) -> Self {
162 Self {
163 extension_manager,
164 config,
165 event_handlers:Arc::new(RwLock::new(HashMap::new())),
166 activation_history:Arc::new(RwLock::new(Vec::new())),
167 }
168 }
169
170 pub async fn activate(&self, extension_id:&str) -> Result<ActivationResult> {
172 dev_log!("extensions", "Activating extension: {}", extension_id);
173
174 let start = std::time::Instant::now();
175
176 let extension_info = self
178 .extension_manager
179 .get_extension(extension_id)
180 .await
181 .ok_or_else(|| anyhow::anyhow!("Extension not found: {}", extension_id))?;
182
183 let handlers = self.event_handlers.read().await;
185 if let Some(handler) = handlers.get(extension_id) {
186 if handler.is_active {
187 dev_log!("extensions", "warn: extension already active: {}", extension_id);
188 return Ok(ActivationResult {
189 extension_id:extension_id.to_string(),
190 success:true,
191 time_ms:0,
192 error:None,
193 contributes:Vec::new(),
194 });
195 }
196 }
197 drop(handlers);
198
199 let activation_events:Result<Vec<ActivationEvent>> = extension_info
201 .activation_events
202 .iter()
203 .map(|e| ActivationEvent::from_str(e))
204 .collect();
205 let activation_events = activation_events.with_context(|| "Failed to parse activation events")?;
206
207 let context = ActivationContext::default();
209
210 let activation_result = self
213 .perform_activation(extension_id, &context)
214 .await
215 .context("Activation failed")?;
216
217 let elapsed_ms = start.elapsed().as_millis() as u64;
218
219 let record = ActivationRecord {
221 extension_id:extension_id.to_string(),
222 events:extension_info.activation_events.clone(),
223 timestamp:std::time::SystemTime::now()
224 .duration_since(std::time::UNIX_EPOCH)
225 .map(|d| d.as_secs())
226 .unwrap_or(0),
227 duration_ms:elapsed_ms,
228 success:activation_result.success,
229 error:None,
230 };
231
232 let activation_timestamp = record.timestamp;
234
235 self.activation_history.write().await.push(record);
236
237 self.extension_manager
239 .update_state(extension_id, ExtensionState::Activated)
240 .await?;
241
242 let mut handlers = self.event_handlers.write().await;
244 handlers.insert(
245 extension_id.to_string(),
246 ActivationHandler {
247 extension_id:extension_id.to_string(),
248 events:activation_events,
249 activation_function:"activate".to_string(),
250 is_active:true,
251 last_activation:Some(activation_timestamp),
252 },
253 );
254
255 dev_log!("extensions", "Extension activated in {}ms: {}", elapsed_ms, extension_id);
256
257 Ok(ActivationResult {
258 extension_id:extension_id.to_string(),
259 success:true,
260 time_ms:elapsed_ms,
261 error:None,
262 contributes:extension_info.capabilities.clone(),
263 })
264 }
265
266 pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
268 dev_log!("extensions", "Deactivating extension: {}", extension_id);
269
270 let mut handlers = self.event_handlers.write().await;
272 if let Some(mut handler) = handlers.remove(extension_id) {
273 handler.is_active = false;
274 }
275
276 self.extension_manager
278 .update_state(extension_id, ExtensionState::Deactivated)
279 .await?;
280
281 dev_log!("extensions", "Extension deactivated: {}", extension_id);
282
283 Ok(())
284 }
285
286 pub async fn trigger_activation(&self, event:&str, _context:&ActivationContext) -> Result<Vec<ActivationResult>> {
288 dev_log!("extensions", "Triggering activation for event: {}", event);
289
290 let activation_event = ActivationEvent::from_str(event)?;
291 let handlers = self.event_handlers.read().await;
292
293 let mut results = Vec::new();
294
295 for (extension_id, handler) in handlers.iter() {
296 if handler.is_active {
298 continue; }
300
301 if self.should_activate(&activation_event, &handler.events) {
302 dev_log!("extensions", "Activating extension {} for event: {}", extension_id, event);
303 match self.activate(extension_id).await {
304 Ok(result) => results.push(result),
305 Err(e) => {
306 dev_log!("extensions", "warn: failed to activate extension {} for event {}: {}", extension_id, event, e);
307 },
308 }
309 }
310 }
311
312 Ok(results)
313 }
314
315 fn should_activate(&self, activation_event:&ActivationEvent, events:&[ActivationEvent]) -> bool {
317 events.iter().any(|e| {
318 match (e, activation_event) {
319 (ActivationEvent::Star, _) => true,
320 (ActivationEvent::Custom(pattern), _) => {
321 WildMatch::new(pattern).matches(activation_event.to_string().as_str())
322 },
323 _ => e == activation_event,
324 }
325 })
326 }
327
328 async fn perform_activation(&self, extension_id:&str, _context:&ActivationContext) -> Result<ActivationResult> {
331 dev_log!("extensions", "Performing activation for extension: {}", extension_id);
338
339 Ok(ActivationResult {
341 extension_id:extension_id.to_string(),
342 success:true,
343 time_ms:0,
344 error:None,
345 contributes:Vec::new(),
346 })
347 }
348
349 pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
351
352 pub async fn get_activation_history_for_extension(&self, extension_id:&str) -> Vec<ActivationRecord> {
354 self.activation_history
355 .read()
356 .await
357 .iter()
358 .filter(|r| r.extension_id == extension_id)
359 .cloned()
360 .collect()
361 }
362}
363
364struct WildMatch {
366 pattern:String,
367}
368
369impl WildMatch {
370 fn new(pattern:&str) -> Self { Self { pattern:pattern.to_lowercase() } }
371
372 fn matches(&self, text:&str) -> bool {
373 let text = text.to_lowercase();
374
375 if self.pattern == "*" {
377 return true;
378 }
379
380 if self.pattern.starts_with('*') {
382 let suffix = &self.pattern[1..];
383 return text.ends_with(suffix);
384 }
385
386 if self.pattern.ends_with('*') {
388 let prefix = &self.pattern[..self.pattern.len() - 1];
389 return text.starts_with(prefix);
390 }
391
392 self.pattern == text
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_activation_event_parsing() {
403 let event = ActivationEvent::from_str("*").unwrap();
404 assert_eq!(event, ActivationEvent::Star);
405
406 let event = ActivationEvent::from_str("onCommand:test.command").unwrap();
407 assert_eq!(event, ActivationEvent::Command("test.command".to_string()));
408
409 let event = ActivationEvent::from_str("onLanguage:rust").unwrap();
410 assert_eq!(event, ActivationEvent::Language("rust".to_string()));
411 }
412
413 #[test]
414 fn test_activation_event_to_string() {
415 assert_eq!(ActivationEvent::Star.to_string(), "*");
416 assert_eq!(ActivationEvent::Command("test".to_string()).to_string(), "onCommand:test");
417 assert_eq!(ActivationEvent::Language("rust".to_string()).to_string(), "onLanguage:rust");
418 }
419
420 #[test]
421 fn test_activation_context_default() {
422 let context = ActivationContext::default();
423 assert!(context.workspace_path.is_none());
424 assert!(context.current_file.is_none());
425 assert!(!context.active_editor);
426 }
427
428 #[test]
429 fn test_wildcard_matching() {
430 let matcher = WildMatch::new("*");
431 assert!(matcher.matches("anything"));
432
433 let matcher = WildMatch::new("prefix*");
434 assert!(matcher.matches("prefix_suffix"));
435 assert!(!matcher.matches("noprefix_suffix"));
436
437 let matcher = WildMatch::new("*suffix");
438 assert!(matcher.matches("prefix_suffix"));
439 assert!(!matcher.matches("prefix_suffix_not"));
440 }
441}