Mountain/Environment/
CommandProvider.rs

1//! # CommandProvider (Environment)
2//!
3//! Implements the `CommandExecutor` trait, serving as the central registry and
4//! dispatcher for all commands in the Mountain application. Commands can be
5//! handled either by native Rust handlers or proxied to extension sidecar
6//! processes.
7//!
8//! ## RESPONSIBILITIES
9//!
10//! ### 1. Command Registry
11//! - Maintain centralized registry of all registered commands
12//! - Store command metadata (id, title, category, flags)
13//! - Track command source (native vs extension)
14//! - Support command enablement/disable state
15//!
16//! ### 2. Command Dispatching
17//! - Route command execution requests to appropriate handlers
18//! - Execute native commands directly via function calls
19//! - Proxy extension commands via IPC to sidecar processes
20//! - Handle command result propagation and error translation
21//!
22//! ### 3. Command Execution Context
23//! - Provide `CommandExecutor` capability to command handlers
24//! - Manage command scope (text editor, file system, etc.)
25//! - Track command invocation source and user context
26//! - Support command cancellation for long-running operations
27//!
28//! ### 4. Command Discovery
29//! - Enumerate all registered commands for UI display
30//! - Support command palette and quick open
31//! - Provide command categories and visibility rules
32//! - Handle command contribution points from extensions
33//!
34//! ## ARCHITECTURAL ROLE
35//!
36//! CommandProvider is the **command execution hub** for Mountain:
37//!
38//! ```text
39//! Command Caller ──► CommandProvider ──► Handler (Native or Extension)
40//!       │                            │
41//!       └─► ExecuteCommand() ───────► Execute
42//! ```
43//!
44//! ### Position in Mountain
45//! - `Environment` module: Core capability provider
46//! - Implements `CommonLibrary::Command::CommandExecutor` trait
47//! - Accessible via `Environment.Require<dyn CommandExecutor>()`
48//!
49//! ### Command Sources
50//! - **Native Commands**: Defined in Rust, registered at startup
51//! - **Extension Commands**: Defined in package.json, contributed by extensions
52//! - **Built-in Commands**: Core Mountain functionality
53//! - **User-defined Commands**: Custom macros and keybindings (future)
54//!
55//! ### Dependencies
56//! - `ApplicationState`: Command registry storage
57//! - `IPCProvider`: For proxying to extension sidecars
58//! - `Log`: For command execution tracing
59//!
60//! ### Dependents
61//! - All command handlers: Use `CommandExecutor` to execute other commands
62//! - `DispatchLogic`::`DispatchFrontendCommand`: Main entry point
63//! - Tauri command handlers: Many invoke `ExecuteCommand`
64//! - Keybinding system: Trigger commands via keyboard shortcuts
65//!
66//! ## COMMAND EXECUTION FLOW
67//!
68//! 1. **Request**: Caller invokes `ExecuteCommand(command_id, args)`
69//! 2. **Lookup**: Provider looks up command in
70//!    `ApplicationState::CommandRegistry`
71//! 3. **Handler**: Retrieves the associated handler (native function or
72//!    extension RPC)
73//! 4. **Execute**: Calls handler with arguments
74//! 5. **Result**: Returns serialized JSON result or error
75//!
76//! ## NATIVE vs EXTENSION COMMANDS
77//!
78//! ### Native Commands
79//! - Implemented directly in Rust
80//! - Registered via `RegisterCommand` function
81//! - Handler is a function pointer or `Arc<Fn>`
82//! - Zero IPC overhead, direct call
83//!
84//! ### Extension Commands
85//! - Defined in extension's `package.json` `contributes.commands`
86//! - Registered when extension is activated
87//! - Handler is RPC method to extension sidecar
88//! - Goes through IPC layer with serialization
89//!
90//! ## ERROR HANDLING
91//!
92//! - Command not found: Returns `CommonError::InvalidArgument`
93//! - Handler errors: Propagated as `CommonError`
94//! - IPC failures: Converted to `CommonError::IPCError`
95//! - Serialization failures: `CommonError::SerializationError`
96//!
97//! ## PERFORMANCE
98//!
99//! - Native commands: Near-zero overhead (direct function call)
100//! - Extension commands: IPC serialization + network latency
101//! - Command lookup: HashMap lookup by string ID (fast)
102//! - Consider caching frequently used command results
103//!
104//! ## VS CODE REFERENCE
105//!
106//! Borrowed from VS Code's command system:
107//! - `vs/platform/commands/common/commands.ts` - Command definitions
108//! - `vs/workbench/services/commands/common/commandService.ts` - Command
109//!   registry
110//! - `vs/platform/commands/common/commandExecutor.ts` - Command execution
111//!
112//! ## TODO
113//!
114//! - [ ] Implement command contribution points from extensions
115//! - [ ] Add command enablement/disable state management
116//! - [ ] Support command categories and grouping
117//! - [ ] Add command history and undo/redo stack
118//! - [ ] Implement command keyboard shortcut resolution
119//! - [ ] Add command telemetry (usage metrics)
120//! - [ ] Support command aliases and deprecation
121//! - [ ] Add command permission validation
122//! - [ ] Implement command batching for related operations
123//!
124//! ## MODULE CONTENTS
125//!
126//! - `CommandProvider`: Main struct implementing `CommandExecutor`
127//! - Command registration functions (to be added)
128//! - Extension command proxy logic
129
130// 1. **Command Registry**: Maintains a centralized registry of all registered
131//    commands and their corresponding handlers (native or proxied).
132//
133// 2. **Command Dispatching**: Routes command execution requests to the
134//    appropriate handler based on the command identifier.
135//
136// 3. **Extension Command Proxying**: Enables extensions to contribute commands
137//    that are executed in their sidecar processes via IPC.
138//
139// 4. **Command Lifecycle Management**: Handles registration, unregistration,
140//    and querying of available commands.
141//
142// # Command Execution Flow
143//
144// 1. Extension or system calls ExecuteCommand(identifier, args)
145// 2. CommandProvider looks up the command in
146//    ApplicationState.Extension.Registry.CommandRegistry
147// 3. If native handler: executes Rust function directly with AppHandle and
148//    arguments
149// 4. If proxied handler: sends IPC request to the owning sidecar via Vine
150//    client
151// 5. Returns result or error to caller
152//
153// # Patterns Borrowed from VSCode
154//
155// - **Command Registry**: Follows VSCode's command registry pattern where
156//   commands are identified by strings and can be contributed by extensions.
157//
158// - **Context Passing**: Like VSCode's execution context, Mountain passes the
159//   AppHandle and Runtime to native handlers for context awareness.
160//
161// - **Conflict Resolution**: VSCode allows command overrides; Mountain
162//   currently does not implement conflict resolution (TODO).
163//
164// # TODOs
165//
166// - [ ] Implement command conflict resolution strategy
167// - [ ] Add command execution context (selection, active editor, etc.)
168// - [ ] Implement command categories and metadata
169// - [ ] Add command enablement/disablement based on context
170// - [ ] Implement command execution metrics and telemetry
171// - [ ] Add command keyboard shortcut registration lookup
172// - [ ] Implement command execution timeout and cancellation
173// - [ ] Add validation of command arguments
174// - [ ] Consider adding command preconditions
175// - [ ] Implement command history for undo/redo scenarios
176
177use std::{future::Future, pin::Pin, sync::Arc};
178
179use CommonLibrary::{
180	Command::CommandExecutor::CommandExecutor,
181	Error::CommonError::CommonError,
182	IPC::DTO::ProxyTarget::ProxyTarget,
183};
184use async_trait::async_trait;
185use log::{debug, error, info};
186use serde_json::{Value, json};
187use tauri::{AppHandle, Manager, Runtime, WebviewWindow};
188
189use super::MountainEnvironment::MountainEnvironment;
190use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, Vine::Client};
191
192/// An enum representing the different ways a command can be handled.
193pub enum CommandHandler<R:Runtime + 'static> {
194	/// A command handled by a native, asynchronous Rust function.
195	Native(
196		fn(
197			AppHandle<R>,
198
199			WebviewWindow<R>,
200
201			Arc<ApplicationRunTime>,
202
203			Value,
204		) -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>>,
205	),
206
207	/// A command implemented in an extension and proxied to a sidecar.
208	Proxied { SideCarIdentifier:String, CommandIdentifier:String },
209}
210
211impl<R:Runtime> Clone for CommandHandler<R> {
212	fn clone(&self) -> Self {
213		match self {
214			Self::Native(Function) => Self::Native(*Function),
215
216			Self::Proxied { SideCarIdentifier, CommandIdentifier } => {
217				Self::Proxied {
218					SideCarIdentifier:SideCarIdentifier.clone(),
219
220					CommandIdentifier:CommandIdentifier.clone(),
221				}
222			},
223		}
224	}
225}
226
227#[async_trait]
228impl CommandExecutor for MountainEnvironment {
229	/// Executes a registered command by dispatching it to the appropriate
230	/// handler.
231	async fn ExecuteCommand(&self, CommandIdentifier:String, Argument:Value) -> Result<Value, CommonError> {
232		let HandlerInfoOption = self
233			.ApplicationState
234			.Extension
235			.Registry
236			.CommandRegistry
237			.lock()
238			.map_err(super::Utility::MapApplicationStateLockErrorToCommonError)?
239			.get(&CommandIdentifier)
240			.cloned();
241
242		match HandlerInfoOption {
243			Some(CommandHandler::Native(Function)) => {
244				debug!("[CommandProvider] Executing NATIVE command '{}'.", CommandIdentifier);
245
246				let RunTime:Arc<ApplicationRunTime> =
247					self.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
248
249				let MainWindow = self.ApplicationHandle.get_webview_window("main").ok_or_else(|| {
250					CommonError::UserInterfaceInteraction {
251						Reason:"Main window not found for command execution".into(),
252					}
253				})?;
254
255				Function(self.ApplicationHandle.clone(), MainWindow, RunTime, Argument)
256					.await
257					.map_err(|Error| CommonError::CommandExecution { CommandIdentifier, Reason:Error })
258			},
259
260			Some(CommandHandler::Proxied { SideCarIdentifier, CommandIdentifier: ProxiedCommandIdentifier }) => {
261				debug!(
262					"[CommandProvider] Executing PROXIED command '{}' on sidecar '{}'.",
263					CommandIdentifier, SideCarIdentifier
264				);
265
266				let RPCParameters = json!([ProxiedCommandIdentifier, Argument]);
267
268				let RPCMethod = format!("{}$ExecuteContributedCommand", ProxyTarget::ExtHostCommands.GetTargetPrefix());
269
270				Client::SendRequest(&SideCarIdentifier, RPCMethod, RPCParameters, 30000)
271					.await
272					.map_err(|Error| CommonError::IPCError { Description:Error.to_string() })
273			},
274
275			None => {
276				error!("[CommandProvider] Command '{}' not found in registry.", CommandIdentifier);
277
278				Err(CommonError::CommandNotFound { Identifier:CommandIdentifier })
279			},
280		}
281	}
282
283	/// Registers a command contributed by a sidecar process.
284	async fn RegisterCommand(&self, SideCarIdentifier:String, CommandIdentifier:String) -> Result<(), CommonError> {
285		info!(
286			"[CommandProvider] Registering PROXY command '{}' from sidecar '{}'",
287			CommandIdentifier, SideCarIdentifier
288		);
289
290		let mut Registry = self
291			.ApplicationState
292			.Extension
293			.Registry
294			.CommandRegistry
295			.lock()
296			.map_err(super::Utility::MapApplicationStateLockErrorToCommonError)?;
297
298		Registry.insert(
299			CommandIdentifier.clone(),
300			CommandHandler::Proxied { SideCarIdentifier, CommandIdentifier },
301		);
302
303		Ok(())
304	}
305
306	/// Unregisters a previously registered command.
307	async fn UnregisterCommand(&self, _SideCarIdentifier:String, CommandIdentifier:String) -> Result<(), CommonError> {
308		info!("[CommandProvider] Unregistering command '{}'", CommandIdentifier);
309
310		self.ApplicationState
311			.Extension
312			.Registry
313			.CommandRegistry
314			.lock()
315			.map_err(super::Utility::MapApplicationStateLockErrorToCommonError)?
316			.remove(&CommandIdentifier);
317
318		Ok(())
319	}
320
321	/// Gets a list of all currently registered command IDs.
322	async fn GetAllCommands(&self) -> Result<Vec<String>, CommonError> {
323		debug!("[CommandProvider] Getting all command identifiers.");
324
325		let Registry = self
326			.ApplicationState
327			.Extension
328			.Registry
329			.CommandRegistry
330			.lock()
331			.map_err(super::Utility::MapApplicationStateLockErrorToCommonError)?;
332
333		Ok(Registry.keys().cloned().collect())
334	}
335}