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	/// Activate when a specific command is executed
27	Command(String),
28	/// Activate when a specific language is detected
29	Language(String),
30	/// Activate when a workspace of a specific type is opened
31	WorkspaceContains(String),
32	/// Activate when specific content type is viewed
33	OnView(String),
34	/// Activate when a URI scheme is used
35	OnUri(String),
36	/// Activate when specific file patterns match
37	OnFiles(String),
38	/// Custom activation event
39	Custom(String),
40	/// Activate on any event (always active)
41	Star,
42}
43
44impl ActivationEvent {
45	/// Parse an activation event from a string
46	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	/// Convert to string representation
62	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
83/// Activation engine for managing extension activation
84pub struct ActivationEngine {
85	/// Extension manager
86	extension_manager:Arc<ExtensionManagerImpl>,
87	/// Host configuration
88	#[allow(dead_code)]
89	config:HostConfig,
90	/// Event handlers mapping
91	event_handlers:Arc<RwLock<HashMap<String, ActivationHandler>>>,
92	/// Activation history
93	activation_history:Arc<RwLock<Vec<ActivationRecord>>>,
94}
95
96/// Activation handler for an extension
97#[derive(Debug, Clone)]
98struct ActivationHandler {
99	/// Extension ID
100	#[allow(dead_code)]
101	extension_id:String,
102	/// Activation events
103	events:Vec<ActivationEvent>,
104	/// Activation function path
105	#[allow(dead_code)]
106	activation_function:String,
107	/// Whether extension is currently active
108	is_active:bool,
109	/// Last activation time
110	#[allow(dead_code)]
111	last_activation:Option<u64>,
112}
113
114/// Activation record for tracking
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct ActivationRecord {
117	/// Extension ID
118	pub extension_id:String,
119	/// Activation events
120	pub events:Vec<String>,
121	/// Activation time (Unix timestamp)
122	pub timestamp:u64,
123	/// Duration in milliseconds
124	pub duration_ms:u64,
125	/// Success flag
126	pub success:bool,
127	/// Error message (if failed)
128	pub error:Option<String>,
129}
130
131/// Activation context passed to extensions
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct ActivationContext {
134	/// Workspace root path
135	pub workspace_path:Option<PathBuf>,
136	/// Current file path
137	pub current_file:Option<PathBuf>,
138	/// Current language ID
139	pub language_id:Option<String>,
140	/// Active editor
141	pub active_editor:bool,
142	/// Environment variables
143	pub environment:HashMap<String, String>,
144	/// Additional context data
145	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	/// Create a new activation engine
163	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	/// Activate an extension
173	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		// Get extension info
179		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		// Check if already active
186		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		// Parse activation events
202		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		// Create activation context
210		let context = ActivationContext::default();
211
212		// Perform activation (in real implementation, this would call the extension's
213		// activate function)
214		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		// Record activation
222		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		// Save timestamp for later use
235		let activation_timestamp = record.timestamp;
236
237		self.activation_history.write().await.push(record);
238
239		// Update extension state
240		self.extension_manager
241			.update_state(extension_id, ExtensionState::Activated)
242			.await?;
243
244		// Register handler
245		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	/// Deactivate an extension
269	pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
270		dev_log!("extensions", "Deactivating extension: {}", extension_id);
271
272		// Remove handler
273		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		// Update extension state
279		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	/// Trigger activation for certain events
289	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			// Check if extension should activate on this event
299			if handler.is_active {
300				continue; // Already active
301			}
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	/// Check if extension should activate for given event
324	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	/// Perform actual activation (placeholder - would call extension's activate
337	/// function)
338	async fn perform_activation(&self, extension_id:&str, _context:&ActivationContext) -> Result<ActivationResult> {
339		// In real implementation, this would:
340		// 1. Call the extension's activate function
341		// 2. Pass the activation context
342		// 3. Wait for activation to complete
343		// 4. Handle any errors
344
345		dev_log!("extensions", "Performing activation for extension: {}", extension_id);
346
347		// Placeholder implementation
348		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	/// Get activation history
358	pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
359
360	/// Get activation history for a specific extension
361	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
372/// Simple wildcard matching for flexible activation events
373struct 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		// Handle * wildcard
384		if self.pattern == "*" {
385			return true;
386		}
387
388		// Handle patterns starting with *
389		if self.pattern.starts_with('*') {
390			let suffix = &self.pattern[1..];
391			return text.ends_with(suffix);
392		}
393
394		// Handle patterns ending with *
395		if self.pattern.ends_with('*') {
396			let prefix = &self.pattern[..self.pattern.len() - 1];
397			return text.starts_with(prefix);
398		}
399
400		// Exact match
401		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}