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