Skip to main content

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 serde_json::{Value, json};
128use tauri::{AppHandle, WebviewWindow, Wry};
129use url::Url;
130
131use crate::dev_log;
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		dev_log!("commands", "[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		dev_log!("commands", "[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		dev_log!("commands", "[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				dev_log!("commands", "[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			dev_log!("commands", "[Native Command] Applying formatting edits...");
246
247			RunTime
248				.Run(ApplyWorkspaceEdit(WorkspaceEdit))
249				.await
250				.map_err(|Error| Error.to_string())?;
251		} else {
252			dev_log!("commands", "[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		dev_log!("commands", "[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		dev_log!("commands", "[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		dev_log!("commands", "[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		dev_log!("commands", "[Native Command] Closing document: {}", URI);
330
331		Ok(Value::Null)
332	})
333}
334
335/// Native no-op for VS Code's built-in `setContext` command. Extensions call
336/// `vscode.commands.executeCommand('setContext', key, value)` to set UI
337/// context-key state used for when-clauses. Wind/Sky owns the actual context
338/// key service; Mountain forwards the value so CommandProvider doesn't raise
339/// "not found". Returns null because the real VS Code command returns void.
340fn CommandSetContext(
341	_ApplicationHandle:AppHandle<Wry>,
342
343	_Window:WebviewWindow<Wry>,
344
345	_RunTime:Arc<ApplicationRunTime>,
346
347	Argument:Value,
348) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
349	Box::pin(async move {
350		dev_log!("commands", "[Native Command] setContext: {}", Argument);
351		Ok(Value::Null)
352	})
353}
354
355/// A native command for reloading the window.
356fn CommandReloadWindow(
357	_ApplicationHandle:AppHandle<Wry>,
358
359	_Window:WebviewWindow<Wry>,
360
361	_RunTime:Arc<ApplicationRunTime>,
362
363	_Argument:Value,
364) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
365	Box::pin(async move {
366		dev_log!("commands", "[Native Command] Executing Reload Window...");
367
368		// Refresh the entire application UI by calling WebviewWindow::reload. This
369		// reinitializes the frontend, reapplies window state, and restarts extension
370		// host processes if configuration changes require it. Used after settings
371		// updates, extension installations, or development hot-reload. Current
372		// implementation returns success without performing the actual reload.
373		Ok(json!({ "success": true }))
374	})
375}
376
377/// Validates command parameters before execution.
378fn ValidateCommandParameters(CommandName:&str, Arguments:&Value) -> Result<(), String> {
379	match CommandName {
380		"mountain.openFile" | "workbench.action.files.openFile" => {
381			// No specific validation needed for open file
382			Ok(())
383		},
384		"editor.action.formatDocument" => {
385			// Ensure there's an active document
386			Ok(())
387		},
388		_ => Ok(()),
389	}
390}
391
392// --- Registration Function ---
393
394/// Registers all native commands and providers with the application state.
395pub fn RegisterNativeCommands(
396	AppHandle:&AppHandle<Wry>,
397
398	ApplicationState:&Arc<ApplicationState>,
399) -> Result<(), CommonError> {
400	// --- Command Registration ---
401	let mut CommandRegistry = ApplicationState
402		.Extension
403		.Registry
404		.CommandRegistry
405		.lock()
406		.map_err(MapLockError)?;
407
408	dev_log!("commands", "[Bootstrap] Registering native commands...");
409
410	// Register core commands
411	CommandRegistry.insert("mountain.helloWorld".to_string(), CommandHandler::Native(CommandHelloWorld));
412
413	CommandRegistry.insert("mountain.openFile".to_string(), CommandHandler::Native(CommandOpenFile));
414
415	CommandRegistry.insert(
416		"workbench.action.files.openFile".to_string(),
417		CommandHandler::Native(CommandOpenFile),
418	);
419
420	CommandRegistry.insert(
421		"editor.action.formatDocument".to_string(),
422		CommandHandler::Native(CommandFormatDocument),
423	);
424
425	CommandRegistry.insert(
426		"workbench.action.files.save".to_string(),
427		CommandHandler::Native(CommandSaveDocument),
428	);
429
430	CommandRegistry.insert(
431		"workbench.action.closeActiveEditor".to_string(),
432		CommandHandler::Native(CommandCloseDocument),
433	);
434
435	CommandRegistry.insert(
436		"workbench.action.reloadWindow".to_string(),
437		CommandHandler::Native(CommandReloadWindow),
438	);
439
440	// setContext is VS Code built-in — extensions invoke it on activation to
441	// declare UI context keys. Registering as a no-op silences the routing
442	// error until Wind/Sky wire through a real context key service.
443	CommandRegistry.insert("setContext".to_string(), CommandHandler::Native(CommandSetContext));
444
445	dev_log!("commands", "[Bootstrap] {} native commands registered.", CommandRegistry.len());
446
447	drop(CommandRegistry);
448
449	// --- Command Validation ---
450	dev_log!("commands", "[Bootstrap] Validating registered commands...");
451	// Validate all registered commands at startup to catch configuration errors
452	// early. Verification includes command signature correctness, parameter type
453	// matching, required permissions and capabilities, and extension metadata
454	// validity. This prevents runtime errors from malformed registrations and
455	// provides immediate feedback to extension developers during development.
456	// Current implementation logs without performing actual validation checks.
457
458	// --- Tree View Provider Registration ---
459	let mut TreeViewRegistry = ApplicationState
460		.Feature
461		.TreeViews
462		.ActiveTreeViews
463		.lock()
464		.map_err(MapLockError)?;
465
466	dev_log!("commands", "[Bootstrap] Registering native tree view providers...");
467
468	let ExplorerViewID = "workbench.view.explorer".to_string();
469
470	let ExplorerProvider = Arc::new(FileExplorerViewProvider::New(AppHandle.clone()));
471
472	TreeViewRegistry.insert(
473		ExplorerViewID.clone(),
474		TreeViewStateDTO {
475			ViewIdentifier:ExplorerViewID,
476
477			Provider:Some(ExplorerProvider),
478
479			// This is a native provider
480			SideCarIdentifier:None,
481
482			CanSelectMany:true,
483
484			HasHandleDrag:false,
485
486			HasHandleDrop:false,
487
488			Message:None,
489
490			Title:Some("Explorer".to_string()),
491
492			Description:None,
493
494			Badge:None,
495		},
496	);
497
498	dev_log!("commands", "[Bootstrap] {} native tree view providers registered.", TreeViewRegistry.len());
499
500	Ok(())
501}