Skip to main content

Mountain/Environment/
MountainEnvironment.rs

1//! # MountainEnvironment
2//!
3//! Top-level dependency injection container wrapping application state,
4//! runtime, and optional Air service client.
5//!
6//! Constructed once during startup and shared as `Arc<MountainEnvironment>`
7//! across all subsystems. Implements `Environment`, `Requires`, and
8//! `ExtensionManagementService` traits from the Common crate.
9//!
10//! Components declare provider requirements via `Requires<T>`. Resolution
11//! happens at compile time through `impl_provider!`.
12
13use std::sync::Arc;
14
15// Import Air service client when Air integration is enabled
16#[cfg(feature = "AirIntegration")]
17use AirLibrary::Vine::Generated::air::air_service_client::AirServiceClient;
18use CommonLibrary::{
19	Command::CommandExecutor::CommandExecutor,
20	Configuration::{ConfigurationInspector::ConfigurationInspector, ConfigurationProvider::ConfigurationProvider},
21	CustomEditor::CustomEditorProvider::CustomEditorProvider,
22	Debug::DebugService::DebugService,
23	Diagnostic::DiagnosticManager::DiagnosticManager,
24	Document::DocumentProvider::DocumentProvider,
25	Environment::{Environment::Environment, Requires::Requires},
26	Error::CommonError::CommonError,
27	ExtensionManagement::ExtensionManagementService::ExtensionManagementService,
28	FileSystem::{
29		FileSystemReader::FileSystemReader,
30		FileSystemWriter::FileSystemWriter,
31		FileWatcherProvider::FileWatcherProvider,
32	},
33	IPC::IPCProvider::IPCProvider,
34	Keybinding::KeybindingProvider::KeybindingProvider,
35	LanguageFeature::LanguageFeatureProviderRegistry::LanguageFeatureProviderRegistry,
36	Output::OutputChannelManager::OutputChannelManager,
37	Search::SearchProvider::SearchProvider,
38	Secret::SecretProvider::SecretProvider,
39	SourceControlManagement::SourceControlManagementProvider::SourceControlManagementProvider,
40	StatusBar::StatusBarProvider::StatusBarProvider,
41	Storage::StorageProvider::StorageProvider,
42	Synchronization::SynchronizationProvider::SynchronizationProvider,
43	Terminal::TerminalProvider::TerminalProvider,
44	Testing::TestController::TestController,
45	TreeView::TreeViewProvider::TreeViewProvider,
46	UserInterface::UserInterfaceProvider::UserInterfaceProvider,
47	Webview::WebviewProvider::WebviewProvider,
48	Workspace::{WorkspaceEditApplier::WorkspaceEditApplier, WorkspaceProvider::WorkspaceProvider},
49};
50use async_trait::async_trait;
51use serde_json::Value;
52use tauri::{AppHandle, Wry};
53
54use crate::{
55	ApplicationState::{
56		DTO::ExtensionDescriptionStateDTO::ExtensionDescriptionStateDTO,
57		State::ApplicationState::ApplicationState,
58	},
59	dev_log,
60};
61// Import the macro for generating trait implementations
62// Note: Macros annotated with #[macro_export] are available at crate root
63use crate::impl_provider;
64
65/// The concrete `Environment` for the Mountain application.
66#[derive(Clone)]
67/// Primary dependency injection container for the Mountain code editor.
68///
69/// Holds thread-safe references to application state, the async runtime,
70/// and the optional Air service client. Provider trait implementations
71/// are generated by `impl_provider!` to enable `Requires<T>` lookups.
72pub struct MountainEnvironment {
73	/// Tauri application handle for window and event management.
74	pub ApplicationHandle:AppHandle<Wry>,
75
76	/// Thread-safe shared application state machine.
77	pub ApplicationState:Arc<ApplicationState>,
78
79	/// Optional Air gRPC client for cloud-based services.
80	///
81	/// When provided (`AirIntegration` feature enabled), providers such as
82	/// `SecretProvider` and `UpdateService` can delegate to the Air service.
83	#[cfg(feature = "AirIntegration")]
84	pub AirClient:Option<AirServiceClient<tonic::transport::Channel>>,
85}
86
87impl MountainEnvironment {
88	/// Creates a new `MountainEnvironment` without an Air client.
89	///
90	/// This is the standard constructor used when the `AirIntegration`
91	/// feature is either disabled or the Air service is not yet available.
92	#[allow(unused_mut)]
93	pub fn Create(ApplicationHandle:AppHandle<Wry>, ApplicationState:Arc<ApplicationState>) -> Self {
94		dev_log!("lifecycle", "[MountainEnvironment] New instance created.");
95
96		#[cfg(feature = "AirIntegration")]
97		{
98			Self { ApplicationHandle, ApplicationState, AirClient:None }
99		}
100
101		#[cfg(not(feature = "AirIntegration"))]
102		{
103			Self { ApplicationHandle, ApplicationState }
104		}
105	}
106
107	/// Creates a new `MountainEnvironment` with an optional Air gRPC client.
108	///
109	/// Use this constructor when the Air service is available at startup.
110	/// The `AirClient` enables cloud-backed operations such as remote secret
111	/// storage and over-the-air updates.
112	#[cfg(feature = "AirIntegration")]
113	pub fn CreateWithAir(
114		ApplicationHandle:AppHandle<Wry>,
115
116		ApplicationState:Arc<ApplicationState>,
117
118		AirClient:Option<AirServiceClient<tonic::transport::Channel>>,
119	) -> Self {
120		dev_log!(
121			"lifecycle",
122			"[MountainEnvironment] New instance created with Air client: {}",
123			AirClient.is_some()
124		);
125
126		Self { ApplicationHandle, ApplicationState, AirClient }
127	}
128
129	/// Replaces the Air gRPC client at runtime.
130	///
131	/// Call this when the Air service becomes available after the environment
132	/// was already constructed, or to swap between different Air endpoints.
133	/// Providers that hold a reference to `MountainEnvironment` will see
134	/// the updated client on their next `IsAirAvailable` call.
135	#[cfg(feature = "AirIntegration")]
136	pub fn SetAirClient(&mut self, AirClient:Option<AirServiceClient<tonic::transport::Channel>>) {
137		dev_log!("lifecycle", "[MountainEnvironment] Air client updated: {}", AirClient.is_some());
138
139		self.AirClient = AirClient;
140	}
141
142	/// Checks whether the Air service client is configured and available.
143	///
144	/// Returns `true` if an Air gRPC client is attached, `false` otherwise.
145	/// Note: the actual gRPC health check is deferred until the AirClient
146	/// wrapper supports mutable references for health-check calls.
147	#[cfg(feature = "AirIntegration")]
148	pub async fn IsAirAvailable(&self) -> bool {
149		// TODO: implement proper health check when AirClient wrapper supports
150		// &mut self for health_check RPC. MountainEnvironment stores an
151		// immutable reference, so this is blocked on the wrapper integration.
152		if let Some(_AirClient) = &self.AirClient {
153			// For now, assume Air is available if the client exists
154			dev_log!(
155				"lifecycle",
156				"[MountainEnvironment] Air client configured (health check disabled pending integration)"
157			);
158
159			true
160		} else {
161			dev_log!("lifecycle", "[MountainEnvironment] No Air client configured");
162
163			false
164		}
165	}
166
167	/// Returns `false` unconditionally when Air integration is disabled.
168	#[cfg(not(feature = "AirIntegration"))]
169	pub async fn IsAirAvailable(&self) -> bool { false }
170
171	/// Scans a single directory for installed extensions.
172	///
173	/// Walks each subdirectory looking for `package.json`. Parsed packages
174	/// are enriched with an `ExtensionLocation` field containing the
175	/// absolute path. Returns all successfully parsed packages; logs
176	/// warnings for parse/read failures without propagating errors.
177	async fn ScanExtensionDirectory(&self, path:&std::path::PathBuf) -> Result<Vec<serde_json::Value>, CommonError> {
178		use std::fs;
179
180		let mut extensions = Vec::new();
181
182		// Check if directory exists
183		if !path.exists() || !path.is_dir() {
184			dev_log!(
185				"lifecycle",
186				"warn: [ExtensionManagementService] Extension directory does not exist: {:?}",
187				path
188			);
189
190			return Ok(extensions);
191		}
192
193		// Read directory contents
194		let entries = fs::read_dir(path).map_err(|error| {
195			CommonError::FileSystemIO {
196				Path:path.clone(),
197				Description:format!("Failed to read extension directory: {}", error),
198			}
199		})?;
200
201		for entry in entries {
202			let entry = entry.map_err(|error| {
203				CommonError::FileSystemIO {
204					Path:path.clone(),
205					Description:format!("Failed to read directory entry: {}", error),
206				}
207			})?;
208
209			let entry_path = entry.path();
210
211			if entry_path.is_dir() {
212				// Look for package.json in the extension directory
213				let package_json_path = entry_path.join("package.json");
214
215				if package_json_path.exists() {
216					match fs::read_to_string(&package_json_path) {
217						Ok(content) => {
218							match serde_json::from_str::<Value>(&content) {
219								Ok(mut package_json) => {
220									// Add extension location information
221									if let Some(obj) = package_json.as_object_mut() {
222										obj.insert(
223											"ExtensionLocation".to_string(),
224											Value::String(entry_path.to_string_lossy().to_string()),
225										);
226									}
227
228									extensions.push(package_json);
229
230									dev_log!(
231										"lifecycle",
232										"[ExtensionManagementService] Found extension at: {:?}",
233										entry_path
234									);
235								},
236
237								Err(error) => {
238									dev_log!(
239										"lifecycle",
240										"warn: [ExtensionManagementService] Failed to parse package.json at {:?}: {}",
241										package_json_path,
242										error
243									);
244								},
245							}
246						},
247
248						Err(error) => {
249							dev_log!(
250								"lifecycle",
251								"warn: [ExtensionManagementService] Failed to read package.json at {:?}: {}",
252								package_json_path,
253								error
254							);
255						},
256					}
257				}
258			}
259		}
260
261		Ok(extensions)
262	}
263}
264
265impl Environment for MountainEnvironment {}
266
267/// Extension discovery implementation.
268#[async_trait]
269impl ExtensionManagementService for MountainEnvironment {
270	/// Scans registered extension directories, parses package.json files,
271	/// and populates `ApplicationState.Extension.ScannedExtensions`.
272	/// Errors during individual extension parsing are logged but do not
273	/// abort the scan.
274	async fn ScanForExtensions(&self) -> Result<(), CommonError> {
275		dev_log!("lifecycle", "[ExtensionManagementService] Scanning for extensions...");
276
277		// Get the extension scan paths from ApplicationState
278		let ScanPaths:Vec<std::path::PathBuf> = {
279			let ScanPathsGuard = self
280				.ApplicationState
281				.Extension
282				.Registry
283				.ExtensionScanPaths
284				.lock()
285				.map_err(|Error| CommonError::StateLockPoisoned { Context:Error.to_string() })?;
286
287			ScanPathsGuard.clone()
288		};
289
290		let mut extensions = Vec::new();
291
292		// Scan each extension directory
293		for path in ScanPaths {
294			if let Ok(mut scan_result) = self.ScanExtensionDirectory(&path).await {
295				extensions.append(&mut scan_result);
296			}
297		}
298
299		// Update ApplicationState with scanned extensions
300		let mut ScannedExtensionsGuard = self
301			.ApplicationState
302			.Extension
303			.ScannedExtensions
304			.ScannedExtensions
305			.lock()
306			.map_err(|Error| CommonError::StateLockPoisoned { Context:Error.to_string() })?;
307
308		ScannedExtensionsGuard.clear();
309
310		for extension in extensions {
311			// The scanner returns camelCase JSON (serde rename_all = "camelCase").
312			// Deserialize directly into ExtensionDescriptionStateDTO.
313			match serde_json::from_value::<ExtensionDescriptionStateDTO>(extension.clone()) {
314				Ok(Dto) => {
315					// Use identifier.value or fall back to name
316					let Key = Dto
317						.Identifier
318						.as_object()
319						.and_then(|O| O.get("value"))
320						.and_then(|V| V.as_str())
321						.unwrap_or(&Dto.Name)
322						.to_string();
323
324					if !Key.is_empty() {
325						ScannedExtensionsGuard.insert(Key, Dto);
326					}
327				},
328
329				Err(Error) => {
330					let Name = extension.get("name").and_then(|V| V.as_str()).unwrap_or("?");
331
332					dev_log!(
333						"lifecycle",
334						"warn: [ExtensionManagementService] Failed to parse extension '{}': {}",
335						Name,
336						Error
337					);
338				},
339			}
340		}
341
342		dev_log!(
343			"lifecycle",
344			"[ExtensionManagementService] Found {} extensions",
345			ScannedExtensionsGuard.len()
346		);
347
348		Ok(())
349	}
350
351	/// Returns all scanned extensions as a JSON array.
352	///
353	/// Null values in the result indicate extensions that failed to
354	/// serialize (these are logged and skipped rather than causing
355	/// an error).
356	async fn GetExtensions(&self) -> Result<Vec<Value>, CommonError> {
357		let ScannedExtensionsGuard = self
358			.ApplicationState
359			.Extension
360			.ScannedExtensions
361			.ScannedExtensions
362			.lock()
363			.map_err(|Error| CommonError::StateLockPoisoned { Context:Error.to_string() })?;
364
365		let GuardLen = ScannedExtensionsGuard.len();
366
367		let Extensions:Vec<Value> = ScannedExtensionsGuard
368			.values()
369			.map(|ext| serde_json::to_value(ext).unwrap_or(Value::Null))
370			.collect();
371
372		let SerializedCount = Extensions.iter().filter(|v| !v.is_null()).count();
373
374		dev_log!(
375			"lifecycle",
376			"[MountainEnvironment] GetExtensions: ScannedExtensions map={} entries, serialized={} non-null",
377			GuardLen,
378			SerializedCount
379		);
380
381		Ok(Extensions)
382	}
383
384	/// Returns a single extension by its identifier as a JSON object.
385	///
386	/// Reconstructs the JSON from the stored `ExtensionDescriptionStateDTO`.
387	/// Returns `Ok(None)` if the extension is not found.
388	async fn GetExtension(&self, id:String) -> Result<Option<Value>, CommonError> {
389		let ScannedExtensionsGuard = self
390			.ApplicationState
391			.Extension
392			.ScannedExtensions
393			.ScannedExtensions
394			.lock()
395			.map_err(|Error| CommonError::StateLockPoisoned { Context:Error.to_string() })?;
396
397		if let Some(extension_dto) = ScannedExtensionsGuard.get(&id) {
398			// Convert ExtensionDescriptionStateDTO back to JSON Value
399			let mut extension_value = serde_json::Map::new();
400
401			extension_value.insert("Identifier".to_string(), extension_dto.Identifier.clone());
402
403			extension_value.insert("Name".to_string(), Value::String(extension_dto.Name.clone()));
404
405			extension_value.insert("Version".to_string(), Value::String(extension_dto.Version.clone()));
406
407			extension_value.insert("Publisher".to_string(), Value::String(extension_dto.Publisher.clone()));
408
409			extension_value.insert("Engines".to_string(), extension_dto.Engines.clone());
410
411			if let Some(main) = &extension_dto.Main {
412				extension_value.insert("Main".to_string(), Value::String(main.clone()));
413			}
414
415			if let Some(browser) = &extension_dto.Browser {
416				extension_value.insert("Browser".to_string(), Value::String(browser.clone()));
417			}
418
419			if let Some(module_type) = &extension_dto.ModuleType {
420				extension_value.insert("ModuleType".to_string(), Value::String(module_type.clone()));
421			}
422
423			extension_value.insert("IsBuiltin".to_string(), Value::Bool(extension_dto.IsBuiltin));
424
425			extension_value.insert("IsUnderDevelopment".to_string(), Value::Bool(extension_dto.IsUnderDevelopment));
426
427			extension_value.insert("ExtensionLocation".to_string(), extension_dto.ExtensionLocation.clone());
428
429			if let Some(activation_events) = &extension_dto.ActivationEvents {
430				let events:Vec<Value> = activation_events.iter().map(|e| Value::String(e.clone())).collect();
431
432				extension_value.insert("ActivationEvents".to_string(), Value::Array(events));
433			}
434
435			if let Some(contributes) = &extension_dto.Contributes {
436				extension_value.insert("Contributes".to_string(), contributes.clone());
437			}
438
439			Ok(Some(Value::Object(extension_value)))
440		} else {
441			Ok(None)
442		}
443	}
444}
445
446// Capability requirement implementations (DI) - all generated by impl_provider!
447
448// Command and Configuration
449impl_provider!(CommandExecutor);
450
451impl_provider!(ConfigurationProvider);
452
453impl_provider!(ConfigurationInspector);
454
455// Custom Editor and Debug
456impl_provider!(CustomEditorProvider);
457
458impl_provider!(DebugService);
459
460// Document and Diagnostic
461impl_provider!(DocumentProvider);
462
463impl_provider!(DiagnosticManager);
464
465// File System
466impl_provider!(FileSystemReader);
467
468impl_provider!(FileSystemWriter);
469
470impl_provider!(FileWatcherProvider);
471
472// IPC and Keybinding
473impl_provider!(IPCProvider);
474
475impl_provider!(KeybindingProvider);
476
477// Language Features and Output
478impl_provider!(LanguageFeatureProviderRegistry);
479
480impl_provider!(OutputChannelManager);
481
482// Secret and SCM
483impl_provider!(SecretProvider);
484
485impl_provider!(SourceControlManagementProvider);
486
487// Status Bar and Storage
488impl_provider!(StatusBarProvider);
489
490impl_provider!(StorageProvider);
491
492// Synchronization and Terminal
493impl_provider!(SynchronizationProvider);
494
495impl_provider!(TerminalProvider);
496
497// Test and Tree View
498impl_provider!(TestController);
499
500impl_provider!(TreeViewProvider);
501
502// UI and Webview
503impl_provider!(UserInterfaceProvider);
504
505impl_provider!(WebviewProvider);
506
507// Workspace
508impl_provider!(WorkspaceProvider);
509
510impl_provider!(WorkspaceEditApplier);
511
512// Extension Management and Search
513impl_provider!(ExtensionManagementService);
514
515impl_provider!(SearchProvider);