Mountain/Command/
Bootstrap.rs

1//! # Bootstrap (Command)
2//!
3//! Registers all native, Rust-implemented commands and providers into the
4//! application's state at startup. This module ensures all core functionality
5//! is available as soon as the application initializes.
6//!
7//! ## RESPONSIBILITIES
8//!
9//! ### 1. Command Registration
10//! - Register all Tauri command handlers from `Command::` module
11//! - Register core IPC command handlers from `Track::` module
12//! - Build the complete `invoke_handler` vector for Tauri builder
13//! - Ensure all commands are available before UI starts
14//!
15//! ### 2. Tree View Provider Registration
16//! - Register native tree view providers (FileExplorer, etc.)
17//! - Create provider instances and store in `ApplicationState::ActiveTreeViews`
18//! - Associate view identifiers with provider implementations
19//!
20//! ### 3. Provider Registration
21//! - Initialize Environment providers that need early setup
22//! - Register command executors and configuration providers
23//! - Set up document and workspace providers
24//!
25//! ## ARCHITECTURAL ROLE
26//!
27//! Bootstrap is the **registration orchestrator** for Mountain's startup:
28//!
29//! ```text
30//! Binary::Main ──► Bootstrap::RegisterAll ──► Tauri Builder ──► App Ready
31//!                      │
32//!                      ├─► Command Handlers Registered
33//!                      ├─► Tree View Providers Registered
34//!                      └─► ApplicationState Populated
35//! ```
36//!
37//! ### Position in Mountain
38//! - `Command` module: Command system initialization
39//! - Called from `Binary::Main::Fn` during Tauri builder setup
40//! - Must complete before `.run()` is called on Tauri app
41//!
42//! ### Key Functions
43//! - `RegisterAll`: Main entry point that registers everything
44//! - `RegisterCommands`: Adds all Tauri command handlers
45//! - `RegisterTreeViewProviders`: Registers native tree view providers
46//!
47//! ## REGISTRATION PROCESS
48//!
49//! 1. **Commands**: All command functions are added to Tauri's `invoke_handler`
50//!    via `tauri::generate_handler![]` macro
51//! 2. **Tree Views**: Native providers are instantiated and stored in state
52//! 3. **Error Handling**: Registration failures are logged but don't stop
53//!    startup
54//!
55//! ## COMMAND REGISTRATION
56//!
57//! The following command modules are registered:
58//! - `Command::TreeView::GetTreeViewChildren`
59//! - `Command::LanguageFeature::MountainProvideHover`
60//! - `Command::LanguageFeature::MountainProvideCompletions`
61//! - `Command::LanguageFeature::MountainProvideDefinition`
62//! - `Command::LanguageFeature::MountainProvideReferences`
63//! - `Command::SourceControlManagement::GetAllSourceControlManagementState`
64//! - `Command::Keybinding::GetResolvedKeybinding`
65//! - `Track::DispatchLogic::DispatchFrontendCommand`
66//! - `Track::DispatchLogic::ResolveUIRequest`
67//! - `IPC::TauriIPCServer::mountain_ipc_receive_message`
68//! - `IPC::TauriIPCServer::mountain_ipc_get_status`
69//! - `Binary::Main::SwitchTrayIcon`
70//! - `Binary::Main::MountainGetWorkbenchConfiguration`
71//! - (and more...)
72//!
73//! ## TREE VIEW PROVIDERS
74//!
75//! Currently registered native providers:
76//! - `FileExplorerViewProvider`: File system tree view
77//!   - View ID: `"fileExplorer"`
78//!   - Provides workspace folders and file listings
79//!
80//! ## PERFORMANCE
81//!
82//! - Registration is synchronous and fast (no async allowed in registration)
83//! - All commands are registered up-front; no lazy loading
84//! - Tree view providers are created once at startup
85//!
86//! ## ERROR HANDLING
87//!
88//! - Command registration errors are logged as errors
89//! - Tree view provider errors are logged as warnings
90//! - Registration continues even if some components fail
91//!
92//! ## TODO
93//!
94//! - [ ] Add command registration metrics (count, duplicates detection)
95//! - [ ] Implement command dependency ordering
96//! - [ ] Add command validation (duplicate names, signature checking)
97//! - [ ] Support dynamic command registration after startup
98//! - [ ] Add command unregistration for hot-reload scenarios
99//! - [ ] Implement command permission system
100//!
101//! ## MODULE CONTENTS
102//!
103//! - `RegisterAll`: Main registration function called from Binary::Main
104//! - `RegisterCommands`: Internal function to register all command handlers
105//! - `RegisterTreeViewProviders`: Internal function to register tree view
106//! providers
107
108// ## VSCode Reference:
109// - vs/workbench/services/actions/common/menuService.ts
110// - vs/workbench/browser/actions.ts
111// - vs/platform/actions/common/actions.ts
112//
113// ============================================================================
114
115use std::{future::Future, pin::Pin, sync::Arc};
116
117use CommonLibrary::{
118	DTO::WorkspaceEditDTO::WorkspaceEditDTO,
119	Document::OpenDocument::OpenDocument,
120	Effect::ApplicationRunTime::ApplicationRunTime as _,
121	Environment::Requires::Requires,
122	Error::CommonError::CommonError,
123	LanguageFeature::LanguageFeatureProviderRegistry::LanguageFeatureProviderRegistry,
124	UserInterface::ShowOpenDialog::ShowOpenDialog,
125	Workspace::ApplyWorkspaceEdit::ApplyWorkspaceEdit,
126};
127use log::info;
128use serde_json::{Value, json};
129use tauri::{AppHandle, WebviewWindow, Wry};
130use url::Url;
131
132use crate::{
133	ApplicationState::{ApplicationState, DTO::TreeViewStateDTO::TreeViewStateDTO, MapLockError},
134	Environment::CommandProvider::CommandHandler,
135	FileSystem::FileExplorerViewProvider::FileExplorerViewProvider,
136	RunTime::ApplicationRunTime::ApplicationRunTime,
137};
138
139// --- Command Implementations ---
140
141/// A simple native command that logs a message.
142fn CommandHelloWorld(
143	_ApplicationHandle:AppHandle<Wry>,
144
145	_Window:WebviewWindow<Wry>,
146
147	_RunTime:Arc<ApplicationRunTime>,
148
149	_Argument:Value,
150) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
151	Box::pin(async move {
152		info!("[Native Command] Hello from Mountain!");
153
154		Ok(json!("Hello from Mountain's native command!"))
155	})
156}
157
158/// A native command that orchestrates the "Open File" dialog flow.
159fn CommandOpenFile(
160	_ApplicationHandle:AppHandle<Wry>,
161
162	_Window:WebviewWindow<Wry>,
163
164	RunTime:Arc<ApplicationRunTime>,
165
166	_Argument:Value,
167) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
168	Box::pin(async move {
169		info!("[Native Command] Executing Open File...");
170
171		let DialogResult = RunTime.Run(ShowOpenDialog(None)).await.map_err(|Error| Error.to_string())?;
172
173		if let Some(Paths) = DialogResult {
174			if let Some(Path) = Paths.first() {
175				// We have a path, now open the document.
176				let URI = Url::from_file_path(Path).map_err(|_| "Invalid file path".to_string())?;
177
178				let OpenDocumentEffect = OpenDocument(json!({ "external": URI.to_string() }), None, None);
179
180				RunTime.Run(OpenDocumentEffect).await.map_err(|Error| Error.to_string())?;
181			}
182		}
183
184		Ok(Value::Null)
185	})
186}
187
188/// A native command that orchestrates the "Format Document" action.
189fn CommandFormatDocument(
190	_ApplicationHandle:AppHandle<Wry>,
191
192	_Window:WebviewWindow<Wry>,
193
194	RunTime:Arc<ApplicationRunTime>,
195
196	_Argument:Value,
197) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
198	Box::pin(async move {
199		info!("[Native Command] Executing Format Document...");
200
201		let AppState = &RunTime.Environment.ApplicationState;
202
203		let URIString = AppState
204			.Workspace
205			.ActiveDocumentURI
206			.lock()
207			.map_err(MapLockError)
208			.map_err(|Error| Error.to_string())?
209			.clone()
210			.ok_or("No active document URI found in state".to_string())?;
211
212		let URI = Url::parse(&URIString).map_err(|_| "Invalid URI in window state".to_string())?;
213
214		// Example formatting options
215		let Options = json!({ "tabSize": 4, "insertSpaces": true });
216
217		// 1. Get the formatting edits from the language feature provider.
218		let LanguageProvider:Arc<dyn LanguageFeatureProviderRegistry> = RunTime.Environment.Require();
219
220		let EditsOption = LanguageProvider
221			.ProvideDocumentFormattingEdits(URI.clone(), Options)
222			.await
223			.map_err(|Error| Error.to_string())?;
224
225		if let Some(Edits) = EditsOption {
226			if Edits.is_empty() {
227				info!("[Native Command] No formatting changes to apply.");
228
229				return Ok(Value::Null);
230			}
231
232			// 2. Convert the text edits into a WorkspaceEdit.
233			let WorkspaceEdit = WorkspaceEditDTO {
234				Edits:vec![(
235					serde_json::to_value(&URI).map_err(|Error| Error.to_string())?,
236					Edits
237						.into_iter()
238						.map(serde_json::to_value)
239						.collect::<Result<Vec<_>, _>>()
240						.map_err(|Error| Error.to_string())?,
241				)],
242			};
243
244			// 3. Apply the workspace edit.
245			info!("[Native Command] Applying formatting edits...");
246
247			RunTime
248				.Run(ApplyWorkspaceEdit(WorkspaceEdit))
249				.await
250				.map_err(|Error| Error.to_string())?;
251		} else {
252			info!("[Native Command] No formatting provider found for this document.");
253		}
254
255		Ok(Value::Null)
256	})
257}
258
259/// A native command for saving the current document.
260fn CommandSaveDocument(
261	_ApplicationHandle:AppHandle<Wry>,
262
263	_Window:WebviewWindow<Wry>,
264
265	RunTime:Arc<ApplicationRunTime>,
266
267	_Argument:Value,
268) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
269	Box::pin(async move {
270		info!("[Native Command] Executing Save Document...");
271
272		let AppState = &RunTime.Environment.ApplicationState;
273
274		let URIString = AppState
275			.Workspace
276			.ActiveDocumentURI
277			.lock()
278			.map_err(MapLockError)
279			.map_err(|Error| Error.to_string())?
280			.clone()
281			.ok_or("No active document URI found in state".to_string())?;
282
283		let URI = Url::parse(&URIString).map_err(|_| "Invalid URI in window state".to_string())?;
284
285		// Persist the active document by invoking DocumentProvider::SaveDocument or the
286		// Document::Save effect. This reads the document URI from ApplicationState,
287		// serializes the current editor content, and writes to disk with proper error
288		// handling, atomic writes, and backup creation. Current implementation only
289		// logs the action; full implementation requires integration with the document
290		// lifecycle and file system provider.
291		info!("[Native Command] Saving document: {}", URI);
292
293		Ok(Value::Null)
294	})
295}
296
297/// A native command for closing the current document.
298fn CommandCloseDocument(
299	_ApplicationHandle:AppHandle<Wry>,
300
301	_Window:WebviewWindow<Wry>,
302
303	RunTime:Arc<ApplicationRunTime>,
304
305	_Argument:Value,
306) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
307	Box::pin(async move {
308		info!("[Native Command] Executing Close Document...");
309
310		let AppState = &RunTime.Environment.ApplicationState;
311
312		let URIString = AppState
313			.Workspace
314			.ActiveDocumentURI
315			.lock()
316			.map_err(MapLockError)
317			.map_err(|Error| Error.to_string())?
318			.clone()
319			.ok_or("No active document URI found in state".to_string())?;
320
321		let URI = Url::parse(&URIString).map_err(|_| "Invalid URI in window state".to_string())?;
322
323		// Close the active document in the editor by triggering the workspace edit
324		// to remove the document from open editors. Checks for unsaved changes and
325		// prompts the user to save, discard, or cancel. Integrates with the document
326		// lifecycle manager to release resources and update the UI. May invoke
327		// Workbench::closeEditor or equivalent command. Current implementation only
328		// logs the action.
329		info!("[Native Command] Closing document: {}", URI);
330
331		Ok(Value::Null)
332	})
333}
334
335/// A native command for reloading the window.
336fn CommandReloadWindow(
337	_ApplicationHandle:AppHandle<Wry>,
338
339	_Window:WebviewWindow<Wry>,
340
341	_RunTime:Arc<ApplicationRunTime>,
342
343	_Argument:Value,
344) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
345	Box::pin(async move {
346		info!("[Native Command] Executing Reload Window...");
347
348		// Refresh the entire application UI by calling WebviewWindow::reload. This
349		// reinitializes the frontend, reapplies window state, and restarts extension
350		// host processes if configuration changes require it. Used after settings
351		// updates, extension installations, or development hot-reload. Current
352		// implementation returns success without performing the actual reload.
353		Ok(json!({ "success": true }))
354	})
355}
356
357/// Validates command parameters before execution.
358fn ValidateCommandParameters(CommandName:&str, Arguments:&Value) -> Result<(), String> {
359	match CommandName {
360		"mountain.openFile" | "workbench.action.files.openFile" => {
361			// No specific validation needed for open file
362			Ok(())
363		},
364		"editor.action.formatDocument" => {
365			// Ensure there's an active document
366			Ok(())
367		},
368		_ => Ok(()),
369	}
370}
371
372// --- Registration Function ---
373
374/// Registers all native commands and providers with the application state.
375pub fn RegisterNativeCommands(
376	AppHandle:&AppHandle<Wry>,
377
378	ApplicationState:&Arc<ApplicationState>,
379) -> Result<(), CommonError> {
380	// --- Command Registration ---
381	let mut CommandRegistry = ApplicationState
382		.Extension
383		.Registry
384		.CommandRegistry
385		.lock()
386		.map_err(MapLockError)?;
387
388	info!("[Bootstrap] Registering native commands...");
389
390	// Register core commands
391	CommandRegistry.insert("mountain.helloWorld".to_string(), CommandHandler::Native(CommandHelloWorld));
392
393	CommandRegistry.insert("mountain.openFile".to_string(), CommandHandler::Native(CommandOpenFile));
394
395	CommandRegistry.insert(
396		"workbench.action.files.openFile".to_string(),
397		CommandHandler::Native(CommandOpenFile),
398	);
399
400	CommandRegistry.insert(
401		"editor.action.formatDocument".to_string(),
402		CommandHandler::Native(CommandFormatDocument),
403	);
404
405	CommandRegistry.insert(
406		"workbench.action.files.save".to_string(),
407		CommandHandler::Native(CommandSaveDocument),
408	);
409
410	CommandRegistry.insert(
411		"workbench.action.closeActiveEditor".to_string(),
412		CommandHandler::Native(CommandCloseDocument),
413	);
414
415	CommandRegistry.insert(
416		"workbench.action.reloadWindow".to_string(),
417		CommandHandler::Native(CommandReloadWindow),
418	);
419
420	info!("[Bootstrap] {} native commands registered.", CommandRegistry.len());
421
422	drop(CommandRegistry);
423
424	// --- Command Validation ---
425	info!("[Bootstrap] Validating registered commands...");
426	// Validate all registered commands at startup to catch configuration errors
427	// early. Verification includes command signature correctness, parameter type
428	// matching, required permissions and capabilities, and extension metadata
429	// validity. This prevents runtime errors from malformed registrations and
430	// provides immediate feedback to extension developers during development.
431	// Current implementation logs without performing actual validation checks.
432
433	// --- Tree View Provider Registration ---
434	let mut TreeViewRegistry = ApplicationState
435		.Feature
436		.TreeViews
437		.ActiveTreeViews
438		.lock()
439		.map_err(MapLockError)?;
440
441	info!("[Bootstrap] Registering native tree view providers...");
442
443	let ExplorerViewID = "workbench.view.explorer".to_string();
444
445	let ExplorerProvider = Arc::new(FileExplorerViewProvider::New(AppHandle.clone()));
446
447	TreeViewRegistry.insert(
448		ExplorerViewID.clone(),
449		TreeViewStateDTO {
450			ViewIdentifier:ExplorerViewID,
451
452			Provider:Some(ExplorerProvider),
453
454			// This is a native provider
455			SideCarIdentifier:None,
456
457			CanSelectMany:true,
458
459			HasHandleDrag:false,
460
461			HasHandleDrop:false,
462
463			Message:None,
464
465			Title:Some("Explorer".to_string()),
466
467			Description:None,
468		},
469	);
470
471	info!("[Bootstrap] {} native tree view providers registered.", TreeViewRegistry.len());
472
473	Ok(())
474}