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	Common::traits::ExtensionContext,
15	Host::{ActivationResult, HostConfig},
16	Host::ExtensionManager::{ExtensionManagerImpl, ExtensionState, ExtensionInfo},
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	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	extension_id:String,
98	/// Activation events
99	events:Vec<ActivationEvent>,
100	/// Activation function path
101	activation_function:String,
102	/// Whether extension is currently active
103	is_active:bool,
104	/// Last activation time
105	last_activation:Option<u64>,
106}
107
108/// Activation record for tracking
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct ActivationRecord {
111	/// Extension ID
112	pub extension_id:String,
113	/// Activation events
114	pub events:Vec<String>,
115	/// Activation time (Unix timestamp)
116	pub timestamp:u64,
117	/// Duration in milliseconds
118	pub duration_ms:u64,
119	/// Success flag
120	pub success:bool,
121	/// Error message (if failed)
122	pub error:Option<String>,
123}
124
125/// Activation context passed to extensions
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ActivationContext {
128	/// Workspace root path
129	pub workspace_path:Option<PathBuf>,
130	/// Current file path
131	pub current_file:Option<PathBuf>,
132	/// Current language ID
133	pub language_id:Option<String>,
134	/// Active editor
135	pub active_editor:bool,
136	/// Environment variables
137	pub environment:HashMap<String, String>,
138	/// Additional context data
139	pub additional_data:serde_json::Value,
140}
141
142impl Default for ActivationContext {
143	fn default() -> Self {
144		Self {
145			workspace_path:None,
146			current_file:None,
147			language_id:None,
148			active_editor:false,
149			environment:HashMap::new(),
150			additional_data:serde_json::Value::Null,
151		}
152	}
153}
154
155impl ActivationEngine {
156	/// Create a new activation engine
157	pub fn new(extension_manager:Arc<ExtensionManagerImpl>, config:HostConfig) -> Self {
158		Self {
159			extension_manager,
160			config,
161			event_handlers:Arc::new(RwLock::new(HashMap::new())),
162			activation_history:Arc::new(RwLock::new(Vec::new())),
163		}
164	}
165
166	/// Activate an extension
167	#[instrument(skip(self, extension_id))]
168	pub async fn activate(&self, extension_id:&str) -> Result<ActivationResult> {
169		info!("Activating extension: {}", extension_id);
170
171		let start = std::time::Instant::now();
172
173		// Get extension info
174		let extension_info = self
175			.extension_manager
176			.get_extension(extension_id)
177			.await
178			.ok_or_else(|| anyhow::anyhow!("Extension not found: {}", extension_id))?;
179
180		// Check if already active
181		let handlers = self.event_handlers.read().await;
182		if let Some(handler) = handlers.get(extension_id) {
183			if handler.is_active {
184				warn!("Extension already active: {}", extension_id);
185				return Ok(ActivationResult {
186					extension_id:extension_id.to_string(),
187					success:true,
188					time_ms:0,
189					error:None,
190					contributes:Vec::new(),
191				});
192			}
193		}
194		drop(handlers);
195
196		// Parse activation events
197		let activation_events:Result<Vec<ActivationEvent>> = extension_info
198			.activation_events
199			.iter()
200			.map(|e| ActivationEvent::from_str(e))
201			.collect();
202		let activation_events = activation_events.with_context(|| "Failed to parse activation events")?;
203
204		// Create activation context
205		let context = ActivationContext::default();
206
207		// Perform activation (in real implementation, this would call the extension's
208		// activate function)
209		let activation_result = self
210			.perform_activation(extension_id, &context)
211			.await
212			.context("Activation failed")?;
213
214		let elapsed_ms = start.elapsed().as_millis() as u64;
215
216		// Record activation
217		let record = ActivationRecord {
218			extension_id:extension_id.to_string(),
219			events:extension_info.activation_events.clone(),
220			timestamp:std::time::SystemTime::now()
221				.duration_since(std::time::UNIX_EPOCH)
222				.map(|d| d.as_secs())
223				.unwrap_or(0),
224			duration_ms:elapsed_ms,
225			success:activation_result.success,
226			error:None,
227		};
228
229		// Save timestamp for later use
230		let activation_timestamp = record.timestamp;
231
232		self.activation_history.write().await.push(record);
233
234		// Update extension state
235		self.extension_manager
236			.update_state(extension_id, ExtensionState::Activated)
237			.await?;
238
239		// Register handler
240		let mut handlers = self.event_handlers.write().await;
241		handlers.insert(
242			extension_id.to_string(),
243			ActivationHandler {
244				extension_id:extension_id.to_string(),
245				events:activation_events,
246				activation_function:"activate".to_string(),
247				is_active:true,
248				last_activation:Some(activation_timestamp),
249			},
250		);
251
252		info!("Extension activated in {}ms: {}", elapsed_ms, extension_id);
253
254		Ok(ActivationResult {
255			extension_id:extension_id.to_string(),
256			success:true,
257			time_ms:elapsed_ms,
258			error:None,
259			contributes:extension_info.capabilities.clone(),
260		})
261	}
262
263	/// Deactivate an extension
264	#[instrument(skip(self, extension_id))]
265	pub async fn deactivate(&self, extension_id:&str) -> Result<()> {
266		info!("Deactivating extension: {}", extension_id);
267
268		// Remove handler
269		let mut handlers = self.event_handlers.write().await;
270		if let Some(mut handler) = handlers.remove(extension_id) {
271			handler.is_active = false;
272		}
273
274		// Update extension state
275		self.extension_manager
276			.update_state(extension_id, ExtensionState::Deactivated)
277			.await?;
278
279		info!("Extension deactivated: {}", extension_id);
280
281		Ok(())
282	}
283
284	/// Trigger activation for certain events
285	#[instrument(skip(self, event, context))]
286	pub async fn trigger_activation(&self, event:&str, context:&ActivationContext) -> Result<Vec<ActivationResult>> {
287		info!("Triggering activation for event: {}", event);
288
289		let activation_event = ActivationEvent::from_str(event)?;
290		let handlers = self.event_handlers.read().await;
291
292		let mut results = Vec::new();
293
294		for (extension_id, handler) in handlers.iter() {
295			// Check if extension should activate on this event
296			if handler.is_active {
297				continue; // Already active
298			}
299
300			if self.should_activate(&activation_event, &handler.events) {
301				debug!("Activating extension {} for event: {}", extension_id, event);
302				match self.activate(extension_id).await {
303					Ok(result) => results.push(result),
304					Err(e) => {
305						warn!("Failed to activate extension {} for event {}: {}", extension_id, event, e);
306					},
307				}
308			}
309		}
310
311		Ok(results)
312	}
313
314	/// Check if extension should activate for given event
315	fn should_activate(&self, activation_event:&ActivationEvent, events:&[ActivationEvent]) -> bool {
316		events.iter().any(|e| {
317			match (e, activation_event) {
318				(ActivationEvent::Star, _) => true,
319				(ActivationEvent::Custom(pattern), _) => {
320					WildMatch::new(pattern).matches(activation_event.to_string().as_str())
321				},
322				_ => e == activation_event,
323			}
324		})
325	}
326
327	/// Perform actual activation (placeholder - would call extension's activate
328	/// function)
329	async fn perform_activation(&self, extension_id:&str, context:&ActivationContext) -> Result<ActivationResult> {
330		// In real implementation, this would:
331		// 1. Call the extension's activate function
332		// 2. Pass the activation context
333		// 3. Wait for activation to complete
334		// 4. Handle any errors
335
336		debug!("Performing activation for extension: {}", extension_id);
337
338		// Placeholder implementation
339		Ok(ActivationResult {
340			extension_id:extension_id.to_string(),
341			success:true,
342			time_ms:0,
343			error:None,
344			contributes:Vec::new(),
345		})
346	}
347
348	/// Get activation history
349	pub async fn get_activation_history(&self) -> Vec<ActivationRecord> { self.activation_history.read().await.clone() }
350
351	/// Get activation history for a specific extension
352	pub async fn get_activation_history_for_extension(&self, extension_id:&str) -> Vec<ActivationRecord> {
353		self.activation_history
354			.read()
355			.await
356			.iter()
357			.filter(|r| r.extension_id == extension_id)
358			.cloned()
359			.collect()
360	}
361}
362
363/// Simple wildcard matching for flexible activation events
364struct WildMatch {
365	pattern:String,
366}
367
368impl WildMatch {
369	fn new(pattern:&str) -> Self { Self { pattern:pattern.to_lowercase() } }
370
371	fn matches(&self, text:&str) -> bool {
372		let text = text.to_lowercase();
373
374		// Handle * wildcard
375		if self.pattern == "*" {
376			return true;
377		}
378
379		// Handle patterns starting with *
380		if self.pattern.starts_with('*') {
381			let suffix = &self.pattern[1..];
382			return text.ends_with(suffix);
383		}
384
385		// Handle patterns ending with *
386		if self.pattern.ends_with('*') {
387			let prefix = &self.pattern[..self.pattern.len() - 1];
388			return text.starts_with(prefix);
389		}
390
391		// Exact match
392		self.pattern == text
393	}
394}
395
396#[cfg(test)]
397mod tests {
398	use super::*;
399
400	#[test]
401	fn test_activation_event_parsing() {
402		let event = ActivationEvent::from_str("*").unwrap();
403		assert_eq!(event, ActivationEvent::Star);
404
405		let event = ActivationEvent::from_str("onCommand:test.command").unwrap();
406		assert_eq!(event, ActivationEvent::Command("test.command".to_string()));
407
408		let event = ActivationEvent::from_str("onLanguage:rust").unwrap();
409		assert_eq!(event, ActivationEvent::Language("rust".to_string()));
410	}
411
412	#[test]
413	fn test_activation_event_to_string() {
414		assert_eq!(ActivationEvent::Star.to_string(), "*");
415		assert_eq!(ActivationEvent::Command("test".to_string()).to_string(), "onCommand:test");
416		assert_eq!(ActivationEvent::Language("rust".to_string()).to_string(), "onLanguage:rust");
417	}
418
419	#[test]
420	fn test_activation_context_default() {
421		let context = ActivationContext::default();
422		assert!(context.workspace_path.is_none());
423		assert!(context.current_file.is_none());
424		assert!(!context.active_editor);
425	}
426
427	#[test]
428	fn test_wildcard_matching() {
429		let matcher = WildMatch::new("*");
430		assert!(matcher.matches("anything"));
431
432		let matcher = WildMatch::new("prefix*");
433		assert!(matcher.matches("prefix_suffix"));
434		assert!(!matcher.matches("noprefix_suffix"));
435
436		let matcher = WildMatch::new("*suffix");
437		assert!(matcher.matches("prefix_suffix"));
438		assert!(!matcher.matches("prefix_suffix_not"));
439	}
440}