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