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::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	#[instrument(skip(self, extension_id))]
172	pub async fn activate(&self, extension_id:&str) -> Result<ActivationResult> {
173		info!("Activating extension: {}", extension_id);
174
175		let start = std::time::Instant::now();
176
177		// Get extension info
178		let extension_info = self
179			.extension_manager
180			.get_extension(extension_id)
181			.await
182			.ok_or_else(|| anyhow::anyhow!("Extension not found: {}", extension_id))?;
183
184		// Check if already active
185		let handlers = self.event_handlers.read().await;
186		if let Some(handler) = handlers.get(extension_id) {
187			if handler.is_active {
188				warn!("Extension already active: {}", extension_id);
189				return Ok(ActivationResult {
190					extension_id:extension_id.to_string(),
191					success:true,
192					time_ms:0,
193					error:None,
194					contributes:Vec::new(),
195				});
196			}
197		}
198		drop(handlers);
199
200		// Parse activation events
201		let activation_events:Result<Vec<ActivationEvent>> = extension_info
202			.activation_events
203			.iter()
204			.map(|e| ActivationEvent::from_str(e))
205			.collect();
206		let activation_events = activation_events.with_context(|| "Failed to parse activation events")?;
207
208		// Create activation context
209		let context = ActivationContext::default();
210
211		// Perform activation (in real implementation, this would call the extension's
212		// activate function)
213		let activation_result = self
214			.perform_activation(extension_id, &context)
215			.await
216			.context("Activation failed")?;
217
218		let elapsed_ms = start.elapsed().as_millis() as u64;
219
220		// Record activation
221		let record = ActivationRecord {
222			extension_id:extension_id.to_string(),
223			events:extension_info.activation_events.clone(),
224			timestamp:std::time::SystemTime::now()
225				.duration_since(std::time::UNIX_EPOCH)
226				.map(|d| d.as_secs())
227				.unwrap_or(0),
228			duration_ms:elapsed_ms,
229			success:activation_result.success,
230			error:None,
231		};
232
233		// Save timestamp for later use
234		let activation_timestamp = record.timestamp;
235
236		self.activation_history.write().await.push(record);
237
238		// Update extension state
239		self.extension_manager
240			.update_state(extension_id, ExtensionState::Activated)
241			.await?;
242
243		// Register handler
244		let mut handlers = self.event_handlers.write().await;
245		handlers.insert(
246			extension_id.to_string(),
247			ActivationHandler {
248				extension_id:extension_id.to_string(),
249				events:activation_events,
250				activation_function:"activate".to_string(),
251				is_active:true,
252				last_activation:Some(activation_timestamp),
253			},
254		);
255
256		info!("Extension activated in {}ms: {}", elapsed_ms, extension_id);
257
258		Ok(ActivationResult {
259			extension_id:extension_id.to_string(),
260			success:true,
261			time_ms:elapsed_ms,
262			error:None,
263			contributes:extension_info.capabilities.clone(),
264		})
265	}
266
267	/// Deactivate an extension
268	#[instrument(skip(self, extension_id))]
269	pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
270		info!("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		info!("Extension deactivated: {}", extension_id);
284
285		Ok(())
286	}
287
288	/// Trigger activation for certain events
289	#[instrument(skip(self, event, _context))]
290	pub async fn trigger_activation(&self, event:&str, _context:&ActivationContext) -> Result<Vec<ActivationResult>> {
291		info!("Triggering activation for event: {}", event);
292
293		let activation_event = ActivationEvent::from_str(event)?;
294		let handlers = self.event_handlers.read().await;
295
296		let mut results = Vec::new();
297
298		for (extension_id, handler) in handlers.iter() {
299			// Check if extension should activate on this event
300			if handler.is_active {
301				continue; // Already active
302			}
303
304			if self.should_activate(&activation_event, &handler.events) {
305				debug!("Activating extension {} for event: {}", extension_id, event);
306				match self.activate(extension_id).await {
307					Ok(result) => results.push(result),
308					Err(e) => {
309						warn!("Failed to activate extension {} for event {}: {}", extension_id, event, e);
310					},
311				}
312			}
313		}
314
315		Ok(results)
316	}
317
318	/// Check if extension should activate for given event
319	fn should_activate(&self, activation_event:&ActivationEvent, events:&[ActivationEvent]) -> bool {
320		events.iter().any(|e| {
321			match (e, activation_event) {
322				(ActivationEvent::Star, _) => true,
323				(ActivationEvent::Custom(pattern), _) => {
324					WildMatch::new(pattern).matches(activation_event.to_string().as_str())
325				},
326				_ => e == activation_event,
327			}
328		})
329	}
330
331	/// Perform actual activation (placeholder - would call extension's activate
332	/// function)
333	async fn perform_activation(&self, extension_id:&str, _context:&ActivationContext) -> Result<ActivationResult> {
334		// In real implementation, this would:
335		// 1. Call the extension's activate function
336		// 2. Pass the activation context
337		// 3. Wait for activation to complete
338		// 4. Handle any errors
339
340		debug!("Performing activation for extension: {}", extension_id);
341
342		// Placeholder implementation
343		Ok(ActivationResult {
344			extension_id:extension_id.to_string(),
345			success:true,
346			time_ms:0,
347			error:None,
348			contributes:Vec::new(),
349		})
350	}
351
352	/// Get activation history
353	pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
354
355	/// Get activation history for a specific extension
356	pub async fn get_activation_history_for_extension(&self, extension_id:&str) -> Vec<ActivationRecord> {
357		self.activation_history
358			.read()
359			.await
360			.iter()
361			.filter(|r| r.extension_id == extension_id)
362			.cloned()
363			.collect()
364	}
365}
366
367/// Simple wildcard matching for flexible activation events
368struct WildMatch {
369	pattern:String,
370}
371
372impl WildMatch {
373	fn new(pattern:&str) -> Self { Self { pattern:pattern.to_lowercase() } }
374
375	fn matches(&self, text:&str) -> bool {
376		let text = text.to_lowercase();
377
378		// Handle * wildcard
379		if self.pattern == "*" {
380			return true;
381		}
382
383		// Handle patterns starting with *
384		if self.pattern.starts_with('*') {
385			let suffix = &self.pattern[1..];
386			return text.ends_with(suffix);
387		}
388
389		// Handle patterns ending with *
390		if self.pattern.ends_with('*') {
391			let prefix = &self.pattern[..self.pattern.len() - 1];
392			return text.starts_with(prefix);
393		}
394
395		// Exact match
396		self.pattern == text
397	}
398}
399
400#[cfg(test)]
401mod tests {
402	use super::*;
403
404	#[test]
405	fn test_activation_event_parsing() {
406		let event = ActivationEvent::from_str("*").unwrap();
407		assert_eq!(event, ActivationEvent::Star);
408
409		let event = ActivationEvent::from_str("onCommand:test.command").unwrap();
410		assert_eq!(event, ActivationEvent::Command("test.command".to_string()));
411
412		let event = ActivationEvent::from_str("onLanguage:rust").unwrap();
413		assert_eq!(event, ActivationEvent::Language("rust".to_string()));
414	}
415
416	#[test]
417	fn test_activation_event_to_string() {
418		assert_eq!(ActivationEvent::Star.to_string(), "*");
419		assert_eq!(ActivationEvent::Command("test".to_string()).to_string(), "onCommand:test");
420		assert_eq!(ActivationEvent::Language("rust".to_string()).to_string(), "onLanguage:rust");
421	}
422
423	#[test]
424	fn test_activation_context_default() {
425		let context = ActivationContext::default();
426		assert!(context.workspace_path.is_none());
427		assert!(context.current_file.is_none());
428		assert!(!context.active_editor);
429	}
430
431	#[test]
432	fn test_wildcard_matching() {
433		let matcher = WildMatch::new("*");
434		assert!(matcher.matches("anything"));
435
436		let matcher = WildMatch::new("prefix*");
437		assert!(matcher.matches("prefix_suffix"));
438		assert!(!matcher.matches("noprefix_suffix"));
439
440		let matcher = WildMatch::new("*suffix");
441		assert!(matcher.matches("prefix_suffix"));
442		assert!(!matcher.matches("prefix_suffix_not"));
443	}
444}