1use std::{collections::HashMap, path::PathBuf, sync::Arc};
7
8use anyhow::{Context, Result};
9use serde::{Deserialize, Serialize};
10use tokio::sync::RwLock;
11
12use crate::{
13 Host::{
14 ActivationResult,
15 ExtensionManager::{ExtensionManagerImpl, ExtensionState},
16 HostConfig,
17 },
18 dev_log,
19};
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub enum ActivationEvent {
24 Startup,
26
27 Command(String),
29
30 Language(String),
32
33 WorkspaceContains(String),
35
36 OnView(String),
38
39 OnUri(String),
41
42 OnFiles(String),
44
45 Custom(String),
47
48 Star,
50}
51
52impl ActivationEvent {
53 pub fn from_str(event_str:&str) -> Result<Self> {
55 match event_str {
56 "*" => Ok(Self::Star),
57
58 e if e.starts_with("onCommand:") => Ok(Self::Command(e.trim_start_matches("onCommand:").to_string())),
59
60 e if e.starts_with("onLanguage:") => Ok(Self::Language(e.trim_start_matches("onLanguage:").to_string())),
61
62 e if e.starts_with("workspaceContains:") => {
63 Ok(Self::WorkspaceContains(e.trim_start_matches("workspaceContains:").to_string()))
64 },
65
66 e if e.starts_with("onView:") => Ok(Self::OnView(e.trim_start_matches("onView:").to_string())),
67
68 e if e.starts_with("onUri:") => Ok(Self::OnUri(e.trim_start_matches("onUri:").to_string())),
69
70 e if e.starts_with("onFiles:") => Ok(Self::OnFiles(e.trim_start_matches("onFiles:").to_string())),
71
72 _ => Ok(Self::Custom(event_str.to_string())),
73 }
74 }
75
76 pub fn to_string(&self) -> String {
78 match self {
79 Self::Startup => "onStartup".to_string(),
80
81 Self::Star => "*".to_string(),
82
83 Self::Command(cmd) => format!("onCommand:{}", cmd),
84
85 Self::Language(lang) => format!("onLanguage:{}", lang),
86
87 Self::WorkspaceContains(pattern) => format!("workspaceContains:{}", pattern),
88
89 Self::OnView(view) => format!("onView:{}", view),
90
91 Self::OnUri(uri) => format!("onUri:{}", uri),
92
93 Self::OnFiles(pattern) => format!("onFiles:{}", pattern),
94
95 Self::Custom(s) => s.clone(),
96 }
97 }
98}
99
100impl std::str::FromStr for ActivationEvent {
101 type Err = anyhow::Error;
102
103 fn from_str(s:&str) -> Result<Self, Self::Err> { Self::from_str(s) }
104}
105
106pub struct ActivationEngine {
108 extension_manager:Arc<ExtensionManagerImpl>,
110
111 #[allow(dead_code)]
113 config:HostConfig,
114
115 event_handlers:Arc<RwLock<HashMap<String, ActivationHandler>>>,
117
118 activation_history:Arc<RwLock<Vec<ActivationRecord>>>,
120}
121
122#[derive(Debug, Clone)]
124struct ActivationHandler {
125 #[allow(dead_code)]
127 extension_id:String,
128
129 events:Vec<ActivationEvent>,
131
132 #[allow(dead_code)]
134 activation_function:String,
135
136 is_active:bool,
138
139 #[allow(dead_code)]
141 last_activation:Option<u64>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct ActivationRecord {
147 pub extension_id:String,
149
150 pub events:Vec<String>,
152
153 pub timestamp:u64,
155
156 pub duration_ms:u64,
158
159 pub success:bool,
161
162 pub error:Option<String>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct ActivationContext {
169 pub workspace_path:Option<PathBuf>,
171
172 pub current_file:Option<PathBuf>,
174
175 pub language_id:Option<String>,
177
178 pub active_editor:bool,
180
181 pub environment:HashMap<String, String>,
183
184 pub additional_data:serde_json::Value,
186}
187
188impl Default for ActivationContext {
189 fn default() -> Self {
190 Self {
191 workspace_path:None,
192
193 current_file:None,
194
195 language_id:None,
196
197 active_editor:false,
198
199 environment:HashMap::new(),
200
201 additional_data:serde_json::Value::Null,
202 }
203 }
204}
205
206impl ActivationEngine {
207 pub fn new(extension_manager:Arc<ExtensionManagerImpl>, config:HostConfig) -> Self {
209 Self {
210 extension_manager,
211
212 config,
213
214 event_handlers:Arc::new(RwLock::new(HashMap::new())),
215
216 activation_history:Arc::new(RwLock::new(Vec::new())),
217 }
218 }
219
220 pub async fn activate(&self, extension_id:&str) -> Result<ActivationResult> {
222 dev_log!("extensions", "Activating extension: {}", extension_id);
223
224 let start = std::time::Instant::now();
225
226 let extension_info = self
228 .extension_manager
229 .get_extension(extension_id)
230 .await
231 .ok_or_else(|| anyhow::anyhow!("Extension not found: {}", extension_id))?;
232
233 let handlers = self.event_handlers.read().await;
235
236 if let Some(handler) = handlers.get(extension_id) {
237 if handler.is_active {
238 dev_log!("extensions", "warn: extension already active: {}", extension_id);
239
240 return Ok(ActivationResult {
241 extension_id:extension_id.to_string(),
242 success:true,
243 time_ms:0,
244 error:None,
245 contributes:Vec::new(),
246 });
247 }
248 }
249
250 drop(handlers);
251
252 let activation_events:Result<Vec<ActivationEvent>> = extension_info
254 .activation_events
255 .iter()
256 .map(|e| ActivationEvent::from_str(e))
257 .collect();
258
259 let activation_events = activation_events.with_context(|| "Failed to parse activation events")?;
260
261 let context = ActivationContext::default();
263
264 let activation_result = self
267 .perform_activation(extension_id, &context)
268 .await
269 .context("Activation failed")?;
270
271 let elapsed_ms = start.elapsed().as_millis() as u64;
272
273 let record = ActivationRecord {
275 extension_id:extension_id.to_string(),
276
277 events:extension_info.activation_events.clone(),
278
279 timestamp:std::time::SystemTime::now()
280 .duration_since(std::time::UNIX_EPOCH)
281 .map(|d| d.as_secs())
282 .unwrap_or(0),
283
284 duration_ms:elapsed_ms,
285
286 success:activation_result.success,
287
288 error:None,
289 };
290
291 let activation_timestamp = record.timestamp;
293
294 self.activation_history.write().await.push(record);
295
296 self.extension_manager
298 .update_state(extension_id, ExtensionState::Activated)
299 .await?;
300
301 let mut handlers = self.event_handlers.write().await;
303
304 handlers.insert(
305 extension_id.to_string(),
306 ActivationHandler {
307 extension_id:extension_id.to_string(),
308 events:activation_events,
309 activation_function:"activate".to_string(),
310 is_active:true,
311 last_activation:Some(activation_timestamp),
312 },
313 );
314
315 dev_log!("extensions", "Extension activated in {}ms: {}", elapsed_ms, extension_id);
316
317 Ok(ActivationResult {
318 extension_id:extension_id.to_string(),
319 success:true,
320 time_ms:elapsed_ms,
321 error:None,
322 contributes:extension_info.capabilities.clone(),
323 })
324 }
325
326 pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
328 dev_log!("extensions", "Deactivating extension: {}", extension_id);
329
330 let mut handlers = self.event_handlers.write().await;
332
333 if let Some(mut handler) = handlers.remove(extension_id) {
334 handler.is_active = false;
335 }
336
337 self.extension_manager
339 .update_state(extension_id, ExtensionState::Deactivated)
340 .await?;
341
342 dev_log!("extensions", "Extension deactivated: {}", extension_id);
343
344 Ok(())
345 }
346
347 pub async fn trigger_activation(&self, event:&str, _context:&ActivationContext) -> Result<Vec<ActivationResult>> {
349 dev_log!("extensions", "Triggering activation for event: {}", event);
350
351 let activation_event = ActivationEvent::from_str(event)?;
352
353 let handlers = self.event_handlers.read().await;
354
355 let mut results = Vec::new();
356
357 for (extension_id, handler) in handlers.iter() {
358 if handler.is_active {
360 continue; }
362
363 if self.should_activate(&activation_event, &handler.events) {
364 dev_log!("extensions", "Activating extension {} for event: {}", extension_id, event);
365
366 match self.activate(extension_id).await {
367 Ok(result) => results.push(result),
368
369 Err(e) => {
370 dev_log!(
371 "extensions",
372 "warn: failed to activate extension {} for event {}: {}",
373 extension_id,
374 event,
375 e
376 );
377 },
378 }
379 }
380 }
381
382 Ok(results)
383 }
384
385 fn should_activate(&self, activation_event:&ActivationEvent, events:&[ActivationEvent]) -> bool {
387 events.iter().any(|e| {
388 match (e, activation_event) {
389 (ActivationEvent::Star, _) => true,
390 (ActivationEvent::Custom(pattern), _) => {
391 WildMatch::new(pattern).matches(activation_event.to_string().as_str())
392 },
393 _ => e == activation_event,
394 }
395 })
396 }
397
398 async fn perform_activation(&self, extension_id:&str, _context:&ActivationContext) -> Result<ActivationResult> {
401 dev_log!("extensions", "Performing activation for extension: {}", extension_id);
408
409 Ok(ActivationResult {
411 extension_id:extension_id.to_string(),
412 success:true,
413 time_ms:0,
414 error:None,
415 contributes:Vec::new(),
416 })
417 }
418
419 pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
421
422 pub async fn get_activation_history_for_extension(&self, extension_id:&str) -> Vec<ActivationRecord> {
424 self.activation_history
425 .read()
426 .await
427 .iter()
428 .filter(|r| r.extension_id == extension_id)
429 .cloned()
430 .collect()
431 }
432}
433
434struct WildMatch {
436 pattern:String,
437}
438
439impl WildMatch {
440 fn new(pattern:&str) -> Self { Self { pattern:pattern.to_lowercase() } }
441
442 fn matches(&self, text:&str) -> bool {
443 let text = text.to_lowercase();
444
445 if self.pattern == "*" {
447 return true;
448 }
449
450 if self.pattern.starts_with('*') {
452 let suffix = &self.pattern[1..];
453
454 return text.ends_with(suffix);
455 }
456
457 if self.pattern.ends_with('*') {
459 let prefix = &self.pattern[..self.pattern.len() - 1];
460
461 return text.starts_with(prefix);
462 }
463
464 self.pattern == text
466 }
467}
468
469#[cfg(test)]
470mod tests {
471
472 use super::*;
473
474 #[test]
475 fn test_activation_event_parsing() {
476 let event = ActivationEvent::from_str("*").unwrap();
477
478 assert_eq!(event, ActivationEvent::Star);
479
480 let event = ActivationEvent::from_str("onCommand:test.command").unwrap();
481
482 assert_eq!(event, ActivationEvent::Command("test.command".to_string()));
483
484 let event = ActivationEvent::from_str("onLanguage:rust").unwrap();
485
486 assert_eq!(event, ActivationEvent::Language("rust".to_string()));
487 }
488
489 #[test]
490 fn test_activation_event_to_string() {
491 assert_eq!(ActivationEvent::Star.to_string(), "*");
492
493 assert_eq!(ActivationEvent::Command("test".to_string()).to_string(), "onCommand:test");
494
495 assert_eq!(ActivationEvent::Language("rust".to_string()).to_string(), "onLanguage:rust");
496 }
497
498 #[test]
499 fn test_activation_context_default() {
500 let context = ActivationContext::default();
501
502 assert!(context.workspace_path.is_none());
503
504 assert!(context.current_file.is_none());
505
506 assert!(!context.active_editor);
507 }
508
509 #[test]
510 fn test_wildcard_matching() {
511 let matcher = WildMatch::new("*");
512
513 assert!(matcher.matches("anything"));
514
515 let matcher = WildMatch::new("prefix*");
516
517 assert!(matcher.matches("prefix_suffix"));
518
519 assert!(!matcher.matches("noprefix_suffix"));
520
521 let matcher = WildMatch::new("*suffix");
522
523 assert!(matcher.matches("prefix_suffix"));
524
525 assert!(!matcher.matches("prefix_suffix_not"));
526 }
527}