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::{
132	ApplicationState::{
133		DTO::TreeViewStateDTO::TreeViewStateDTO,
134		State::ApplicationState::{ApplicationState, MapLockError},
135	},
136	Environment::CommandProvider::CommandHandler,
137	FileSystem::FileExplorerViewProvider::Struct as FileExplorerViewProvider,
138	RunTime::ApplicationRunTime::ApplicationRunTime,
139	dev_log,
140};
141
142// --- Command Implementations ---
143
144/// A simple native command that logs a message.
145fn CommandHelloWorld(
146	_ApplicationHandle:AppHandle<Wry>,
147
148	_Window:WebviewWindow<Wry>,
149
150	_RunTime:Arc<ApplicationRunTime>,
151
152	_Argument:Value,
153) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
154	Box::pin(async move {
155		dev_log!("commands", "[Native Command] Hello from Mountain!");
156
157		Ok(json!("Hello from Mountain's native command!"))
158	})
159}
160
161/// A native command that orchestrates the "Open File" dialog flow.
162fn CommandOpenFile(
163	_ApplicationHandle:AppHandle<Wry>,
164
165	_Window:WebviewWindow<Wry>,
166
167	RunTime:Arc<ApplicationRunTime>,
168
169	_Argument:Value,
170) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
171	Box::pin(async move {
172		dev_log!("commands", "[Native Command] Executing Open File...");
173
174		let DialogResult = RunTime.Run(ShowOpenDialog(None)).await.map_err(|Error| Error.to_string())?;
175
176		if let Some(Paths) = DialogResult {
177			if let Some(Path) = Paths.first() {
178				// We have a path, now open the document.
179				let URI = Url::from_file_path(Path).map_err(|_| "Invalid file path".to_string())?;
180
181				let OpenDocumentEffect = OpenDocument(json!({ "external": URI.to_string() }), None, None);
182
183				RunTime.Run(OpenDocumentEffect).await.map_err(|Error| Error.to_string())?;
184			}
185		}
186
187		Ok(Value::Null)
188	})
189}
190
191/// A native command that orchestrates the "Format Document" action.
192fn CommandFormatDocument(
193	_ApplicationHandle:AppHandle<Wry>,
194
195	_Window:WebviewWindow<Wry>,
196
197	RunTime:Arc<ApplicationRunTime>,
198
199	_Argument:Value,
200) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
201	Box::pin(async move {
202		dev_log!("commands", "[Native Command] Executing Format Document...");
203
204		let AppState = &RunTime.Environment.ApplicationState;
205
206		let URIString = AppState
207			.Workspace
208			.ActiveDocumentURI
209			.lock()
210			.map_err(MapLockError)
211			.map_err(|Error| Error.to_string())?
212			.clone()
213			.ok_or("No active document URI found in state".to_string())?;
214
215		let URI = Url::parse(&URIString).map_err(|_| "Invalid URI in window state".to_string())?;
216
217		// Example formatting options
218		let Options = json!({ "tabSize": 4, "insertSpaces": true });
219
220		// 1. Get the formatting edits from the language feature provider.
221		let LanguageProvider:Arc<dyn LanguageFeatureProviderRegistry> = RunTime.Environment.Require();
222
223		let EditsOption = LanguageProvider
224			.ProvideDocumentFormattingEdits(URI.clone(), Options)
225			.await
226			.map_err(|Error| Error.to_string())?;
227
228		if let Some(Edits) = EditsOption {
229			if Edits.is_empty() {
230				dev_log!("commands", "[Native Command] No formatting changes to apply.");
231
232				return Ok(Value::Null);
233			}
234
235			// 2. Convert the text edits into a WorkspaceEdit.
236			let WorkspaceEdit = WorkspaceEditDTO {
237				Edits:vec![(
238					serde_json::to_value(&URI).map_err(|Error| Error.to_string())?,
239					Edits
240						.into_iter()
241						.map(serde_json::to_value)
242						.collect::<Result<Vec<_>, _>>()
243						.map_err(|Error| Error.to_string())?,
244				)],
245			};
246
247			// 3. Apply the workspace edit.
248			dev_log!("commands", "[Native Command] Applying formatting edits...");
249
250			RunTime
251				.Run(ApplyWorkspaceEdit(WorkspaceEdit))
252				.await
253				.map_err(|Error| Error.to_string())?;
254		} else {
255			dev_log!("commands", "[Native Command] No formatting provider found for this document.");
256		}
257
258		Ok(Value::Null)
259	})
260}
261
262/// A native command for saving the current document.
263fn CommandSaveDocument(
264	_ApplicationHandle:AppHandle<Wry>,
265
266	_Window:WebviewWindow<Wry>,
267
268	RunTime:Arc<ApplicationRunTime>,
269
270	_Argument:Value,
271) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
272	Box::pin(async move {
273		dev_log!("commands", "[Native Command] Executing Save Document...");
274
275		let AppState = &RunTime.Environment.ApplicationState;
276
277		let URIString = AppState
278			.Workspace
279			.ActiveDocumentURI
280			.lock()
281			.map_err(MapLockError)
282			.map_err(|Error| Error.to_string())?
283			.clone()
284			.ok_or("No active document URI found in state".to_string())?;
285
286		let URI = Url::parse(&URIString).map_err(|_| "Invalid URI in window state".to_string())?;
287
288		// Persist the active document by invoking DocumentProvider::SaveDocument or the
289		// Document::Save effect. This reads the document URI from ApplicationState,
290		// serializes the current editor content, and writes to disk with proper error
291		// handling, atomic writes, and backup creation. Current implementation only
292		// logs the action; full implementation requires integration with the document
293		// lifecycle and file system provider.
294		dev_log!("commands", "[Native Command] Saving document: {}", URI);
295
296		Ok(Value::Null)
297	})
298}
299
300/// A native command for closing the current document.
301fn CommandCloseDocument(
302	_ApplicationHandle:AppHandle<Wry>,
303
304	_Window:WebviewWindow<Wry>,
305
306	RunTime:Arc<ApplicationRunTime>,
307
308	_Argument:Value,
309) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
310	Box::pin(async move {
311		dev_log!("commands", "[Native Command] Executing Close Document...");
312
313		let AppState = &RunTime.Environment.ApplicationState;
314
315		let URIString = AppState
316			.Workspace
317			.ActiveDocumentURI
318			.lock()
319			.map_err(MapLockError)
320			.map_err(|Error| Error.to_string())?
321			.clone()
322			.ok_or("No active document URI found in state".to_string())?;
323
324		let URI = Url::parse(&URIString).map_err(|_| "Invalid URI in window state".to_string())?;
325
326		// Close the active document in the editor by triggering the workspace edit
327		// to remove the document from open editors. Checks for unsaved changes and
328		// prompts the user to save, discard, or cancel. Integrates with the document
329		// lifecycle manager to release resources and update the UI. May invoke
330		// Workbench::closeEditor or equivalent command. Current implementation only
331		// logs the action.
332		dev_log!("commands", "[Native Command] Closing document: {}", URI);
333
334		Ok(Value::Null)
335	})
336}
337
338/// Native no-op for VS Code's built-in `setContext` command. Extensions call
339/// `vscode.commands.executeCommand('setContext', key, value)` to set UI
340/// context-key state used for when-clauses. Wind/Sky owns the actual context
341/// key service; Mountain forwards the value so CommandProvider doesn't raise
342/// "not found". Returns null because the real VS Code command returns void.
343fn CommandSetContext(
344	_ApplicationHandle:AppHandle<Wry>,
345
346	_Window:WebviewWindow<Wry>,
347
348	_RunTime:Arc<ApplicationRunTime>,
349
350	Argument:Value,
351) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
352	Box::pin(async move {
353		// setContext fires on every UI state change (focus, view toggle,
354		// gitlens mode, SCM repo change). ~130 calls per session. Route
355		// to `commands-verbose` so per-keypress context changes don't
356		// flood the default log.
357		dev_log!("commands-verbose", "[Native Command] setContext: {}", Argument);
358		Ok(Value::Null)
359	})
360}
361
362/// Native no-op for `workbench.action.openWalkthrough`. VS Code's
363/// walkthrough UI lives in `workbench/contrib/welcomeGettingStarted` and is
364/// not wired through Land yet. Extensions (notably `claude-code`) invoke this
365/// at activation - returning null avoids a user-visible "command not found"
366/// error while the walkthrough system remains unimplemented.
367fn CommandOpenWalkthrough(
368	_ApplicationHandle:AppHandle<Wry>,
369
370	_Window:WebviewWindow<Wry>,
371
372	_RunTime:Arc<ApplicationRunTime>,
373
374	Argument:Value,
375) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
376	Box::pin(async move {
377		dev_log!("commands", "[Native Command] openWalkthrough (no-op): {}", Argument);
378		Ok(Value::Null)
379	})
380}
381
382/// A native command for reloading the window.
383fn CommandReloadWindow(
384	_ApplicationHandle:AppHandle<Wry>,
385
386	Window:WebviewWindow<Wry>,
387
388	_RunTime:Arc<ApplicationRunTime>,
389
390	_Argument:Value,
391) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
392	Box::pin(async move {
393		dev_log!("commands", "[Native Command] Executing Reload Window...");
394
395		// Drive the real webview reload so extensions, settings, and locale
396		// changes take effect without restarting the process. Swallow the
397		// error - VS Code's contract returns `{ success: true }` on
398		// best-effort reload and extensions don't inspect it further.
399		if let Err(Error) = Window.eval("location.reload()") {
400			dev_log!("commands", "warn: [Native Command] Reload Window eval failed: {}", Error);
401		}
402
403		Ok(json!({ "success": true }))
404	})
405}
406
407/// `vscode.open(uri, columnOrOptions?)` - the built-in command every
408/// extension uses to jump to a file or open an external URL. Routes to
409/// `window.showTextDocument` for `file://` URIs (via the sky-channel so Sky
410/// can open the editor) and to `NativeHost.OpenExternal` for anything else.
411fn CommandVscodeOpen(
412	ApplicationHandle:AppHandle<Wry>,
413
414	_Window:WebviewWindow<Wry>,
415
416	_RunTime:Arc<ApplicationRunTime>,
417
418	Argument:Value,
419) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
420	Box::pin(async move {
421		use tauri::Emitter;
422
423		let UriRaw = if Argument.is_array() {
424			Argument.get(0).cloned().unwrap_or_default()
425		} else {
426			Argument.clone()
427		};
428		let UriString = match &UriRaw {
429			Value::String(S) => S.clone(),
430			Value::Object(Object) => {
431				Object
432					.get("external")
433					.and_then(Value::as_str)
434					.or_else(|| Object.get("path").and_then(Value::as_str))
435					.map(str::to_string)
436					.unwrap_or_default()
437			},
438			Value::Null => String::new(),
439			_ => UriRaw.to_string(),
440		};
441		if UriString.is_empty() {
442			return Err("vscode.open requires a URI".to_string());
443		}
444		let IsFileLike = UriString.starts_with("file:") || UriString.starts_with('/');
445		if IsFileLike {
446			if let Err(Error) = ApplicationHandle.emit("sky://window/showTextDocument", json!({ "uri": UriString })) {
447				dev_log!(
448					"commands",
449					"warn: [vscode.open] sky://window/showTextDocument emit failed: {}",
450					Error
451				);
452			}
453			Ok(json!(true))
454		} else {
455			// Fall through to platform open. Mirrors `NativeHost.OpenExternal`.
456			let Command:Option<(&str, Vec<String>)> = if cfg!(target_os = "macos") {
457				Some(("open", vec![UriString.clone()]))
458			} else if cfg!(target_os = "windows") {
459				Some(("cmd.exe", vec!["/c".into(), "start".into(), String::new(), UriString.clone()]))
460			} else {
461				Some(("xdg-open", vec![UriString.clone()]))
462			};
463			if let Some((Bin, Args)) = Command {
464				let _ = tokio::process::Command::new(Bin).args(&Args).spawn();
465			}
466			Ok(json!(true))
467		}
468	})
469}
470
471/// Validates command parameters before execution.
472#[allow(dead_code)]
473fn ValidateCommandParameters(CommandName:&str, Arguments:&Value) -> Result<(), String> {
474	match CommandName {
475		"mountain.openFile" | "workbench.action.files.openFile" => {
476			// No specific validation needed for open file
477			Ok(())
478		},
479		"editor.action.formatDocument" => {
480			// Ensure there's an active document
481			Ok(())
482		},
483		_ => Ok(()),
484	}
485}
486
487// --- Registration Function ---
488
489/// Registers all native commands and providers with the application state.
490pub fn RegisterNativeCommands(
491	AppHandle:&AppHandle<Wry>,
492
493	ApplicationState:&Arc<ApplicationState>,
494) -> Result<(), CommonError> {
495	// --- Command Registration ---
496	let mut CommandRegistry = ApplicationState
497		.Extension
498		.Registry
499		.CommandRegistry
500		.lock()
501		.map_err(MapLockError)?;
502
503	dev_log!("commands", "[Bootstrap] Registering native commands...");
504
505	// Register core commands
506	CommandRegistry.insert("mountain.helloWorld".to_string(), CommandHandler::Native(CommandHelloWorld));
507
508	CommandRegistry.insert("mountain.openFile".to_string(), CommandHandler::Native(CommandOpenFile));
509
510	CommandRegistry.insert(
511		"workbench.action.files.openFile".to_string(),
512		CommandHandler::Native(CommandOpenFile),
513	);
514
515	CommandRegistry.insert(
516		"editor.action.formatDocument".to_string(),
517		CommandHandler::Native(CommandFormatDocument),
518	);
519
520	CommandRegistry.insert(
521		"workbench.action.files.save".to_string(),
522		CommandHandler::Native(CommandSaveDocument),
523	);
524
525	CommandRegistry.insert(
526		"workbench.action.closeActiveEditor".to_string(),
527		CommandHandler::Native(CommandCloseDocument),
528	);
529
530	CommandRegistry.insert(
531		"workbench.action.reloadWindow".to_string(),
532		CommandHandler::Native(CommandReloadWindow),
533	);
534
535	// setContext is VS Code built-in - extensions invoke it on activation to
536	// declare UI context keys. Registering as a no-op silences the routing
537	// error until Wind/Sky wire through a real context key service.
538	CommandRegistry.insert("setContext".to_string(), CommandHandler::Native(CommandSetContext));
539
540	// `vscode.open(uri)` - dispatches to the editor for file URIs and to the
541	// platform shell for everything else. Extensions call this without
542	// guarding on whether we've registered it; a missing registration shows
543	// up as "command 'vscode.open' not found" in user-visible error toasts.
544	CommandRegistry.insert("vscode.open".to_string(), CommandHandler::Native(CommandVscodeOpen));
545	CommandRegistry.insert("vscode.openFolder".to_string(), CommandHandler::Native(CommandVscodeOpen));
546
547	// `workbench.action.openWalkthrough` is VS Code's welcome/getting-started
548	// walkthrough entry point; the `claude-code` extension wraps it with its
549	// own `claude-vscode.openWalkthrough` command and invokes both at
550	// activation. Land has no walkthrough UI yet - register both as no-ops so
551	// extension activation doesn't surface "command not found" errors.
552	CommandRegistry.insert(
553		"workbench.action.openWalkthrough".to_string(),
554		CommandHandler::Native(CommandOpenWalkthrough),
555	);
556	CommandRegistry.insert(
557		"claude-vscode.openWalkthrough".to_string(),
558		CommandHandler::Native(CommandOpenWalkthrough),
559	);
560
561	dev_log!("commands", "[Bootstrap] {} native commands registered.", CommandRegistry.len());
562
563	drop(CommandRegistry);
564
565	// --- Command Validation ---
566	dev_log!("commands", "[Bootstrap] Validating registered commands...");
567	// Validate all registered commands at startup to catch configuration errors
568	// early. Verification includes command signature correctness, parameter type
569	// matching, required permissions and capabilities, and extension metadata
570	// validity. This prevents runtime errors from malformed registrations and
571	// provides immediate feedback to extension developers during development.
572	// Current implementation logs without performing actual validation checks.
573
574	// --- Tree View Provider Registration ---
575	let mut TreeViewRegistry = ApplicationState
576		.Feature
577		.TreeViews
578		.ActiveTreeViews
579		.lock()
580		.map_err(MapLockError)?;
581
582	dev_log!("commands", "[Bootstrap] Registering native tree view providers...");
583
584	let ExplorerViewID = "workbench.view.explorer".to_string();
585
586	let ExplorerProvider = Arc::new(FileExplorerViewProvider::New(AppHandle.clone()));
587
588	TreeViewRegistry.insert(
589		ExplorerViewID.clone(),
590		TreeViewStateDTO {
591			ViewIdentifier:ExplorerViewID,
592
593			Provider:Some(ExplorerProvider),
594
595			// This is a native provider
596			SideCarIdentifier:None,
597
598			CanSelectMany:true,
599
600			HasHandleDrag:false,
601
602			HasHandleDrop:false,
603
604			Message:None,
605
606			Title:Some("Explorer".to_string()),
607
608			Description:None,
609
610			Badge:None,
611		},
612	);
613
614	dev_log!(
615		"commands",
616		"[Bootstrap] {} native tree view providers registered.",
617		TreeViewRegistry.len()
618	);
619
620	Ok(())
621}