1use std::{collections::HashMap, path::PathBuf, sync::Arc};
7
8use anyhow::{Context, Result};
9use serde::{Deserialize, Serialize};
10use tokio::sync::RwLock;
11use tracing::{debug, info, instrument, warn};
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 #[instrument(skip(self, extension_id))]
172 pub async fn activate(&self, extension_id:&str) -> Result<ActivationResult> {
173 info!("Activating extension: {}", extension_id);
174
175 let start = std::time::Instant::now();
176
177 let extension_info = self
179 .extension_manager
180 .get_extension(extension_id)
181 .await
182 .ok_or_else(|| anyhow::anyhow!("Extension not found: {}", extension_id))?;
183
184 let handlers = self.event_handlers.read().await;
186 if let Some(handler) = handlers.get(extension_id) {
187 if handler.is_active {
188 warn!("Extension already active: {}", extension_id);
189 return Ok(ActivationResult {
190 extension_id:extension_id.to_string(),
191 success:true,
192 time_ms:0,
193 error:None,
194 contributes:Vec::new(),
195 });
196 }
197 }
198 drop(handlers);
199
200 let activation_events:Result<Vec<ActivationEvent>> = extension_info
202 .activation_events
203 .iter()
204 .map(|e| ActivationEvent::from_str(e))
205 .collect();
206 let activation_events = activation_events.with_context(|| "Failed to parse activation events")?;
207
208 let context = ActivationContext::default();
210
211 let activation_result = self
214 .perform_activation(extension_id, &context)
215 .await
216 .context("Activation failed")?;
217
218 let elapsed_ms = start.elapsed().as_millis() as u64;
219
220 let record = ActivationRecord {
222 extension_id:extension_id.to_string(),
223 events:extension_info.activation_events.clone(),
224 timestamp:std::time::SystemTime::now()
225 .duration_since(std::time::UNIX_EPOCH)
226 .map(|d| d.as_secs())
227 .unwrap_or(0),
228 duration_ms:elapsed_ms,
229 success:activation_result.success,
230 error:None,
231 };
232
233 let activation_timestamp = record.timestamp;
235
236 self.activation_history.write().await.push(record);
237
238 self.extension_manager
240 .update_state(extension_id, ExtensionState::Activated)
241 .await?;
242
243 let mut handlers = self.event_handlers.write().await;
245 handlers.insert(
246 extension_id.to_string(),
247 ActivationHandler {
248 extension_id:extension_id.to_string(),
249 events:activation_events,
250 activation_function:"activate".to_string(),
251 is_active:true,
252 last_activation:Some(activation_timestamp),
253 },
254 );
255
256 info!("Extension activated in {}ms: {}", elapsed_ms, extension_id);
257
258 Ok(ActivationResult {
259 extension_id:extension_id.to_string(),
260 success:true,
261 time_ms:elapsed_ms,
262 error:None,
263 contributes:extension_info.capabilities.clone(),
264 })
265 }
266
267 #[instrument(skip(self, extension_id))]
269 pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
270 info!("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 info!("Extension deactivated: {}", extension_id);
284
285 Ok(())
286 }
287
288 #[instrument(skip(self, event, _context))]
290 pub async fn trigger_activation(&self, event:&str, _context:&ActivationContext) -> Result<Vec<ActivationResult>> {
291 info!("Triggering activation for event: {}", event);
292
293 let activation_event = ActivationEvent::from_str(event)?;
294 let handlers = self.event_handlers.read().await;
295
296 let mut results = Vec::new();
297
298 for (extension_id, handler) in handlers.iter() {
299 if handler.is_active {
301 continue; }
303
304 if self.should_activate(&activation_event, &handler.events) {
305 debug!("Activating extension {} for event: {}", extension_id, event);
306 match self.activate(extension_id).await {
307 Ok(result) => results.push(result),
308 Err(e) => {
309 warn!("Failed to activate extension {} for event {}: {}", extension_id, event, e);
310 },
311 }
312 }
313 }
314
315 Ok(results)
316 }
317
318 fn should_activate(&self, activation_event:&ActivationEvent, events:&[ActivationEvent]) -> bool {
320 events.iter().any(|e| {
321 match (e, activation_event) {
322 (ActivationEvent::Star, _) => true,
323 (ActivationEvent::Custom(pattern), _) => {
324 WildMatch::new(pattern).matches(activation_event.to_string().as_str())
325 },
326 _ => e == activation_event,
327 }
328 })
329 }
330
331 async fn perform_activation(&self, extension_id:&str, _context:&ActivationContext) -> Result<ActivationResult> {
334 debug!("Performing activation for extension: {}", extension_id);
341
342 Ok(ActivationResult {
344 extension_id:extension_id.to_string(),
345 success:true,
346 time_ms:0,
347 error:None,
348 contributes:Vec::new(),
349 })
350 }
351
352 pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
354
355 pub async fn get_activation_history_for_extension(&self, extension_id:&str) -> Vec<ActivationRecord> {
357 self.activation_history
358 .read()
359 .await
360 .iter()
361 .filter(|r| r.extension_id == extension_id)
362 .cloned()
363 .collect()
364 }
365}
366
367struct WildMatch {
369 pattern:String,
370}
371
372impl WildMatch {
373 fn new(pattern:&str) -> Self { Self { pattern:pattern.to_lowercase() } }
374
375 fn matches(&self, text:&str) -> bool {
376 let text = text.to_lowercase();
377
378 if self.pattern == "*" {
380 return true;
381 }
382
383 if self.pattern.starts_with('*') {
385 let suffix = &self.pattern[1..];
386 return text.ends_with(suffix);
387 }
388
389 if self.pattern.ends_with('*') {
391 let prefix = &self.pattern[..self.pattern.len() - 1];
392 return text.starts_with(prefix);
393 }
394
395 self.pattern == text
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_activation_event_parsing() {
406 let event = ActivationEvent::from_str("*").unwrap();
407 assert_eq!(event, ActivationEvent::Star);
408
409 let event = ActivationEvent::from_str("onCommand:test.command").unwrap();
410 assert_eq!(event, ActivationEvent::Command("test.command".to_string()));
411
412 let event = ActivationEvent::from_str("onLanguage:rust").unwrap();
413 assert_eq!(event, ActivationEvent::Language("rust".to_string()));
414 }
415
416 #[test]
417 fn test_activation_event_to_string() {
418 assert_eq!(ActivationEvent::Star.to_string(), "*");
419 assert_eq!(ActivationEvent::Command("test".to_string()).to_string(), "onCommand:test");
420 assert_eq!(ActivationEvent::Language("rust".to_string()).to_string(), "onLanguage:rust");
421 }
422
423 #[test]
424 fn test_activation_context_default() {
425 let context = ActivationContext::default();
426 assert!(context.workspace_path.is_none());
427 assert!(context.current_file.is_none());
428 assert!(!context.active_editor);
429 }
430
431 #[test]
432 fn test_wildcard_matching() {
433 let matcher = WildMatch::new("*");
434 assert!(matcher.matches("anything"));
435
436 let matcher = WildMatch::new("prefix*");
437 assert!(matcher.matches("prefix_suffix"));
438 assert!(!matcher.matches("noprefix_suffix"));
439
440 let matcher = WildMatch::new("*suffix");
441 assert!(matcher.matches("prefix_suffix"));
442 assert!(!matcher.matches("prefix_suffix_not"));
443 }
444}