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::{
14 Host::{ActivationResult, HostConfig},
15 Host::ExtensionManager::{ExtensionManagerImpl, ExtensionState},
16};
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub enum ActivationEvent {
21 Startup,
23 Command(String),
25 Language(String),
27 WorkspaceContains(String),
29 OnView(String),
31 OnUri(String),
33 OnFiles(String),
35 Custom(String),
37 Star,
39}
40
41impl ActivationEvent {
42 pub fn from_str(event_str:&str) -> Result<Self> {
44 match event_str {
45 "*" => Ok(Self::Star),
46 e if e.starts_with("onCommand:") => Ok(Self::Command(e.trim_start_matches("onCommand:").to_string())),
47 e if e.starts_with("onLanguage:") => Ok(Self::Language(e.trim_start_matches("onLanguage:").to_string())),
48 e if e.starts_with("workspaceContains:") => {
49 Ok(Self::WorkspaceContains(e.trim_start_matches("workspaceContains:").to_string()))
50 },
51 e if e.starts_with("onView:") => Ok(Self::OnView(e.trim_start_matches("onView:").to_string())),
52 e if e.starts_with("onUri:") => Ok(Self::OnUri(e.trim_start_matches("onUri:").to_string())),
53 e if e.starts_with("onFiles:") => Ok(Self::OnFiles(e.trim_start_matches("onFiles:").to_string())),
54 _ => Ok(Self::Custom(event_str.to_string())),
55 }
56 }
57
58 pub fn to_string(&self) -> String {
60 match self {
61 Self::Startup => "onStartup".to_string(),
62 Self::Star => "*".to_string(),
63 Self::Command(cmd) => format!("onCommand:{}", cmd),
64 Self::Language(lang) => format!("onLanguage:{}", lang),
65 Self::WorkspaceContains(pattern) => format!("workspaceContains:{}", pattern),
66 Self::OnView(view) => format!("onView:{}", view),
67 Self::OnUri(uri) => format!("onUri:{}", uri),
68 Self::OnFiles(pattern) => format!("onFiles:{}", pattern),
69 Self::Custom(s) => s.clone(),
70 }
71 }
72}
73
74impl std::str::FromStr for ActivationEvent {
75 type Err = anyhow::Error;
76
77 fn from_str(s:&str) -> Result<Self, Self::Err> { Self::from_str(s) }
78}
79
80pub struct ActivationEngine {
82 extension_manager:Arc<ExtensionManagerImpl>,
84 #[allow(dead_code)]
86 config:HostConfig,
87 event_handlers:Arc<RwLock<HashMap<String, ActivationHandler>>>,
89 activation_history:Arc<RwLock<Vec<ActivationRecord>>>,
91}
92
93#[derive(Debug, Clone)]
95struct ActivationHandler {
96 #[allow(dead_code)]
98 extension_id:String,
99 events:Vec<ActivationEvent>,
101 #[allow(dead_code)]
103 activation_function:String,
104 is_active:bool,
106 #[allow(dead_code)]
108 last_activation:Option<u64>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ActivationRecord {
114 pub extension_id:String,
116 pub events:Vec<String>,
118 pub timestamp:u64,
120 pub duration_ms:u64,
122 pub success:bool,
124 pub error:Option<String>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct ActivationContext {
131 pub workspace_path:Option<PathBuf>,
133 pub current_file:Option<PathBuf>,
135 pub language_id:Option<String>,
137 pub active_editor:bool,
139 pub environment:HashMap<String, String>,
141 pub additional_data:serde_json::Value,
143}
144
145impl Default for ActivationContext {
146 fn default() -> Self {
147 Self {
148 workspace_path:None,
149 current_file:None,
150 language_id:None,
151 active_editor:false,
152 environment:HashMap::new(),
153 additional_data:serde_json::Value::Null,
154 }
155 }
156}
157
158impl ActivationEngine {
159 pub fn new(extension_manager:Arc<ExtensionManagerImpl>, config:HostConfig) -> Self {
161 Self {
162 extension_manager,
163 config,
164 event_handlers:Arc::new(RwLock::new(HashMap::new())),
165 activation_history:Arc::new(RwLock::new(Vec::new())),
166 }
167 }
168
169 #[instrument(skip(self, extension_id))]
171 pub async fn activate(&self, extension_id:&str) -> Result<ActivationResult> {
172 info!("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 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 info!("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 #[instrument(skip(self, extension_id))]
268 pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
269 info!("Deactivating extension: {}", extension_id);
270
271 let mut handlers = self.event_handlers.write().await;
273 if let Some(mut handler) = handlers.remove(extension_id) {
274 handler.is_active = false;
275 }
276
277 self.extension_manager
279 .update_state(extension_id, ExtensionState::Deactivated)
280 .await?;
281
282 info!("Extension deactivated: {}", extension_id);
283
284 Ok(())
285 }
286
287 #[instrument(skip(self, event, _context))]
289 pub async fn trigger_activation(&self, event:&str, _context:&ActivationContext) -> Result<Vec<ActivationResult>> {
290 info!("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 debug!("Activating extension {} for event: {}", extension_id, event);
305 match self.activate(extension_id).await {
306 Ok(result) => results.push(result),
307 Err(e) => {
308 warn!("Failed to activate extension {} for event {}: {}", extension_id, event, e);
309 },
310 }
311 }
312 }
313
314 Ok(results)
315 }
316
317 fn should_activate(&self, activation_event:&ActivationEvent, events:&[ActivationEvent]) -> bool {
319 events.iter().any(|e| {
320 match (e, activation_event) {
321 (ActivationEvent::Star, _) => true,
322 (ActivationEvent::Custom(pattern), _) => {
323 WildMatch::new(pattern).matches(activation_event.to_string().as_str())
324 },
325 _ => e == activation_event,
326 }
327 })
328 }
329
330 async fn perform_activation(&self, extension_id:&str, _context:&ActivationContext) -> Result<ActivationResult> {
333 debug!("Performing activation for extension: {}", extension_id);
340
341 Ok(ActivationResult {
343 extension_id:extension_id.to_string(),
344 success:true,
345 time_ms:0,
346 error:None,
347 contributes:Vec::new(),
348 })
349 }
350
351 pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
353
354 pub async fn get_activation_history_for_extension(&self, extension_id:&str) -> Vec<ActivationRecord> {
356 self.activation_history
357 .read()
358 .await
359 .iter()
360 .filter(|r| r.extension_id == extension_id)
361 .cloned()
362 .collect()
363 }
364}
365
366struct WildMatch {
368 pattern:String,
369}
370
371impl WildMatch {
372 fn new(pattern:&str) -> Self { Self { pattern:pattern.to_lowercase() } }
373
374 fn matches(&self, text:&str) -> bool {
375 let text = text.to_lowercase();
376
377 if self.pattern == "*" {
379 return true;
380 }
381
382 if self.pattern.starts_with('*') {
384 let suffix = &self.pattern[1..];
385 return text.ends_with(suffix);
386 }
387
388 if self.pattern.ends_with('*') {
390 let prefix = &self.pattern[..self.pattern.len() - 1];
391 return text.starts_with(prefix);
392 }
393
394 self.pattern == text
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn test_activation_event_parsing() {
405 let event = ActivationEvent::from_str("*").unwrap();
406 assert_eq!(event, ActivationEvent::Star);
407
408 let event = ActivationEvent::from_str("onCommand:test.command").unwrap();
409 assert_eq!(event, ActivationEvent::Command("test.command".to_string()));
410
411 let event = ActivationEvent::from_str("onLanguage:rust").unwrap();
412 assert_eq!(event, ActivationEvent::Language("rust".to_string()));
413 }
414
415 #[test]
416 fn test_activation_event_to_string() {
417 assert_eq!(ActivationEvent::Star.to_string(), "*");
418 assert_eq!(ActivationEvent::Command("test".to_string()).to_string(), "onCommand:test");
419 assert_eq!(ActivationEvent::Language("rust".to_string()).to_string(), "onLanguage:rust");
420 }
421
422 #[test]
423 fn test_activation_context_default() {
424 let context = ActivationContext::default();
425 assert!(context.workspace_path.is_none());
426 assert!(context.current_file.is_none());
427 assert!(!context.active_editor);
428 }
429
430 #[test]
431 fn test_wildcard_matching() {
432 let matcher = WildMatch::new("*");
433 assert!(matcher.matches("anything"));
434
435 let matcher = WildMatch::new("prefix*");
436 assert!(matcher.matches("prefix_suffix"));
437 assert!(!matcher.matches("noprefix_suffix"));
438
439 let matcher = WildMatch::new("*suffix");
440 assert!(matcher.matches("prefix_suffix"));
441 assert!(!matcher.matches("prefix_suffix_not"));
442 }
443}