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
359		Ok(Value::Null)
360	})
361}
362
363/// Native no-op for `workbench.action.openWalkthrough`. VS Code's
364/// walkthrough UI lives in `workbench/contrib/welcomeGettingStarted` and is
365/// not wired through Land yet. Extensions (notably `claude-code`) invoke this
366/// at activation - returning null avoids a user-visible "command not found"
367/// error while the walkthrough system remains unimplemented.
368fn CommandOpenWalkthrough(
369	_ApplicationHandle:AppHandle<Wry>,
370
371	_Window:WebviewWindow<Wry>,
372
373	_RunTime:Arc<ApplicationRunTime>,
374
375	Argument:Value,
376) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
377	Box::pin(async move {
378		dev_log!("commands", "[Native Command] openWalkthrough (no-op): {}", Argument);
379
380		Ok(Value::Null)
381	})
382}
383
384/// A native command for reloading the window.
385fn CommandReloadWindow(
386	_ApplicationHandle:AppHandle<Wry>,
387
388	Window:WebviewWindow<Wry>,
389
390	_RunTime:Arc<ApplicationRunTime>,
391
392	_Argument:Value,
393) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
394	Box::pin(async move {
395		dev_log!("commands", "[Native Command] Executing Reload Window...");
396
397		// Drive the real webview reload so extensions, settings, and locale
398		// changes take effect without restarting the process. Swallow the
399		// error - VS Code's contract returns `{ success: true }` on
400		// best-effort reload and extensions don't inspect it further.
401		if let Err(Error) = Window.eval("location.reload()") {
402			dev_log!("commands", "warn: [Native Command] Reload Window eval failed: {}", Error);
403		}
404
405		Ok(json!({ "success": true }))
406	})
407}
408
409/// `vscode.open(uri, columnOrOptions?)` - the built-in command every
410/// extension uses to jump to a file or open an external URL. Routes to
411/// `window.showTextDocument` for `file://` URIs (via the sky-channel so Sky
412/// can open the editor) and to `NativeHost.OpenExternal` for anything else.
413fn CommandVscodeOpen(
414	ApplicationHandle:AppHandle<Wry>,
415
416	_Window:WebviewWindow<Wry>,
417
418	_RunTime:Arc<ApplicationRunTime>,
419
420	Argument:Value,
421) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
422	Box::pin(async move {
423		use tauri::Emitter;
424
425		let UriRaw = if Argument.is_array() {
426			Argument.get(0).cloned().unwrap_or_default()
427		} else {
428			Argument.clone()
429		};
430
431		// Resolve the URI to a real wire string. Cocoon may forward a raw
432		// string, a serialised `vscode.Uri` POJO (`{scheme, authority,
433		// path, query, fragment}`), or a `{external, path}` shape used by
434		// older rendering paths. Reconstruct the full URI rather than
435		// picking a single field - extracting bare `path` from a non-file
436		// URI (e.g. `rust-analyzer-diagnostics-view:/diag/foo`) drops the
437		// scheme and Sky then tries to open `/diag/foo` as a file, which
438		// either 404s or renders as "[object Object]" in the editor tab
439		// when the workbench falls back to `String(uri)` on a bad input.
440		let UriString = match &UriRaw {
441			Value::String(S) => S.clone(),
442			Value::Object(Object) => {
443				if let Some(External) = Object.get("external").and_then(Value::as_str) {
444					External.to_string()
445				} else if let Some(Scheme) = Object.get("scheme").and_then(Value::as_str)
446					&& !Scheme.is_empty()
447				{
448					let Authority = Object.get("authority").and_then(Value::as_str).unwrap_or("");
449
450					let Path = Object.get("path").and_then(Value::as_str).unwrap_or("");
451
452					let Query = Object.get("query").and_then(Value::as_str).unwrap_or("");
453
454					let Fragment = Object.get("fragment").and_then(Value::as_str).unwrap_or("");
455
456					let mut Out = format!("{}://{}{}", Scheme, Authority, Path);
457
458					if !Query.is_empty() {
459						Out.push('?');
460
461						Out.push_str(Query);
462					}
463
464					if !Fragment.is_empty() {
465						Out.push('#');
466
467						Out.push_str(Fragment);
468					}
469
470					Out
471				} else if let Some(FsPath) = Object.get("fsPath").and_then(Value::as_str) {
472					if FsPath.starts_with('/') {
473						format!("file://{}", FsPath)
474					} else {
475						FsPath.to_string()
476					}
477				} else if let Some(Path) = Object.get("path").and_then(Value::as_str) {
478					Path.to_string()
479				} else {
480					String::new()
481				}
482			},
483			Value::Null => String::new(),
484			_ => UriRaw.to_string(),
485		};
486
487		if UriString.is_empty() {
488			return Err("vscode.open requires a URI".to_string());
489		}
490
491		let IsFileLike = UriString.starts_with("file:") || UriString.starts_with('/');
492
493		if IsFileLike {
494			if let Err(Error) = ApplicationHandle.emit("sky://window/showTextDocument", json!({ "uri": UriString })) {
495				dev_log!(
496					"commands",
497					"warn: [vscode.open] sky://window/showTextDocument emit failed: {}",
498					Error
499				);
500			}
501
502			Ok(json!(true))
503		} else {
504			// Fall through to platform open. Mirrors `NativeHost.OpenExternal`.
505			let Command:Option<(&str, Vec<String>)> = if cfg!(target_os = "macos") {
506				Some(("open", vec![UriString.clone()]))
507			} else if cfg!(target_os = "windows") {
508				Some(("cmd.exe", vec!["/c".into(), "start".into(), String::new(), UriString.clone()]))
509			} else {
510				Some(("xdg-open", vec![UriString.clone()]))
511			};
512
513			if let Some((Bin, Args)) = Command {
514				let _ = tokio::process::Command::new(Bin).args(&Args).spawn();
515			}
516
517			Ok(json!(true))
518		}
519	})
520}
521
522/// Validates command parameters before execution.
523fn ValidateCommandParameters(CommandName:&str, Arguments:&Value) -> Result<(), String> {
524	match CommandName {
525		"mountain.openFile" | "workbench.action.files.openFile" => {
526			// No specific validation needed for open file
527			Ok(())
528		},
529
530		"editor.action.formatDocument" => {
531			// Ensure there's an active document
532			Ok(())
533		},
534
535		_ => Ok(()),
536	}
537}
538
539// --- Registration Function ---
540
541/// Registers all native commands and providers with the application state.
542pub fn RegisterNativeCommands(
543	AppHandle:&AppHandle<Wry>,
544
545	ApplicationState:&Arc<ApplicationState>,
546) -> Result<(), CommonError> {
547	// --- Command Registration ---
548	let mut CommandRegistry = ApplicationState
549		.Extension
550		.Registry
551		.CommandRegistry
552		.lock()
553		.map_err(MapLockError)?;
554
555	dev_log!("commands", "[Bootstrap] Registering native commands...");
556
557	// Register core commands
558	CommandRegistry.insert("mountain.helloWorld".to_string(), CommandHandler::Native(CommandHelloWorld));
559
560	CommandRegistry.insert("mountain.openFile".to_string(), CommandHandler::Native(CommandOpenFile));
561
562	CommandRegistry.insert(
563		"workbench.action.files.openFile".to_string(),
564		CommandHandler::Native(CommandOpenFile),
565	);
566
567	CommandRegistry.insert(
568		"editor.action.formatDocument".to_string(),
569		CommandHandler::Native(CommandFormatDocument),
570	);
571
572	CommandRegistry.insert(
573		"workbench.action.files.save".to_string(),
574		CommandHandler::Native(CommandSaveDocument),
575	);
576
577	CommandRegistry.insert(
578		"workbench.action.closeActiveEditor".to_string(),
579		CommandHandler::Native(CommandCloseDocument),
580	);
581
582	CommandRegistry.insert(
583		"workbench.action.reloadWindow".to_string(),
584		CommandHandler::Native(CommandReloadWindow),
585	);
586
587	// setContext is VS Code built-in - extensions invoke it on activation to
588	// declare UI context keys. Registering as a no-op silences the routing
589	// error until Wind/Sky wire through a real context key service.
590	CommandRegistry.insert("setContext".to_string(), CommandHandler::Native(CommandSetContext));
591
592	// `vscode.open(uri)` - dispatches to the editor for file URIs and to the
593	// platform shell for everything else. Extensions call this without
594	// guarding on whether we've registered it; a missing registration shows
595	// up as "command 'vscode.open' not found" in user-visible error toasts.
596	CommandRegistry.insert("vscode.open".to_string(), CommandHandler::Native(CommandVscodeOpen));
597
598	CommandRegistry.insert("vscode.openFolder".to_string(), CommandHandler::Native(CommandVscodeOpen));
599
600	// `workbench.action.openWalkthrough` is VS Code's welcome/getting-started
601	// walkthrough entry point; the `claude-code` extension wraps it with its
602	// own `claude-vscode.openWalkthrough` command and invokes both at
603	// activation. Land has no walkthrough UI yet - register both as no-ops so
604	// extension activation doesn't surface "command not found" errors.
605	CommandRegistry.insert(
606		"workbench.action.openWalkthrough".to_string(),
607		CommandHandler::Native(CommandOpenWalkthrough),
608	);
609
610	CommandRegistry.insert(
611		"claude-vscode.openWalkthrough".to_string(),
612		CommandHandler::Native(CommandOpenWalkthrough),
613	);
614
615	dev_log!("commands", "[Bootstrap] {} native commands registered.", CommandRegistry.len());
616
617	drop(CommandRegistry);
618
619	// --- Command Validation ---
620	dev_log!("commands", "[Bootstrap] Validating registered commands...");
621
622	// Validate all registered commands at startup to catch configuration errors
623	// early. Verification includes command signature correctness, parameter type
624	// matching, required permissions and capabilities, and extension metadata
625	// validity. This prevents runtime errors from malformed registrations and
626	// provides immediate feedback to extension developers during development.
627	// Current implementation logs without performing actual validation checks.
628
629	// --- Tree View Provider Registration ---
630	let mut TreeViewRegistry = ApplicationState
631		.Feature
632		.TreeViews
633		.ActiveTreeViews
634		.lock()
635		.map_err(MapLockError)?;
636
637	dev_log!("commands", "[Bootstrap] Registering native tree view providers...");
638
639	let ExplorerViewID = "workbench.view.explorer".to_string();
640
641	let ExplorerProvider = Arc::new(FileExplorerViewProvider::New(AppHandle.clone()));
642
643	TreeViewRegistry.insert(
644		ExplorerViewID.clone(),
645		TreeViewStateDTO {
646			ViewIdentifier:ExplorerViewID,
647
648			Provider:Some(ExplorerProvider),
649
650			// This is a native provider
651			SideCarIdentifier:None,
652
653			CanSelectMany:true,
654
655			HasHandleDrag:false,
656
657			HasHandleDrop:false,
658
659			Message:None,
660
661			Title:Some("Explorer".to_string()),
662
663			Description:None,
664
665			Badge:None,
666		},
667	);
668
669	dev_log!(
670		"commands",
671		"[Bootstrap] {} native tree view providers registered.",
672		TreeViewRegistry.len()
673	);
674
675	Ok(())
676}