Mountain/
Binary.rs

1// File: Mountain/Source/Binary.rs
2// Role: Main entry point for the Mountain native host application.
3// Responsibilities:
4//   - Orchestrate the entire application lifecycle.
5//   - Initialize logging (via Tauri plugin), the Echo scheduler,
6// 	 ApplicationState, and ApplicationRunTime.
7//   - Serve assets via http://localhost (determined by portpicker) to support
8// 	 Service Workers.
9//   - Parse command-line arguments to open a workspace.
10//   - Bootstrap native command registration.
11//   - Set up the Vine gRPC server and spawn the Cocoon sidecar process.
12//   - Create and customize the main Tauri application window.
13//   - Manage the main application event loop and graceful shutdown.
14//
15// Logging strategy:
16//   - Release default: Info (low noise) unless RUST_LOG overrides.
17//   - Debug default: Debug (high fidelity) unless RUST_LOG overrides.
18//   - Very noisy deps are capped using level_for(...) and filter(...).
19//
20//
21// NOTE (Webview logs):
22//   - To see Rust logs in the Webview console, enable TargetKind::Webview and
23// 	 call attachConsole() in the frontend.
24
25//! # Mountain Binary Entry Point
26//!
27//! This file orchestrates the entire application lifecycle. It is responsible
28//! for setting up logging, initializing the `Echo` scheduler, the core
29//! `ApplicationState`, the `ApplicationRunTime`, the `Vine` gRPC server, the
30//! `Cocoon` sidecar process, and the Tauri application window and event loop.
31
32#![allow(non_snake_case, non_camel_case_types)]
33
34use std::{
35	path::PathBuf,
36	sync::{Arc, Mutex},
37};
38
39use Echo::Scheduler::SchedulerBuilder::SchedulerBuilder;
40use log::{LevelFilter, debug, error, info, trace, warn};
41use tauri::{AppHandle, Manager, RunEvent, Wry};
42use tauri_plugin_log::{RotationStrategy, Target, TargetKind, TimezoneStrategy};
43
44use crate::{
45	ApplicationState::{
46		ApplicationState::{ApplicationState, MapLockError},
47		Internal::ScanAndPopulateExtensions,
48	},
49	Command,
50	Environment::{ConfigurationProvider::InitializeAndMergeConfigurations, MountainEnvironment::MountainEnvironment},
51	ProcessManagement::{CocoonManagement::InitializeCocoon, InitializationData},
52	RunTime::ApplicationRunTime::ApplicationRunTime,
53	Vine,
54};
55
56// =============================================================================
57// Debug Helpers (Highly Verbose, Low Intrusion)
58// =============================================================================
59
60/// Logs a checkpoint message at TRACE level (for "every step" tracing).
61macro_rules! TraceStep {
62	($($arg:tt)*) => {{
63		trace!($($arg)*);
64	}};
65}
66
67// =============================================================================
68// IPC Bridge Commands
69// =============================================================================
70
71/// A Tauri command to provide the initial workbench configuration to the Sky
72/// frontend.
73#[tauri::command]
74async fn MountainGetWorkbenchConfiguration(
75	ApplicationHandle:AppHandle<Wry>,
76	State:tauri::State<'_, Arc<ApplicationState>>,
77) -> Result<serde_json::Value, String> {
78	info!("[IPC] [WorkbenchConfig] Request received.");
79
80	debug!("[IPC] [WorkbenchConfig] Constructing sandbox configuration...");
81
82	let Config = InitializationData::ConstructSandboxConfiguration(&ApplicationHandle, &State)
83		.await
84		.map_err(|Error| {
85			error!("[IPC] [WorkbenchConfig] Failed: {}", Error);
86
87			Error.to_string()
88		})?;
89
90	debug!("[IPC] [WorkbenchConfig] Success. Returning payload.");
91
92	Ok(Config)
93}
94
95// =============================================================================
96// Binary Entrypoint
97// =============================================================================
98
99/// The main asynchronous function that sets up and runs the application.
100pub fn Fn() {
101	// -------------------------------------------------------------------------
102	// [Boot] [Runtime] Tokio runtime creation
103	// -------------------------------------------------------------------------
104	TraceStep!("[Boot] [Runtime] Building Tokio runtime...");
105
106	let Runtime = tokio::runtime::Builder::new_multi_thread()
107		.enable_all()
108		.build()
109		.expect("FATAL: Cannot build Tokio runtime.");
110
111	TraceStep!("[Boot] [Runtime] Tokio runtime built.");
112
113	Runtime.block_on(async {
114		// ---------------------------------------------------------------------
115		// [Boot] [Args] CLI parsing
116		// ---------------------------------------------------------------------
117		debug!("[Boot] [Args] Collecting CLI args...");
118
119		let CliArgs:Vec<String> = std::env::args().collect();
120
121		debug!("[Boot] [Args] CLI Args: {:?}", CliArgs);
122
123		let WorkSpacePathArgument = CliArgs.iter().find(|Arg| Arg.ends_with(".code-workspace"));
124
125		TraceStep!("[Boot] [Args] Workspace arg present: {}", WorkSpacePathArgument.is_some());
126
127		let (InitialFolders, WorkSpaceConfigurationPath) = if let Some(PathString) = WorkSpacePathArgument {
128			let Path = PathBuf::from(PathString);
129
130			println!("[Boot] [Args] Found workspace argument: {}", Path.display());
131
132			debug!("[Boot] [Workspace] Reading workspace file: {}", Path.display());
133
134			match std::fs::read_to_string(&Path) {
135				Ok(Content) => {
136					debug!("[Boot] [Workspace] Workspace file read ok ({} bytes).", Content.len());
137
138					crate::WorkSpace::WorkSpaceFileService::ParseWorkSpaceFile(&Path, &Content)
139						.map(|Folders| {
140							debug!("[Boot] [Workspace] Parsed workspace ok. folder_count={}", Folders.len());
141
142							(Folders, Some(Path))
143						})
144						.unwrap_or_else(|Error| {
145							error!("[Boot] [Workspace] Parse failed: {}. Continuing without workspace.", Error);
146
147							(Vec::new(), None)
148						})
149				},
150				Err(Error) => {
151					error!("[Boot] [Workspace] Read failed: {}. Continuing without workspace.", Error);
152
153					(Vec::new(), None)
154				},
155			}
156		} else {
157			debug!("[Boot] [Workspace] No workspace provided. Starting empty.");
158
159			(Vec::new(), None)
160		};
161
162		// ---------------------------------------------------------------------
163		// [Boot] [State] ApplicationState
164		// ---------------------------------------------------------------------
165		debug!("[Boot] [State] Building ApplicationState...");
166
167		let AppState = Arc::new(ApplicationState {
168			WorkSpaceFolders:Arc::new(Mutex::new(InitialFolders)),
169			WorkSpaceConfigurationPath:Arc::new(Mutex::new(WorkSpaceConfigurationPath)),
170			..ApplicationState::default()
171		});
172
173		debug!("[Boot] [State] ApplicationState created and managed.");
174
175		// ---------------------------------------------------------------------
176		// [Boot] [Echo] Scheduler
177		// ---------------------------------------------------------------------
178		let NumberOfWorkers = num_cpus::get().max(2);
179
180		debug!("[Boot] [Echo] Creating scheduler. workers={}", NumberOfWorkers);
181
182		let Scheduler = SchedulerBuilder::Create().WithWorkerCount(NumberOfWorkers).Build();
183
184		debug!("[Boot] [Echo] Scheduler built.");
185
186		let SchedulerForShutdown = Arc::new(Scheduler);
187
188		let SchedulerForRunTime = SchedulerForShutdown.clone();
189
190		TraceStep!("[Boot] [Echo] Scheduler handles prepared.");
191
192		// ---------------------------------------------------------------------
193		// [Boot] [Localhost] Port selection (for Service Workers & stable origin)
194		// ---------------------------------------------------------------------
195		debug!("[Boot] [Localhost] Selecting unused port...");
196
197		let ServerPort =
198			portpicker::pick_unused_port().expect("FATAL: Failed to find a free port for Localhost Server");
199
200		debug!("[Boot] [Localhost] Selected port={}", ServerPort);
201
202		let LocalhostUrl = format!("http://localhost:{}", ServerPort);
203
204		println!("[Boot] [Localhost] Selected: {} ({})", ServerPort, LocalhostUrl);
205
206		// ---------------------------------------------------------------------
207		// [Boot] [Logging] Log level resolution
208		// ---------------------------------------------------------------------
209		// Supported by tauri_plugin_log:
210		// - level(...) sets the max log level
211		// - level_for(...) overrides max level per module
212		// - filter(...) discards by metadata
213		// - format(...) custom line formatting
214		// - targets([...]) configures outputs (Stdout/Webview/File)
215		// - timezone_strategy/rotation_strategy for file behavior
216		let EnvLogLevel = std::env::var("RUST_LOG").ok().and_then(|s| s.parse::<LevelFilter>().ok());
217
218		let DefaultLogLevel = if cfg!(debug_assertions) { LevelFilter::Debug } else { LevelFilter::Info };
219
220		let LogLevel = EnvLogLevel.unwrap_or(DefaultLogLevel);
221
222		// Log *very early* using stderr/stdout (logger not yet installed).
223		// Once the plugin is installed, subsequent logs will go through it.
224		eprintln!(
225			"[Boot] [Logging] Resolved LogLevel={:?} (env={:?}, default={:?})",
226			LogLevel, EnvLogLevel, DefaultLogLevel
227		);
228
229		// ---------------------------------------------------------------------
230		// [Boot] [Tauri] Builder
231		// ---------------------------------------------------------------------
232		#[allow(unused_mut)]
233		let mut Builder = tauri::Builder::default();
234
235		#[cfg(any(windows, target_os = "linux"))]
236		{
237			Builder = Builder.any_thread();
238		}
239
240		Builder
241			// -----------------------------------------------------------------
242			// [Boot] [Tauri] [Plugins] Logging
243			// -----------------------------------------------------------------
244			.plugin(
245				tauri_plugin_log::Builder::new()
246					.targets([
247						Target::new(TargetKind::Stdout),
248						Target::new(TargetKind::LogDir {
249							file_name: Some("Mountain.log".into()),
250						}),
251						Target::new(TargetKind::Webview),
252					])
253					.timezone_strategy(TimezoneStrategy::UseLocal)
254					.rotation_strategy(RotationStrategy::KeepAll)
255					.level(LogLevel)
256					// Cap common noisy deps even when RUST_LOG=trace.
257					.level_for("hyper", LevelFilter::Info)
258					.level_for("mio", LevelFilter::Info)
259					.level_for("tao", LevelFilter::Info)
260					.level_for("tracing", LevelFilter::Info)
261					// Drop noise by target metadata.
262					.filter(|Metadata| {
263						!Metadata.target().starts_with("polling")
264							&& !Metadata.target().starts_with("tokio_reactor")
265							&& !Metadata.target().starts_with("want")
266					})
267					// Add category-like formatting: DATE [LEVEL] [TARGET] message.
268					.format(|out, message, record| {
269						out.finish(format_args!(
270							"[{:<5}] [{}] {}",
271							record.level(),
272							record.target(),
273							message
274						))
275					})
276					.build(),
277			)
278			// -----------------------------------------------------------------
279			// [Boot] [Tauri] [Plugins] Localhost server (pre-selected port)
280			// -----------------------------------------------------------------
281			.plugin(tauri_plugin_localhost::Builder::new(ServerPort)
282			.on_request(|_, Response| {
283				Response.add_header("Access-Control-Allow-Origin", "*");
284				Response.add_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD");
285				Response.add_header("Access-Control-Allow-Headers", "Content-Type, Authorization, Origin, Accept");
286			})
287			.build())
288			// -----------------------------------------------------------------
289			// [Boot] [DI] Global state
290			// -----------------------------------------------------------------
291			.manage(AppState.clone())
292			// -----------------------------------------------------------------
293			// [Lifecycle] Setup hook
294			// -----------------------------------------------------------------
295			.setup({
296				let LocalhostUrl = LocalhostUrl.clone();
297
298				move |Application| {
299					info!("[Lifecycle] [Setup] Setup hook started.");
300
301					debug!("[Lifecycle] [Setup] LocalhostUrl={}", LocalhostUrl);
302
303					let ApplicationHandle = Application.handle().clone();
304
305					TraceStep!("[Lifecycle] [Setup] AppHandle acquired.");
306
307					// ---------------------------------------------------------
308					// [Lifecycle] [Commands] Bootstrap native commands
309					// ---------------------------------------------------------
310					debug!("[Lifecycle] [Commands] Registering native commands...");
311
312					Command::Bootstrap::RegisterNativeCommands(&ApplicationHandle, &AppState)
313						.expect("FATAL: Failed to register native commands.");
314
315					debug!("[Lifecycle] [Commands] Native commands registered.");
316
317					// ---------------------------------------------------------
318					// [UI] [Window] Main window creation
319					// ---------------------------------------------------------
320					debug!("[UI] [Window] Building init script...");
321
322					let InitScript = format!("window.__MOUNTAIN_BASE_URL__ = '{}';", LocalhostUrl);
323
324					TraceStep!("[UI] [Window] InitScript bytes={}", InitScript.len());
325
326					debug!("[UI] [Window] Creating window builder...");
327
328					let mut WindowBuilder = tauri::WebviewWindowBuilder::new(
329						Application,
330						"main",
331						tauri::WebviewUrl::External(
332							format!("{}/Application/index.html", LocalhostUrl).parse().unwrap(),
333						),
334					)
335					.use_https_scheme(false)
336					.initialization_script(&InitScript)
337					.zoom_hotkeys_enabled(true)
338					.browser_extensions_enabled(false);
339
340					#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
341					{
342						WindowBuilder = WindowBuilder
343							.title("Mountain")
344							.maximized(true)
345							.decorations(false)
346							.shadow(true);
347					}
348
349					debug!("[UI] [Window] Building main window...");
350
351					#[allow(unused_variables)]
352					let MainWindow = WindowBuilder.build().expect("FATAL: Main window build failed");
353
354					info!("[UI] [Window] Main window ready.");
355
356					#[cfg(debug_assertions)]
357					{
358						debug!("[UI] [Window] Debug build: opening DevTools.");
359
360						MainWindow.open_devtools();
361					}
362
363					// ---------------------------------------------------------
364					// [Backend] [Env] Mountain environment
365					// ---------------------------------------------------------
366					debug!("[Backend] [Env] Creating MountainEnvironment...");
367
368					let Environment = Arc::new(MountainEnvironment::Create(ApplicationHandle.clone()));
369
370					info!("[Backend] [Env] MountainEnvironment ready.");
371
372					// ---------------------------------------------------------
373					// [Backend] [Runtime] ApplicationRunTime
374					// ---------------------------------------------------------
375					debug!("[Backend] [Runtime] Creating ApplicationRunTime...");
376
377					let RunTime = Arc::new(ApplicationRunTime::Create(
378						SchedulerForRunTime.clone(),
379						Environment.clone(),
380					));
381
382					ApplicationHandle.manage(RunTime);
383
384					info!("[Backend] [Runtime] ApplicationRunTime managed.");
385
386					// ---------------------------------------------------------
387					// [Lifecycle] [PostSetup] Async initialization work
388					// ---------------------------------------------------------
389					let PostSetupApplicationHandle = ApplicationHandle.clone();
390
391					let PostSetupEnvironment = Environment.clone();
392
393					tauri::async_runtime::spawn(async move {
394						info!("[Lifecycle] [PostSetup] Starting...");
395
396						let AppStateForSetup = PostSetupEnvironment.ApplicationState.clone();
397
398						TraceStep!("[Lifecycle] [PostSetup] AppState cloned.");
399
400						// [Config]
401						debug!("[Config] InitializeAndMergeConfigurations starting...");
402
403						if let Err(Error) = InitializeAndMergeConfigurations(&PostSetupEnvironment).await {
404							error!("[Config] InitializeAndMergeConfigurations failed: {}", Error);
405						} else {
406							info!("[Config] InitializeAndMergeConfigurations done.");
407						}
408
409						// [Extensions] [ScanPaths]
410						{
411							debug!("[Extensions] [ScanPaths] Locking ExtensionScanPaths...");
412
413							let mut ScanPathsGuard = AppStateForSetup
414								.ExtensionScanPaths
415								.lock()
416								.map_err(MapLockError)
417								.expect("FATAL: Failed to lock ExtensionScanPaths");
418
419							debug!("[Extensions] [ScanPaths] Adding default scan paths...");
420
421							if let Ok(ExecutableDirectory) = std::env::current_exe() {
422								if let Some(Parent) = ExecutableDirectory.parent() {
423									let ResourcesPath = Parent.join("../Resources/extensions");
424
425									let LocalPath = Parent.join("extensions");
426
427									debug!(
428										"[Extensions] [ScanPaths] + {}",
429										ResourcesPath.display()
430									);
431
432									ScanPathsGuard.push(ResourcesPath);
433
434									debug!(
435										"[Extensions] [ScanPaths] + {}",
436										LocalPath.display()
437									);
438
439									ScanPathsGuard.push(LocalPath);
440								}
441							}
442
443							info!(
444								"[Extensions] [ScanPaths] Initialized: {:?}",
445								*ScanPathsGuard
446							);
447						}
448
449						// [Extensions] [Scan]
450						debug!("[Extensions] [Scan] ScanAndPopulateExtensions starting...");
451
452						if let Err(Error) =
453							ScanAndPopulateExtensions(PostSetupApplicationHandle.clone(), &AppStateForSetup).await
454						{
455							error!("[Extensions] [Scan] Failed: {}", Error);
456						} else {
457							info!("[Extensions] [Scan] Completed.");
458						}
459
460						// [Vine] [gRPC]
461						debug!("[Vine] [Init] Starting Vine gRPC server...");
462
463						if let Err(Error) = Vine::Server::Initialize::Initialize(
464							PostSetupApplicationHandle.clone(),
465							"[::1]:50051".to_string(),
466						) {
467							error!("[Vine] [Init] Failed: {}", Error);
468						} else {
469							info!("[Vine] [Init] Ready.");
470						}
471
472						// [Cocoon] [Sidecar]
473						debug!("[Cocoon] [Init] InitializeCocoon starting...");
474
475						if let Err(Error) = InitializeCocoon(&PostSetupApplicationHandle, &PostSetupEnvironment).await {
476							error!("[Cocoon] [Init] Failed: {}", Error);
477						} else {
478							info!("[Cocoon] [Init] Ready.");
479						}
480
481						info!("[Lifecycle] [PostSetup] Complete. System ready.");
482					});
483
484					Ok(())
485				}
486			})
487			// ---------------------------------------------------------------------
488			// [Tauri] [Plugins] Standard
489			// ---------------------------------------------------------------------
490			.plugin(tauri_plugin_dialog::init())
491			.plugin(tauri_plugin_fs::init())
492			// ---------------------------------------------------------------------
493			// [IPC] Command routing
494			// ---------------------------------------------------------------------
495			.invoke_handler(tauri::generate_handler![
496				MountainGetWorkbenchConfiguration,
497				Command::TreeView::GetTreeViewChildren,
498				Command::LanguageFeature::MountainProvideHover,
499				Command::LanguageFeature::MountainProvideCompletions,
500				Command::LanguageFeature::MountainProvideDefinition,
501				Command::LanguageFeature::MountainProvideReferences,
502				Command::SourceControlManagement::GetAllSourceControlManagementState,
503				Command::Keybinding::GetResolvedKeybinding,
504				crate::Track::DispatchLogic::DispatchFrontendCommand,
505				crate::Track::DispatchLogic::ResolveUIRequest,
506			])
507			// ---------------------------------------------------------------------
508			// [Tauri] Build & run loop
509			// ---------------------------------------------------------------------
510			.build(tauri::generate_context!())
511			.expect("FATAL: Error while building Mountain Tauri application")
512			.run(move |ApplicationHandle, Event| {
513				// Debug-only: log selected lifecycle events (but avoid super-noisy ones).
514				if cfg!(debug_assertions) {
515					match &Event {
516						RunEvent::MainEventsCleared => {},
517						RunEvent::WindowEvent { .. } => {},
518						_ => debug!("[Lifecycle] [RunEvent] {:?}", Event),
519					}
520				}
521
522				if let RunEvent::ExitRequested { api, .. } = Event {
523					warn!("[Lifecycle] [Shutdown] Exit requested. Starting graceful shutdown...");
524
525					api.prevent_exit();
526
527					let SchedulerHandle = SchedulerForShutdown.clone();
528
529					let ApplicationHandleClone = ApplicationHandle.clone();
530
531					tokio::spawn(async move {
532						debug!("[Lifecycle] [Shutdown] Shutting down ApplicationRunTime...");
533
534						if let Some(RunTime) = ApplicationHandleClone.try_state::<Arc<ApplicationRunTime>>() {
535							RunTime.inner().clone().Shutdown().await;
536
537							info!("[Lifecycle] [Shutdown] ApplicationRunTime stopped.");
538
539						} else {
540							error!("[Lifecycle] [Shutdown] ApplicationRunTime not found.");
541
542						}
543
544						debug!("[Lifecycle] [Shutdown] Stopping Echo scheduler...");
545
546						if let Ok(mut Scheduler) = Arc::try_unwrap(SchedulerHandle) {
547							Scheduler.Stop().await;
548
549							info!("[Lifecycle] [Shutdown] Echo scheduler stopped.");
550						} else {
551							error!("[Lifecycle] [Shutdown] Scheduler not exclusively owned; cannot stop cleanly.");
552						}
553
554						info!("[Lifecycle] [Shutdown] Done. Exiting process.");
555
556						ApplicationHandleClone.exit(0);
557					});
558				}
559			});
560
561		info!("[Lifecycle] [Exit] Mountain application has shut down.");
562	});
563}