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 tracing::{debug, info, instrument, warn};
12
13use crate::{
14    Host::{ActivationResult, HostConfig},
15    Host::ExtensionManager::{ExtensionManagerImpl, ExtensionState},
16};
17
18/// Extension activation event types
19#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub enum ActivationEvent {
21	/// Activate when the extension host starts up
22	Startup,
23	/// Activate when a specific command is executed
24	Command(String),
25	/// Activate when a specific language is detected
26	Language(String),
27	/// Activate when a workspace of a specific type is opened
28	WorkspaceContains(String),
29	/// Activate when specific content type is viewed
30	OnView(String),
31	/// Activate when a URI scheme is used
32	OnUri(String),
33	/// Activate when specific file patterns match
34	OnFiles(String),
35	/// Custom activation event
36	Custom(String),
37	/// Activate on any event (always active)
38	Star,
39}
40
41impl ActivationEvent {
42	/// Parse an activation event from a string
43	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	/// Convert to string representation
59	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
80/// Activation engine for managing extension activation
81pub struct ActivationEngine {
82    /// Extension manager
83    extension_manager:Arc<ExtensionManagerImpl>,
84    /// Host configuration
85    #[allow(dead_code)]
86    config:HostConfig,
87    /// Event handlers mapping
88    event_handlers:Arc<RwLock<HashMap<String, ActivationHandler>>>,
89    /// Activation history
90    activation_history:Arc<RwLock<Vec<ActivationRecord>>>,
91}
92
93/// Activation handler for an extension
94#[derive(Debug, Clone)]
95struct ActivationHandler {
96    /// Extension ID
97    #[allow(dead_code)]
98    extension_id:String,
99    /// Activation events
100    events:Vec<ActivationEvent>,
101    /// Activation function path
102    #[allow(dead_code)]
103    activation_function:String,
104    /// Whether extension is currently active
105    is_active:bool,
106    /// Last activation time
107    #[allow(dead_code)]
108    last_activation:Option<u64>,
109}
110
111/// Activation record for tracking
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ActivationRecord {
114	/// Extension ID
115	pub extension_id:String,
116	/// Activation events
117	pub events:Vec<String>,
118	/// Activation time (Unix timestamp)
119	pub timestamp:u64,
120	/// Duration in milliseconds
121	pub duration_ms:u64,
122	/// Success flag
123	pub success:bool,
124	/// Error message (if failed)
125	pub error:Option<String>,
126}
127
128/// Activation context passed to extensions
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct ActivationContext {
131	/// Workspace root path
132	pub workspace_path:Option<PathBuf>,
133	/// Current file path
134	pub current_file:Option<PathBuf>,
135	/// Current language ID
136	pub language_id:Option<String>,
137	/// Active editor
138	pub active_editor:bool,
139	/// Environment variables
140	pub environment:HashMap<String, String>,
141	/// Additional context data
142	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	/// Create a new activation engine
160	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	/// Activate an extension
170	#[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		// 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				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		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	/// Deactivate an extension
267	#[instrument(skip(self, extension_id))]
268	pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
269		info!("Deactivating extension: {}", extension_id);
270
271		// Remove handler
272		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		// Update extension state
278		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	/// Trigger activation for certain events
288	#[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			// 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				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	/// Check if extension should activate for given event
318	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	/// Perform actual activation (placeholder - would call extension's activate
331	/// function)
332	async fn perform_activation(&self, extension_id:&str, _context:&ActivationContext) -> Result<ActivationResult> {
333		// In real implementation, this would:
334		// 1. Call the extension's activate function
335		// 2. Pass the activation context
336		// 3. Wait for activation to complete
337		// 4. Handle any errors
338
339		debug!("Performing activation for extension: {}", extension_id);
340
341		// Placeholder implementation
342		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	/// Get activation history
352	pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
353
354	/// Get activation history for a specific extension
355	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
366/// Simple wildcard matching for flexible activation events
367struct 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		// Handle * wildcard
378		if self.pattern == "*" {
379			return true;
380		}
381
382		// Handle patterns starting with *
383		if self.pattern.starts_with('*') {
384			let suffix = &self.pattern[1..];
385			return text.ends_with(suffix);
386		}
387
388		// Handle patterns ending with *
389		if self.pattern.ends_with('*') {
390			let prefix = &self.pattern[..self.pattern.len() - 1];
391			return text.starts_with(prefix);
392		}
393
394		// Exact match
395		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}