Skip to main content

Grove/Host/
Activation.rs

1//! Activation Module
2//!
3//! Handles extension activation events and orchestration.
4//! Manages the activation lifecycle for extensions.
5
6use 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/// Extension activation event types
22#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub enum ActivationEvent {
24	/// Activate when the extension host starts up
25	Startup,
26
27	/// Activate when a specific command is executed
28	Command(String),
29
30	/// Activate when a specific language is detected
31	Language(String),
32
33	/// Activate when a workspace of a specific type is opened
34	WorkspaceContains(String),
35
36	/// Activate when specific content type is viewed
37	OnView(String),
38
39	/// Activate when a URI scheme is used
40	OnUri(String),
41
42	/// Activate when specific file patterns match
43	OnFiles(String),
44
45	/// Custom activation event
46	Custom(String),
47
48	/// Activate on any event (always active)
49	Star,
50}
51
52impl ActivationEvent {
53	/// Parse an activation event from a string
54	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	/// Convert to string representation
77	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
106/// Activation engine for managing extension activation
107pub struct ActivationEngine {
108	/// Extension manager
109	extension_manager:Arc<ExtensionManagerImpl>,
110
111	/// Host configuration
112	#[allow(dead_code)]
113	config:HostConfig,
114
115	/// Event handlers mapping
116	event_handlers:Arc<RwLock<HashMap<String, ActivationHandler>>>,
117
118	/// Activation history
119	activation_history:Arc<RwLock<Vec<ActivationRecord>>>,
120}
121
122/// Activation handler for an extension
123#[derive(Debug, Clone)]
124struct ActivationHandler {
125	/// Extension ID
126	#[allow(dead_code)]
127	extension_id:String,
128
129	/// Activation events
130	events:Vec<ActivationEvent>,
131
132	/// Activation function path
133	#[allow(dead_code)]
134	activation_function:String,
135
136	/// Whether extension is currently active
137	is_active:bool,
138
139	/// Last activation time
140	#[allow(dead_code)]
141	last_activation:Option<u64>,
142}
143
144/// Activation record for tracking
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct ActivationRecord {
147	/// Extension ID
148	pub extension_id:String,
149
150	/// Activation events
151	pub events:Vec<String>,
152
153	/// Activation time (Unix timestamp)
154	pub timestamp:u64,
155
156	/// Duration in milliseconds
157	pub duration_ms:u64,
158
159	/// Success flag
160	pub success:bool,
161
162	/// Error message (if failed)
163	pub error:Option<String>,
164}
165
166/// Activation context passed to extensions
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct ActivationContext {
169	/// Workspace root path
170	pub workspace_path:Option<PathBuf>,
171
172	/// Current file path
173	pub current_file:Option<PathBuf>,
174
175	/// Current language ID
176	pub language_id:Option<String>,
177
178	/// Active editor
179	pub active_editor:bool,
180
181	/// Environment variables
182	pub environment:HashMap<String, String>,
183
184	/// Additional context data
185	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	/// Create a new activation engine
208	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	/// Activate an extension
221	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		// Get extension info
227		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		// Check if already active
234		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		// Parse activation events
253		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		// Create activation context
262		let context = ActivationContext::default();
263
264		// Perform activation (in real implementation, this would call the extension's
265		// activate function)
266		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		// Record activation
274		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		// Save timestamp for later use
292		let activation_timestamp = record.timestamp;
293
294		self.activation_history.write().await.push(record);
295
296		// Update extension state
297		self.extension_manager
298			.update_state(extension_id, ExtensionState::Activated)
299			.await?;
300
301		// Register handler
302		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	/// Deactivate an extension
327	pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
328		dev_log!("extensions", "Deactivating extension: {}", extension_id);
329
330		// Remove handler
331		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		// Update extension state
338		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	/// Trigger activation for certain events
348	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			// Check if extension should activate on this event
359			if handler.is_active {
360				continue; // Already active
361			}
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	/// Check if extension should activate for given event
386	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	/// Perform actual activation (placeholder - would call extension's activate
399	/// function)
400	async fn perform_activation(&self, extension_id:&str, _context:&ActivationContext) -> Result<ActivationResult> {
401		// In real implementation, this would:
402		// 1. Call the extension's activate function
403		// 2. Pass the activation context
404		// 3. Wait for activation to complete
405		// 4. Handle any errors
406
407		dev_log!("extensions", "Performing activation for extension: {}", extension_id);
408
409		// Placeholder implementation
410		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	/// Get activation history
420	pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
421
422	/// Get activation history for a specific extension
423	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
434/// Simple wildcard matching for flexible activation events
435struct 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		// Handle * wildcard
446		if self.pattern == "*" {
447			return true;
448		}
449
450		// Handle patterns starting with *
451		if self.pattern.starts_with('*') {
452			let suffix = &self.pattern[1..];
453
454			return text.ends_with(suffix);
455		}
456
457		// Handle patterns ending with *
458		if self.pattern.ends_with('*') {
459			let prefix = &self.pattern[..self.pattern.len() - 1];
460
461			return text.starts_with(prefix);
462		}
463
464		// Exact match
465		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}