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;
11use crate::dev_log;
12
13use crate::Host::{
14	ActivationResult,
15	ExtensionManager::{ExtensionManagerImpl, ExtensionState},
16	HostConfig,
17};
18
19/// Extension activation event types
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub enum ActivationEvent {
22	/// Activate when the extension host starts up
23	Startup,
24	/// Activate when a specific command is executed
25	Command(String),
26	/// Activate when a specific language is detected
27	Language(String),
28	/// Activate when a workspace of a specific type is opened
29	WorkspaceContains(String),
30	/// Activate when specific content type is viewed
31	OnView(String),
32	/// Activate when a URI scheme is used
33	OnUri(String),
34	/// Activate when specific file patterns match
35	OnFiles(String),
36	/// Custom activation event
37	Custom(String),
38	/// Activate on any event (always active)
39	Star,
40}
41
42impl ActivationEvent {
43	/// Parse an activation event from a string
44	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	/// Convert to string representation
60	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
81/// Activation engine for managing extension activation
82pub struct ActivationEngine {
83	/// Extension manager
84	extension_manager:Arc<ExtensionManagerImpl>,
85	/// Host configuration
86	#[allow(dead_code)]
87	config:HostConfig,
88	/// Event handlers mapping
89	event_handlers:Arc<RwLock<HashMap<String, ActivationHandler>>>,
90	/// Activation history
91	activation_history:Arc<RwLock<Vec<ActivationRecord>>>,
92}
93
94/// Activation handler for an extension
95#[derive(Debug, Clone)]
96struct ActivationHandler {
97	/// Extension ID
98	#[allow(dead_code)]
99	extension_id:String,
100	/// Activation events
101	events:Vec<ActivationEvent>,
102	/// Activation function path
103	#[allow(dead_code)]
104	activation_function:String,
105	/// Whether extension is currently active
106	is_active:bool,
107	/// Last activation time
108	#[allow(dead_code)]
109	last_activation:Option<u64>,
110}
111
112/// Activation record for tracking
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ActivationRecord {
115	/// Extension ID
116	pub extension_id:String,
117	/// Activation events
118	pub events:Vec<String>,
119	/// Activation time (Unix timestamp)
120	pub timestamp:u64,
121	/// Duration in milliseconds
122	pub duration_ms:u64,
123	/// Success flag
124	pub success:bool,
125	/// Error message (if failed)
126	pub error:Option<String>,
127}
128
129/// Activation context passed to extensions
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ActivationContext {
132	/// Workspace root path
133	pub workspace_path:Option<PathBuf>,
134	/// Current file path
135	pub current_file:Option<PathBuf>,
136	/// Current language ID
137	pub language_id:Option<String>,
138	/// Active editor
139	pub active_editor:bool,
140	/// Environment variables
141	pub environment:HashMap<String, String>,
142	/// Additional context data
143	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	/// Create a new activation engine
161	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	/// Activate an extension
171	pub async fn activate(&self, extension_id:&str) -> Result<ActivationResult> {
172		dev_log!("extensions", "Activating extension: {}", extension_id);
173
174		let start = std::time::Instant::now();
175
176		// Get extension info
177		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		// Check if already active
184		let handlers = self.event_handlers.read().await;
185		if let Some(handler) = handlers.get(extension_id) {
186			if handler.is_active {
187				dev_log!("extensions", "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		// Parse activation events
200		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		// Create activation context
208		let context = ActivationContext::default();
209
210		// Perform activation (in real implementation, this would call the extension's
211		// activate function)
212		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		// Record activation
220		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		// Save timestamp for later use
233		let activation_timestamp = record.timestamp;
234
235		self.activation_history.write().await.push(record);
236
237		// Update extension state
238		self.extension_manager
239			.update_state(extension_id, ExtensionState::Activated)
240			.await?;
241
242		// Register handler
243		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		dev_log!("extensions", "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	/// Deactivate an extension
267	pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
268		dev_log!("extensions", "Deactivating extension: {}", extension_id);
269
270		// Remove handler
271		let mut handlers = self.event_handlers.write().await;
272		if let Some(mut handler) = handlers.remove(extension_id) {
273			handler.is_active = false;
274		}
275
276		// Update extension state
277		self.extension_manager
278			.update_state(extension_id, ExtensionState::Deactivated)
279			.await?;
280
281		dev_log!("extensions", "Extension deactivated: {}", extension_id);
282
283		Ok(())
284	}
285
286	/// Trigger activation for certain events
287	pub async fn trigger_activation(&self, event:&str, _context:&ActivationContext) -> Result<Vec<ActivationResult>> {
288		dev_log!("extensions", "Triggering activation for event: {}", event);
289
290		let activation_event = ActivationEvent::from_str(event)?;
291		let handlers = self.event_handlers.read().await;
292
293		let mut results = Vec::new();
294
295		for (extension_id, handler) in handlers.iter() {
296			// Check if extension should activate on this event
297			if handler.is_active {
298				continue; // Already active
299			}
300
301			if self.should_activate(&activation_event, &handler.events) {
302				dev_log!("extensions", "Activating extension {} for event: {}", extension_id, event);
303				match self.activate(extension_id).await {
304					Ok(result) => results.push(result),
305					Err(e) => {
306						dev_log!("extensions", "warn: failed to activate extension {} for event {}: {}", extension_id, event, e);
307					},
308				}
309			}
310		}
311
312		Ok(results)
313	}
314
315	/// Check if extension should activate for given event
316	fn should_activate(&self, activation_event:&ActivationEvent, events:&[ActivationEvent]) -> bool {
317		events.iter().any(|e| {
318			match (e, activation_event) {
319				(ActivationEvent::Star, _) => true,
320				(ActivationEvent::Custom(pattern), _) => {
321					WildMatch::new(pattern).matches(activation_event.to_string().as_str())
322				},
323				_ => e == activation_event,
324			}
325		})
326	}
327
328	/// Perform actual activation (placeholder - would call extension's activate
329	/// function)
330	async fn perform_activation(&self, extension_id:&str, _context:&ActivationContext) -> Result<ActivationResult> {
331		// In real implementation, this would:
332		// 1. Call the extension's activate function
333		// 2. Pass the activation context
334		// 3. Wait for activation to complete
335		// 4. Handle any errors
336
337		dev_log!("extensions", "Performing activation for extension: {}", extension_id);
338
339		// Placeholder implementation
340		Ok(ActivationResult {
341			extension_id:extension_id.to_string(),
342			success:true,
343			time_ms:0,
344			error:None,
345			contributes:Vec::new(),
346		})
347	}
348
349	/// Get activation history
350	pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
351
352	/// Get activation history for a specific extension
353	pub async fn get_activation_history_for_extension(&self, extension_id:&str) -> Vec<ActivationRecord> {
354		self.activation_history
355			.read()
356			.await
357			.iter()
358			.filter(|r| r.extension_id == extension_id)
359			.cloned()
360			.collect()
361	}
362}
363
364/// Simple wildcard matching for flexible activation events
365struct WildMatch {
366	pattern:String,
367}
368
369impl WildMatch {
370	fn new(pattern:&str) -> Self { Self { pattern:pattern.to_lowercase() } }
371
372	fn matches(&self, text:&str) -> bool {
373		let text = text.to_lowercase();
374
375		// Handle * wildcard
376		if self.pattern == "*" {
377			return true;
378		}
379
380		// Handle patterns starting with *
381		if self.pattern.starts_with('*') {
382			let suffix = &self.pattern[1..];
383			return text.ends_with(suffix);
384		}
385
386		// Handle patterns ending with *
387		if self.pattern.ends_with('*') {
388			let prefix = &self.pattern[..self.pattern.len() - 1];
389			return text.starts_with(prefix);
390		}
391
392		// Exact match
393		self.pattern == text
394	}
395}
396
397#[cfg(test)]
398mod tests {
399	use super::*;
400
401	#[test]
402	fn test_activation_event_parsing() {
403		let event = ActivationEvent::from_str("*").unwrap();
404		assert_eq!(event, ActivationEvent::Star);
405
406		let event = ActivationEvent::from_str("onCommand:test.command").unwrap();
407		assert_eq!(event, ActivationEvent::Command("test.command".to_string()));
408
409		let event = ActivationEvent::from_str("onLanguage:rust").unwrap();
410		assert_eq!(event, ActivationEvent::Language("rust".to_string()));
411	}
412
413	#[test]
414	fn test_activation_event_to_string() {
415		assert_eq!(ActivationEvent::Star.to_string(), "*");
416		assert_eq!(ActivationEvent::Command("test".to_string()).to_string(), "onCommand:test");
417		assert_eq!(ActivationEvent::Language("rust".to_string()).to_string(), "onLanguage:rust");
418	}
419
420	#[test]
421	fn test_activation_context_default() {
422		let context = ActivationContext::default();
423		assert!(context.workspace_path.is_none());
424		assert!(context.current_file.is_none());
425		assert!(!context.active_editor);
426	}
427
428	#[test]
429	fn test_wildcard_matching() {
430		let matcher = WildMatch::new("*");
431		assert!(matcher.matches("anything"));
432
433		let matcher = WildMatch::new("prefix*");
434		assert!(matcher.matches("prefix_suffix"));
435		assert!(!matcher.matches("noprefix_suffix"));
436
437		let matcher = WildMatch::new("*suffix");
438		assert!(matcher.matches("prefix_suffix"));
439		assert!(!matcher.matches("prefix_suffix_not"));
440	}
441}