1#![allow(non_snake_case, non_camel_case_types)]
34
35use std::{
36 path::PathBuf,
37 sync::{Arc, Mutex},
38};
39
40use Echo::Scheduler::SchedulerBuilder::SchedulerBuilder;
41use log::{LevelFilter, debug, error, info, trace, warn};
42use tauri::{
43 AppHandle,
44 Manager,
45 RunEvent,
46 Wry,
47 image::Image,
48 menu::{MenuBuilder, MenuItem},
49 tray::{MouseButton, TrayIconBuilder, TrayIconEvent},
50};
51use tauri_plugin_log::{RotationStrategy, Target, TargetKind, TimezoneStrategy};
52
53use crate::{
54 ApplicationState::{
55 ApplicationState::{ApplicationState, MapLockError},
56 Internal::ScanAndPopulateExtensions,
57 },
58 Command,
59 Environment::{ConfigurationProvider::InitializeAndMergeConfigurations, MountainEnvironment::MountainEnvironment},
60 ProcessManagement::{CocoonManagement::InitializeCocoon, InitializationData},
61 RunTime::ApplicationRunTime::ApplicationRunTime,
62 Vine,
63};
64
65macro_rules! TraceStep {
71 ($($arg:tt)*) => {{
72 trace!($($arg)*);
73 }};
74}
75
76#[tauri::command]
83async fn MountainGetWorkbenchConfiguration(
84 ApplicationHandle:AppHandle<Wry>,
85 State:tauri::State<'_, Arc<ApplicationState>>,
86) -> Result<serde_json::Value, String> {
87 info!("[IPC] [WorkbenchConfig] Request received.");
88
89 debug!("[IPC] [WorkbenchConfig] Constructing sandbox configuration...");
90
91 let Config = InitializationData::ConstructSandboxConfiguration(&ApplicationHandle, &State)
92 .await
93 .map_err(|Error| {
94 error!("[IPC] [WorkbenchConfig] Failed: {}", Error);
95
96 Error.to_string()
97 })?;
98
99 debug!("[IPC] [WorkbenchConfig] Success. Returning payload.");
100
101 Ok(Config)
102}
103
104#[tauri::command]
107fn SwitchTrayIcon(App:AppHandle, IsDarkMode:bool) {
108 debug!("[UI] [Tray] Switching icon. IsDarkMode: {}", IsDarkMode);
109
110 const DARK_ICON_BYTES:&[u8] = include_bytes!("../icons/32x32.png");
111
112 const LIGHT_ICON_BYTES:&[u8] = include_bytes!("../icons/32x32.png");
113
114 let IconBytes = if IsDarkMode { DARK_ICON_BYTES } else { LIGHT_ICON_BYTES };
115
116 if let Some(Tray) = App.tray_by_id("tray") {
117 match Image::from_bytes(IconBytes) {
118 Ok(IconImage) => {
119 if let Err(e) = Tray.set_icon(Some(IconImage)) {
120 error!("[UI] [Tray] Failed to set icon: {}", e);
121 }
122 },
123 Err(e) => error!("[UI] [Tray] Failed to load icon bytes: {}", e),
124 }
125 } else {
126 warn!("[UI] [Tray] Tray with ID 'tray' not found.");
127 }
128}
129
130fn EnableTray(Application:&mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
136 let Handle = Application.handle();
137
138 let OpenItem = MenuItem::with_id(Handle, "open", "Open Mountain", true, None::<&str>)?;
140
141 let HideItem = MenuItem::with_id(Handle, "hide", "Hide Mountain", true, None::<&str>)?;
142
143 let Separator = tauri::menu::PredefinedMenuItem::separator(Handle)?;
144
145 let QuitItem = MenuItem::with_id(Handle, "quit", "Quit", true, None::<&str>)?;
146
147 let TrayMenu = MenuBuilder::new(Handle)
149 .item(&OpenItem)
150 .item(&HideItem)
151 .item(&Separator)
152 .item(&QuitItem)
153 .build()?;
154
155 let IconBytes = include_bytes!("../icons/32x32.png");
157
158 let TrayIconImage = Image::from_bytes(IconBytes)?;
159
160 TrayIconBuilder::with_id("tray")
162 .icon(TrayIconImage)
163 .menu(&TrayMenu)
164 .tooltip("Mountain")
165 .on_menu_event(|AppHandle, Event| match Event.id.as_ref() {
167 "open" => {
168 if let Some(Window) = AppHandle.get_webview_window("main") {
169 let _ = Window.show();
170
171 let _ = Window.set_focus();
172
173 }
174 },
175 "hide" => {
176 if let Some(Window) = AppHandle.get_webview_window("main") {
177 let _ = Window.hide();
178
179 }
180 },
181 "quit" => AppHandle.exit(0),
182 _ => warn!("[UI] [Tray] Unhandled menu item: {:?}", Event.id),
183 })
184 .on_tray_icon_event(|Tray, Event| {
186 if let TrayIconEvent::Click { button: MouseButton::Left, .. } = Event {
187 let App = Tray.app_handle();
188
189 if let Some(Window) = App.get_webview_window("main") {
190 if Window.is_visible().unwrap_or(false) {
191 let _ = Window.hide();
192 } else {
193 let _ = Window.show();
194
195 let _ = Window.set_focus();
196 }
197 }
198 }
199 })
200 .build(Application)?;
201
202 info!("[UI] [Tray] System tray enabled successfully.");
203
204 Ok(())
205}
206
207pub fn Fn() {
213 TraceStep!("[Boot] [Runtime] Building Tokio runtime...");
217
218 let Runtime = tokio::runtime::Builder::new_multi_thread()
219 .enable_all()
220 .build()
221 .expect("FATAL: Cannot build Tokio runtime.");
222
223 TraceStep!("[Boot] [Runtime] Tokio runtime built.");
224
225 Runtime.block_on(async {
226 debug!("[Boot] [Args] Collecting CLI args...");
230
231 let CliArgs:Vec<String> = std::env::args().collect();
232
233 debug!("[Boot] [Args] CLI Args: {:?}", CliArgs);
234
235 let WorkSpacePathArgument = CliArgs.iter().find(|Arg| Arg.ends_with(".code-workspace"));
236
237 TraceStep!("[Boot] [Args] Workspace arg present: {}", WorkSpacePathArgument.is_some());
238
239 let (InitialFolders, WorkSpaceConfigurationPath) = if let Some(PathString) = WorkSpacePathArgument {
240 let Path = PathBuf::from(PathString);
241
242 println!("[Boot] [Args] Found workspace argument: {}", Path.display());
243
244 debug!("[Boot] [Workspace] Reading workspace file: {}", Path.display());
245
246 match std::fs::read_to_string(&Path) {
247 Ok(Content) => {
248 debug!("[Boot] [Workspace] Workspace file read ok ({} bytes).", Content.len());
249
250 crate::WorkSpace::WorkSpaceFileService::ParseWorkSpaceFile(&Path, &Content)
251 .map(|Folders| {
252 debug!("[Boot] [Workspace] Parsed workspace ok. folder_count={}", Folders.len());
253
254 (Folders, Some(Path))
255 })
256 .unwrap_or_else(|Error| {
257 error!("[Boot] [Workspace] Parse failed: {}. Continuing without workspace.", Error);
258
259 (Vec::new(), None)
260 })
261 },
262 Err(Error) => {
263 error!("[Boot] [Workspace] Read failed: {}. Continuing without workspace.", Error);
264
265 (Vec::new(), None)
266 },
267 }
268 } else {
269 debug!("[Boot] [Workspace] No workspace provided. Starting empty.");
270
271 (Vec::new(), None)
272 };
273
274 debug!("[Boot] [State] Building ApplicationState...");
278
279 let AppState = Arc::new(ApplicationState {
280 WorkSpaceFolders:Arc::new(Mutex::new(InitialFolders)),
281 WorkSpaceConfigurationPath:Arc::new(Mutex::new(WorkSpaceConfigurationPath)),
282 ..ApplicationState::default()
283 });
284
285 debug!("[Boot] [State] ApplicationState created and managed.");
286
287 let NumberOfWorkers = num_cpus::get().max(2);
291
292 debug!("[Boot] [Echo] Creating scheduler. workers={}", NumberOfWorkers);
293
294 let Scheduler = SchedulerBuilder::Create().WithWorkerCount(NumberOfWorkers).Build();
295
296 debug!("[Boot] [Echo] Scheduler built.");
297
298 let SchedulerForShutdown = Arc::new(Scheduler);
299
300 let SchedulerForRunTime = SchedulerForShutdown.clone();
301
302 TraceStep!("[Boot] [Echo] Scheduler handles prepared.");
303
304 debug!("[Boot] [Localhost] Selecting unused port...");
308
309 let ServerPort =
310 portpicker::pick_unused_port().expect("FATAL: Failed to find a free port for Localhost Server");
311
312 debug!("[Boot] [Localhost] Selected port={}", ServerPort);
313
314 let LocalhostUrl = format!("http://localhost:{}", ServerPort);
315
316 println!("[Boot] [Localhost] Selected: {} ({})", ServerPort, LocalhostUrl);
317
318 let EnvLogLevel = std::env::var("RUST_LOG").ok().and_then(|s| s.parse::<LevelFilter>().ok());
329
330 let DefaultLogLevel = if cfg!(debug_assertions) { LevelFilter::Debug } else { LevelFilter::Info };
331
332 let LogLevel = EnvLogLevel.unwrap_or(DefaultLogLevel);
333
334 eprintln!(
337 "[Boot] [Logging] Resolved LogLevel={:?} (env={:?}, default={:?})",
338 LogLevel, EnvLogLevel, DefaultLogLevel
339 );
340
341 #[allow(unused_mut)]
345 let mut Builder = tauri::Builder::default();
346
347 #[cfg(any(windows, target_os = "linux"))]
348 {
349 Builder = Builder.any_thread();
350 }
351
352 Builder
353 .plugin(
357 tauri_plugin_log::Builder::new()
358 .targets([
359 Target::new(TargetKind::Stdout),
360 Target::new(TargetKind::LogDir {
361 file_name: Some("Mountain.log".into()),
362 }),
363 Target::new(TargetKind::Webview),
364 ])
365 .timezone_strategy(TimezoneStrategy::UseLocal)
366 .rotation_strategy(RotationStrategy::KeepAll)
367 .level(LogLevel)
368 .level_for("hyper", LevelFilter::Info)
370 .level_for("mio", LevelFilter::Info)
371 .level_for("tao", LevelFilter::Info)
372 .level_for("tracing", LevelFilter::Info)
373 .filter(|Metadata| {
375 !Metadata.target().starts_with("polling")
376 && !Metadata.target().starts_with("tokio_reactor")
377 && !Metadata.target().starts_with("want")
378 })
379 .format(|out, message, record| {
381 out.finish(format_args!(
382 "[{:<5}] [{}] {}",
383 record.level(),
384 record.target(),
385 message
386 ))
387 })
388 .build(),
389 )
390 .plugin(tauri_plugin_localhost::Builder::new(ServerPort)
394 .on_request(|_, Response| {
395 Response.add_header("Access-Control-Allow-Origin", "*");
396
397 Response.add_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD");
398
399 Response.add_header("Access-Control-Allow-Headers", "Content-Type, Authorization, Origin, Accept");
400 })
401 .build())
402 .manage(AppState.clone())
406 .setup({
410 let LocalhostUrl = LocalhostUrl.clone();
411
412 move |Application| {
413 info!("[Lifecycle] [Setup] Setup hook started.");
414
415 debug!("[Lifecycle] [Setup] LocalhostUrl={}", LocalhostUrl);
416
417 let ApplicationHandle = Application.handle().clone();
418
419 TraceStep!("[Lifecycle] [Setup] AppHandle acquired.");
420
421 debug!("[UI] [Tray] Initializing system tray...");
425
426 if let Err(Error) = EnableTray(Application) {
427 error!("[UI] [Tray] Failed to enable tray: {}", Error);
428
429 }
431
432 debug!("[Lifecycle] [Commands] Registering native commands...");
436
437 Command::Bootstrap::RegisterNativeCommands(&ApplicationHandle, &AppState)
438 .expect("FATAL: Failed to register native commands.");
439
440 debug!("[Lifecycle] [Commands] Native commands registered.");
441
442 debug!("[UI] [Window] Building init script...");
446
447 let InitScript = format!("window.__MOUNTAIN_BASE_URL__ = '{}';", LocalhostUrl);
448
449 TraceStep!("[UI] [Window] InitScript bytes={}", InitScript.len());
450
451 debug!("[UI] [Window] Creating window builder...");
452
453 let mut WindowBuilder = tauri::WebviewWindowBuilder::new(
454 Application,
455 "main",
456 tauri::WebviewUrl::External(
457 format!("{}/Application/index.html", LocalhostUrl).parse().unwrap(),
458 ),
459 )
460 .use_https_scheme(false)
461 .initialization_script(&InitScript)
462 .zoom_hotkeys_enabled(true)
463 .browser_extensions_enabled(false);
464
465 #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
466 {
467 WindowBuilder = WindowBuilder
468 .title("Mountain")
469 .maximized(true)
470 .decorations(false)
471 .shadow(true);
472 }
473
474 debug!("[UI] [Window] Building main window...");
475
476 #[allow(unused_variables)]
477 let MainWindow = WindowBuilder.build().expect("FATAL: Main window build failed");
478
479 info!("[UI] [Window] Main window ready.");
480
481 #[cfg(debug_assertions)]
482 {
483 debug!("[UI] [Window] Debug build: opening DevTools.");
484
485 MainWindow.open_devtools();
486 }
487
488 debug!("[Backend] [Env] Creating MountainEnvironment...");
492
493 let Environment = Arc::new(MountainEnvironment::Create(ApplicationHandle.clone()));
494
495 info!("[Backend] [Env] MountainEnvironment ready.");
496
497 debug!("[Backend] [Runtime] Creating ApplicationRunTime...");
501
502 let RunTime = Arc::new(ApplicationRunTime::Create(
503 SchedulerForRunTime.clone(),
504 Environment.clone(),
505 ));
506
507 ApplicationHandle.manage(RunTime);
508
509 info!("[Backend] [Runtime] ApplicationRunTime managed.");
510
511 let PostSetupApplicationHandle = ApplicationHandle.clone();
515
516 let PostSetupEnvironment = Environment.clone();
517
518 tauri::async_runtime::spawn(async move {
519 info!("[Lifecycle] [PostSetup] Starting...");
520
521 let AppStateForSetup = PostSetupEnvironment.ApplicationState.clone();
522
523 TraceStep!("[Lifecycle] [PostSetup] AppState cloned.");
524
525 debug!("[Config] InitializeAndMergeConfigurations starting...");
527
528 if let Err(Error) = InitializeAndMergeConfigurations(&PostSetupEnvironment).await {
529 error!("[Config] InitializeAndMergeConfigurations failed: {}", Error);
530 } else {
531 info!("[Config] InitializeAndMergeConfigurations done.");
532 }
533
534 {
536 debug!("[Extensions] [ScanPaths] Locking ExtensionScanPaths...");
537
538 let mut ScanPathsGuard = AppStateForSetup
539 .ExtensionScanPaths
540 .lock()
541 .map_err(MapLockError)
542 .expect("FATAL: Failed to lock ExtensionScanPaths");
543
544 debug!("[Extensions] [ScanPaths] Adding default scan paths...");
545
546 if let Ok(ExecutableDirectory) = std::env::current_exe() {
547 if let Some(Parent) = ExecutableDirectory.parent() {
548 let ResourcesPath = Parent.join("../Resources/extensions");
549
550 let LocalPath = Parent.join("extensions");
551
552 debug!(
553 "[Extensions] [ScanPaths] + {}",
554 ResourcesPath.display()
555 );
556
557 ScanPathsGuard.push(ResourcesPath);
558
559 debug!(
560 "[Extensions] [ScanPaths] + {}",
561 LocalPath.display()
562 );
563
564 ScanPathsGuard.push(LocalPath);
565
566 }
567 }
568
569 info!(
570 "[Extensions] [ScanPaths] Initialized: {:?}",
571 *ScanPathsGuard
572 );
573 }
574
575 debug!("[Extensions] [Scan] ScanAndPopulateExtensions starting...");
577
578 if let Err(Error) =
579 ScanAndPopulateExtensions(PostSetupApplicationHandle.clone(), &AppStateForSetup).await
580 {
581 error!("[Extensions] [Scan] Failed: {}", Error);
582 } else {
583 info!("[Extensions] [Scan] Completed.");
584 }
585
586 debug!("[Vine] [Init] Starting Vine gRPC server...");
588
589 if let Err(Error) = Vine::Server::Initialize::Initialize(
590 PostSetupApplicationHandle.clone(),
591 "[::1]:50051".to_string(),
592 ) {
593 error!("[Vine] [Init] Failed: {}", Error);
594 } else {
595 info!("[Vine] [Init] Ready.");
596 }
597
598 debug!("[Cocoon] [Init] InitializeCocoon starting...");
600
601 if let Err(Error) = InitializeCocoon(&PostSetupApplicationHandle, &PostSetupEnvironment).await {
602 error!("[Cocoon] [Init] Failed: {}", Error);
603 } else {
604 info!("[Cocoon] [Init] Ready.");
605 }
606
607 info!("[Lifecycle] [PostSetup] Complete. System ready.");
608 });
609
610 Ok(())
611 }
612 })
613 .plugin(tauri_plugin_dialog::init())
617 .plugin(tauri_plugin_fs::init())
618 .invoke_handler(tauri::generate_handler![
622 SwitchTrayIcon,
623 MountainGetWorkbenchConfiguration,
624 Command::TreeView::GetTreeViewChildren,
625 Command::LanguageFeature::MountainProvideHover,
626 Command::LanguageFeature::MountainProvideCompletions,
627 Command::LanguageFeature::MountainProvideDefinition,
628 Command::LanguageFeature::MountainProvideReferences,
629 Command::SourceControlManagement::GetAllSourceControlManagementState,
630 Command::Keybinding::GetResolvedKeybinding,
631 crate::Track::DispatchLogic::DispatchFrontendCommand,
632 crate::Track::DispatchLogic::ResolveUIRequest,
633 ])
634 .build(tauri::generate_context!())
638 .expect("FATAL: Error while building Mountain Tauri application")
639 .run(move |ApplicationHandle, Event| {
640 if cfg!(debug_assertions) {
642 match &Event {
643 RunEvent::MainEventsCleared => {},
644 RunEvent::WindowEvent { .. } => {},
645 _ => debug!("[Lifecycle] [RunEvent] {:?}", Event),
646 }
647 }
648
649 if let RunEvent::ExitRequested { api, .. } = Event {
650 warn!("[Lifecycle] [Shutdown] Exit requested. Starting graceful shutdown...");
651
652 api.prevent_exit();
653
654 let SchedulerHandle = SchedulerForShutdown.clone();
655
656 let ApplicationHandleClone = ApplicationHandle.clone();
657
658 tokio::spawn(async move {
659 debug!("[Lifecycle] [Shutdown] Shutting down ApplicationRunTime...");
660
661 if let Some(RunTime) = ApplicationHandleClone.try_state::<Arc<ApplicationRunTime>>() {
662 RunTime.inner().clone().Shutdown().await;
663
664 info!("[Lifecycle] [Shutdown] ApplicationRunTime stopped.");
665 } else {
666 error!("[Lifecycle] [Shutdown] ApplicationRunTime not found.");
667 }
668
669 debug!("[Lifecycle] [Shutdown] Stopping Echo scheduler...");
670
671 if let Ok(mut Scheduler) = Arc::try_unwrap(SchedulerHandle) {
672 Scheduler.Stop().await;
673
674 info!("[Lifecycle] [Shutdown] Echo scheduler stopped.");
675 } else {
676 error!("[Lifecycle] [Shutdown] Scheduler not exclusively owned; cannot stop cleanly.");
677 }
678
679 info!("[Lifecycle] [Shutdown] Done. Exiting process.");
680
681 ApplicationHandleClone.exit(0);
682 });
683
684 }
685 });
686
687 info!("[Lifecycle] [Exit] Mountain application has shut down.");
688 });
689}