Mountain/Track/Effect/
CreateEffectForRequest.rs

1//! # CreateEffectForRequest (Track)
2//!
3//! ## RESPONSIBILITIES
4//!
5//! This module provides the central routing table that maps string-based
6//! commands/RPC methods to typed effects. It creates MappedEffect (type-erased
7//! async closures) for dispatch execution and integrates with the effect system
8//! (ActionEffect) and provider traits. Some operations use direct provider
9//! calls for performance.
10//!
11//! ### Core Functions:
12//! - Map string-based method names to effect constructors
13//! - Create MappedEffect (boxed closures) for execution
14//! - Support direct provider calls for hot paths
15//! - Handle parameter deserialization and validation
16//!
17//! ## ARCHITECTURAL ROLE
18//!
19//! CreateEffectForRequest acts as the **effect mapper** in Track's dispatch
20//! system:
21//!
22//! ```text
23//! Dispatch Logic ──► CreateEffectForRequest (Match) ──► MappedEffect ──► ApplicationRunTime Execution
24//! ```
25//!
26//! ## KEY COMPONENTS
27//!
28//! - **Fn**: Main effect creation function (pub fn Fn<R:Runtime>)
29//! - **MappedEffect**: Type alias for boxed async closure (imported from
30//!   MappedEffect module)
31//!
32//! ## ERROR HANDLING
33//!
34//! - All effects return Result<Value, String> (serializable errors for IPC)
35//! - Parameter validation with descriptive error messages
36//! - Unknown command handling returns error instead of panic
37//! - Serialization/deserialization errors caught and reported
38//! - Provider errors propagate with context
39//!
40//! ## LOGGING
41//!
42//! - Unknown commands are logged at warn level
43//! - Log format: "[`CreateEffectForRequest`] Unknown method: {}"
44//!
45//! ## PERFORMANCE CONSIDERATIONS
46//!
47//! - Effect creation is cheap: match + constructor call + box
48//! - Direct provider calls avoid allocation (for hot paths)
49//! - TODO: Consider implementing an effect pool to cache frequently created
50//!   effects
51//! - TODO: Add configurable command timeouts per command type and rate limiting
52//!
53//! ## DIRECT PROVIDER CALLS
54//!
55//! Some operations bypass the effect system for performance:
56//! - Configuration: `GetConfiguration`, `UpdateConfiguration`
57//! - Diagnostics: `SetDiagnostics`, `ClearDiagnostics`
58//! - Language Features: `ProvideHover`, `ProvideCompletions`, etc.
59//! - Terminal: direct text send/receive
60//! - Why? Avoid effect overhead for high-frequency operations
61//!
62//! ## SUPPORTED COMMAND CATEGORIES
63//!
64//! **Commands**: Execute, GetAll, Register
65//! **Configuration**: Inspect, Update
66//! **Documents**: Save, SaveAs
67//! **FileSystem**: ReadFile, WriteFile, ReadDirectory
68//! **Debug**: Start, RegisterConfigurationProvider
69//! **Diagnostics**: Set, Clear
70//! **Keybinding**: GetResolved
71//! **LanguageFeatures**: $languageFeatures:registerProvider
72//! **Search**: TextSearch
73//! **SourceControlManagement**: $scm:createSourceControl, updateSourceControl,
74//! updateGroup, registerInputBox **StatusBar**: $statusBar:set, dispose,
75//! $setStatusBarMessage, $disposeStatusBarMessage **Storage**: Get, Set
76//! **Terminal**: $terminal:create, sendText, dispose
77//! **TreeView**: $tree:register
78//! **UserInterface**: ShowMessage, ShowOpenDialog, ShowSaveDialog
79//! **Webview**: $webview:create, $resolveCustomEditor
80//!
81//! ## TODO
82//!
83//! High Priority:
84//! - [ ] Add command parameter schema validation (JSON schema per command)
85//! - [ ] Implement command permission checking (capability-based security)
86//! - [ ] Add command deprecation warnings and migration
87//! - [ ] Cache frequently created effects (reuse boxed closures)
88//! - [ ] Add command timeout configuration (per-command TTL)
89//! - [ ] Implement command rate limiting (DoS protection)
90//! - [ ] Add command metrics collection (latency, success rate)
91//!
92//! Medium Priority:
93//! - [ ] Implement command aliasing (user-defined shortcuts)
94//! - [ ] Add command migration support (rename, deprecate)
95//! - [ ] Add comprehensive command audit logging
96//! - [ ] Support command chaining and composition
97//! - [ ] Implement command undo/redo integration
98//! - [ ] Split CreateEffectForRequest into individual effect modules
99//!
100//! Low Priority:
101//! - [ ] Add request tracing across the entire pipeline
102
103use std::{future::Future, pin::Pin, sync::Arc};
104
105use base64::{Engine as _, engine::general_purpose::STANDARD};
106use CommonLibrary::{
107	Command::CommandExecutor::CommandExecutor,
108	Configuration::{
109		ConfigurationInspector::ConfigurationInspector,
110		ConfigurationProvider::ConfigurationProvider,
111		DTO::ConfigurationTarget::ConfigurationTarget,
112	},
113	CustomEditor::CustomEditorProvider::CustomEditorProvider,
114	Debug::DebugService::DebugService,
115	Diagnostic::DiagnosticManager::DiagnosticManager,
116	Document::DocumentProvider::DocumentProvider,
117	Environment::Requires::Requires,
118	FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
119	Keybinding::KeybindingProvider::KeybindingProvider,
120	LanguageFeature::{
121		DTO::ProviderType::ProviderType,
122		LanguageFeatureProviderRegistry::LanguageFeatureProviderRegistry,
123	},
124	Search::SearchProvider::SearchProvider,
125	SourceControlManagement::SourceControlManagementProvider::SourceControlManagementProvider,
126	StatusBar::{DTO::StatusBarEntryDTO::StatusBarEntryDTO, StatusBarProvider::StatusBarProvider},
127	Storage::StorageProvider::StorageProvider,
128	Terminal::TerminalProvider::TerminalProvider,
129	TreeView::TreeViewProvider::TreeViewProvider,
130	UserInterface::{DTO::MessageSeverity::MessageSeverity, UserInterfaceProvider::UserInterfaceProvider},
131	Webview::WebviewProvider,
132};
133use serde_json::{Value, json};
134use tauri::{AppHandle, Runtime};
135use url::Url;
136use log::warn;
137
138use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, Track::Effect::MappedEffectType::MappedEffect};
139
140/// Maps a string-based method name (command or RPC) to its corresponding effect
141/// constructor, returning a boxed closure ([`MappedEffect`]) that can be
142/// executed by the ApplicationRunTime.
143///
144/// # Arguments
145/// - `ApplicationHandle`: Tauri app handle for accessing state
146/// - `MethodName`: The command/RPC method name to map
147/// - `Parameters`: JSON value containing parameters for the effect
148///
149/// # Returns
150/// `Result<MappedEffect, String>` - either a boxed async closure or an error
151/// if the command is unknown
152pub fn CreateEffectForRequest<R:Runtime>(
153	_ApplicationHandle:&AppHandle<R>,
154	MethodName:&str,
155	Parameters:Value,
156) -> Result<MappedEffect, String> {
157	match MethodName {
158		// Configuration
159		"Configuration.Inspect" => {
160			let effect =
161				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
162					Box::pin(async move {
163						let provider:Arc<dyn ConfigurationInspector> = run_time.Environment.Require();
164						let section = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
165						let result = provider.InspectConfigurationValue(section, Default::default()).await;
166						result.map(|_opt_dto| json!(null)).map_err(|e| e.to_string())
167					})
168				};
169			Ok(Box::new(effect))
170		},
171
172		"Configuration.Update" => {
173			let effect =
174				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
175					Box::pin(async move {
176						let provider:Arc<dyn ConfigurationProvider> = run_time.Environment.Require();
177						let key = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
178						let value = Parameters.get(1).cloned().unwrap_or_default();
179						let target = match Parameters.get(2).and_then(Value::as_u64) {
180							Some(0) => ConfigurationTarget::User,
181							Some(1) => ConfigurationTarget::Workspace,
182							_ => ConfigurationTarget::User,
183						};
184						let result = provider
185							.UpdateConfigurationValue(key, value, target, Default::default(), None)
186							.await;
187						result.map(|_| json!(null)).map_err(|e| e.to_string())
188					})
189				};
190			Ok(Box::new(effect))
191		},
192
193		// Diagnostics
194		"Diagnostic.Set" => {
195			let effect =
196				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
197					Box::pin(async move {
198						let provider:Arc<dyn DiagnosticManager> = run_time.Environment.Require();
199						let owner = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
200						let entries = Parameters.get(1).cloned().unwrap_or_default();
201						provider
202							.SetDiagnostics(owner, entries)
203							.await
204							.map(|_| json!(null))
205							.map_err(|e| e.to_string())
206					})
207				};
208			Ok(Box::new(effect))
209		},
210
211		"Diagnostic.Clear" => {
212			let effect =
213				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
214					Box::pin(async move {
215						let provider:Arc<dyn DiagnosticManager> = run_time.Environment.Require();
216						let owner = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
217						provider
218							.ClearDiagnostics(owner)
219							.await
220							.map(|_| json!(null))
221							.map_err(|e| e.to_string())
222					})
223				};
224			Ok(Box::new(effect))
225		},
226
227		// Language Features
228		"$languageFeatures:registerProvider" => {
229			let effect =
230				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
231					Box::pin(async move {
232						let provider:Arc<dyn LanguageFeatureProviderRegistry> = run_time.Environment.Require();
233						let id = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
234						let selector = Parameters.get(1).cloned().unwrap_or_default();
235						let extension_id = Parameters.get(2).cloned().unwrap_or_default();
236						let options = Parameters.get(3).cloned();
237						provider
238							.RegisterProvider(id, ProviderType::Hover, selector, extension_id, options)
239							.await
240							.map(|handle| json!(handle))
241							.map_err(|e| e.to_string())
242					})
243				};
244			Ok(Box::new(effect))
245		},
246
247		// Documents
248		"Document.Save" => {
249			let effect =
250				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
251					Box::pin(async move {
252						let document_provider:Arc<dyn DocumentProvider> = run_time.Environment.Require();
253						let uri_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
254						let uri = Url::parse(uri_str).unwrap_or_else(|_| Url::parse("file:///tmp/test.txt").unwrap());
255						document_provider
256							.SaveDocument(uri)
257							.await
258							.map(|success| json!(success))
259							.map_err(|e| e.to_string())
260					})
261				};
262			Ok(Box::new(effect))
263		},
264
265		"Document.SaveAs" => {
266			let effect =
267				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
268					Box::pin(async move {
269						let document_provider:Arc<dyn DocumentProvider> = run_time.Environment.Require();
270						let original_uri_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
271						let original_uri = Url::parse(original_uri_str)
272							.unwrap_or_else(|_| Url::parse("file:///tmp/test.txt").unwrap());
273						let target_uri = Parameters
274							.get(1)
275							.and_then(Value::as_str)
276							.map(Url::parse)
277							.transpose()
278							.unwrap_or(None);
279						document_provider
280							.SaveDocumentAs(original_uri, target_uri)
281							.await
282							.map(|uri_option| json!(uri_option))
283							.map_err(|e| e.to_string())
284					})
285				};
286			Ok(Box::new(effect))
287		},
288
289		// FileSystem
290		"FileSystem.ReadFile" => {
291			let effect =
292				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
293					Box::pin(async move {
294						let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
295						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
296						let path = std::path::PathBuf::from(path_str);
297						fs_reader
298							.ReadFile(&path)
299							.await
300							.map(|bytes| json!(bytes))
301							.map_err(|e| e.to_string())
302					})
303				};
304			Ok(Box::new(effect))
305		},
306
307		"FileSystem.WriteFile" => {
308			let effect =
309				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
310					Box::pin(async move {
311						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
312						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
313						let path = std::path::PathBuf::from(path_str);
314						let content = Parameters.get(1).cloned();
315						let content_bytes = match content {
316							Some(Value::Array(arr)) => {
317								arr.into_iter().filter_map(|v| v.as_u64().map(|n| n as u8)).collect()
318							},
319							Some(Value::String(s)) => STANDARD.decode(&s).unwrap_or_default(),
320							_ => vec![],
321						};
322						fs_writer
323							.WriteFile(&path, content_bytes, true, true)
324							.await
325							.map(|_| json!(null))
326							.map_err(|e| e.to_string())
327					})
328				};
329			Ok(Box::new(effect))
330		},
331
332		"FileSystem.ReadDirectory" => {
333			let effect =
334				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
335					Box::pin(async move {
336						let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
337						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
338						let path = std::path::PathBuf::from(path_str);
339						fs_reader
340							.ReadDirectory(&path)
341							.await
342							.map(|entries| json!(entries))
343							.map_err(|e| e.to_string())
344					})
345				};
346			Ok(Box::new(effect))
347		},
348
349		// Keybinding
350		"Keybinding.GetResolved" => {
351			let effect =
352				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
353					Box::pin(async move {
354						let provider:Arc<dyn KeybindingProvider> = run_time.Environment.Require();
355						provider.GetResolvedKeybinding().await.map_err(|e| e.to_string())
356					})
357				};
358			Ok(Box::new(effect))
359		},
360
361		// Search
362		"Search.TextSearch" => {
363			let effect =
364				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
365					Box::pin(async move {
366						let provider:Arc<dyn SearchProvider> = run_time.Environment.Require();
367						let query = Parameters.get(0).cloned().unwrap_or_default();
368						let options = Parameters.get(1).cloned().unwrap_or_default();
369						provider.TextSearch(query, options).await.map_err(|e| e.to_string())
370					})
371				};
372			Ok(Box::new(effect))
373		},
374
375		// Storage
376		"Storage.Get" => {
377			let effect =
378				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
379					Box::pin(async move {
380						let provider:Arc<dyn StorageProvider> = run_time.Environment.Require();
381						let key = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
382						provider
383							.GetStorageValue(false, &key)
384							.await
385							.map(|opt_val| json!(opt_val))
386							.map_err(|e| e.to_string())
387					})
388				};
389			Ok(Box::new(effect))
390		},
391
392		"Storage.Set" => {
393			let effect =
394				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
395					Box::pin(async move {
396						let provider:Arc<dyn StorageProvider> = run_time.Environment.Require();
397						let key = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
398						let value = Parameters.get(1).cloned();
399						provider
400							.UpdateStorageValue(false, key, value)
401							.await
402							.map(|_| json!(null))
403							.map_err(|e| e.to_string())
404					})
405				};
406			Ok(Box::new(effect))
407		},
408
409		// Commands
410		"Command.Execute" => {
411			let effect =
412				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
413					Box::pin(async move {
414						let command_executor:Arc<dyn CommandExecutor> = run_time.Environment.Require();
415						let command_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
416						let args = Parameters.get(1).cloned().unwrap_or_default();
417						command_executor
418							.ExecuteCommand(command_id, args)
419							.await
420							.map_err(|e| e.to_string())
421					})
422				};
423			Ok(Box::new(effect))
424		},
425
426		"Command.GetAll" => {
427			let effect =
428				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
429					Box::pin(async move {
430						let provider:Arc<dyn CommandExecutor> = run_time.Environment.Require();
431						provider
432							.GetAllCommands()
433							.await
434							.map(|cmds| json!(cmds))
435							.map_err(|e| e.to_string())
436					})
437				};
438			Ok(Box::new(effect))
439		},
440
441		// Status Bar
442		"$statusBar:set" => {
443			let effect =
444				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
445					Box::pin(async move {
446						let provider:Arc<dyn StatusBarProvider> = run_time.Environment.Require();
447						// Construct a minimal StatusBarEntryDTO from parameters
448						let text = Parameters.get(0).and_then(Value::as_str).unwrap_or("status").to_string();
449						let entry = StatusBarEntryDTO {
450							EntryIdentifier:"id".to_string(),
451							ItemIdentifier:"item".to_string(),
452							ExtensionIdentifier:"ext".to_string(),
453							Name:None,
454							Text:text,
455							Tooltip:None,
456							HasTooltipProvider:false,
457							Command:None,
458							Color:None,
459							BackgroundColor:None,
460							IsAlignedLeft:false,
461							Priority:None,
462							AccessibilityInformation:None,
463						};
464						provider
465							.SetStatusBarEntry(entry)
466							.await
467							.map(|_| json!(null))
468							.map_err(|e| e.to_string())
469					})
470				};
471			Ok(Box::new(effect))
472		},
473
474		"$statusBar:dispose" => {
475			let effect =
476				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
477					Box::pin(async move {
478						let provider:Arc<dyn StatusBarProvider> = run_time.Environment.Require();
479						let id = Parameters.get(0).and_then(Value::as_str).unwrap_or("id").to_string();
480						provider
481							.DisposeStatusBarEntry(id)
482							.await
483							.map(|_| json!(null))
484							.map_err(|e| e.to_string())
485					})
486				};
487			Ok(Box::new(effect))
488		},
489
490		"$setStatusBarMessage" => {
491			let effect =
492				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
493					Box::pin(async move {
494						let provider:Arc<dyn StatusBarProvider> = run_time.Environment.Require();
495						let message_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("msg_id").to_string();
496						let text = Parameters.get(1).and_then(Value::as_str).unwrap_or("message").to_string();
497						provider
498							.SetStatusBarMessage(message_id, text)
499							.await
500							.map(|_| json!(null))
501							.map_err(|e| e.to_string())
502					})
503				};
504			Ok(Box::new(effect))
505		},
506
507		"$disposeStatusBarMessage" => {
508			let effect =
509				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
510					Box::pin(async move {
511						let provider:Arc<dyn StatusBarProvider> = run_time.Environment.Require();
512						let message_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("msg_id").to_string();
513						provider
514							.DisposeStatusBarMessage(message_id)
515							.await
516							.map(|_| json!(null))
517							.map_err(|e| e.to_string())
518					})
519				};
520			Ok(Box::new(effect))
521		},
522
523		// User Interface
524		"UserInterface.ShowMessage" => {
525			let effect =
526				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
527					Box::pin(async move {
528						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
529						let severity_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("info");
530						let message = Parameters.get(1).and_then(Value::as_str).unwrap_or("").to_string();
531						let options = Parameters.get(2).cloned();
532						let severity = match severity_str {
533							"warning" => MessageSeverity::Warning,
534							"error" => MessageSeverity::Error,
535							_ => MessageSeverity::Info,
536						};
537						provider
538							.ShowMessage(severity, message, options)
539							.await
540							.map(|_| json!(null))
541							.map_err(|e| e.to_string())
542					})
543				};
544			Ok(Box::new(effect))
545		},
546
547		"UserInterface.ShowQuickPick" => {
548			let effect =
549				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
550					Box::pin(async move {
551						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
552						// Using default empty parameters for now
553						let (items, options) = (vec!(), None as Option<CommonLibrary::UserInterface::DTO::QuickPickOptionsDTO::QuickPickOptionsDTO>);
554						provider
555							.ShowQuickPick(items, options)
556							.await
557							.map(|selected_items| json!(selected_items))
558							.map_err(|e| e.to_string())
559					})
560				};
561			Ok(Box::new(effect))
562		},
563
564		"UserInterface.ShowInputBox" => {
565			let effect =
566				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
567					Box::pin(async move {
568						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
569						let options = if let Some(Value::Object(_obj)) = Parameters.get(0) {
570							// TODO: Properly deserialize to InputBoxOptionsDTO
571							Some(CommonLibrary::UserInterface::DTO::InputBoxOptionsDTO::InputBoxOptionsDTO::default())
572						} else {
573							None
574						};
575						provider
576							.ShowInputBox(options)
577							.await
578							.map(|input_opt| json!(input_opt))
579							.map_err(|e| e.to_string())
580					})
581				};
582			Ok(Box::new(effect))
583		},
584
585		"UserInterface.ShowOpenDialog" => {
586			let effect =
587				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
588					Box::pin(async move {
589						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
590						let options = if let Some(Value::Object(_obj)) = Parameters.get(0) {
591							// TODO: Properly deserialize to OpenDialogOptionsDTO
592							Some(Default::default())
593						} else {
594							None
595						};
596						provider
597							.ShowOpenDialog(options)
598							.await
599							.map(|path_buf_opt| json!(path_buf_opt))
600							.map_err(|e| e.to_string())
601					})
602				};
603			Ok(Box::new(effect))
604		},
605
606		"UserInterface.ShowSaveDialog" => {
607			let effect =
608				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
609					Box::pin(async move {
610						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
611						let options = if let Some(Value::Object(_obj)) = Parameters.get(0) {
612							// TODO: Properly deserialize to SaveDialogOptionsDTO
613							Some(Default::default())
614						} else {
615							None
616						};
617						provider
618							.ShowSaveDialog(options)
619							.await
620							.map(|path_buf_opt| json!(path_buf_opt))
621							.map_err(|e| e.to_string())
622					})
623				};
624			Ok(Box::new(effect))
625		},
626
627		// Terminal
628		"$terminal:create" => {
629			let effect =
630				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
631					Box::pin(async move {
632						let provider:Arc<dyn TerminalProvider> = run_time.Environment.Require();
633						let options = Parameters.get(0).cloned().unwrap_or_default();
634						provider.CreateTerminal(options).await.map_err(|e| e.to_string())
635					})
636				};
637			Ok(Box::new(effect))
638		},
639
640		"$terminal:sendText" => {
641			let effect =
642				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
643					Box::pin(async move {
644						let provider:Arc<dyn TerminalProvider> = run_time.Environment.Require();
645						let terminal_id = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u64).unwrap_or(0);
646						let text = Parameters.get(1).and_then(Value::as_str).unwrap_or("").to_string();
647						provider
648							.SendTextToTerminal(terminal_id, text)
649							.await
650							.map(|_| json!(null))
651							.map_err(|e| e.to_string())
652					})
653				};
654			Ok(Box::new(effect))
655		},
656
657		"$terminal:dispose" => {
658			let effect =
659				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
660					Box::pin(async move {
661						let provider:Arc<dyn TerminalProvider> = run_time.Environment.Require();
662						let terminal_id = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u64).unwrap_or(0);
663						provider
664							.DisposeTerminal(terminal_id)
665							.await
666							.map(|_| json!(null))
667							.map_err(|e| e.to_string())
668					})
669				};
670			Ok(Box::new(effect))
671		},
672
673		// Webview
674		"$webview:create" => {
675			let effect =
676				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
677					Box::pin(async move {
678						warn!("$webview:create not fully implemented");
679						Ok(json!({"handle": "webview-123"}))
680					})
681				};
682			Ok(Box::new(effect))
683		},
684
685		"$resolveCustomEditor" => {
686			let effect =
687				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
688					Box::pin(async move {
689						let provider:Arc<dyn CustomEditorProvider> = run_time.Environment.Require();
690						let view_type = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
691						let resource_uri_str = Parameters.get(1).and_then(Value::as_str).unwrap_or("");
692						let resource_uri = Url::parse(resource_uri_str)
693							.unwrap_or_else(|_| Url::parse("file:///tmp/test.txt").unwrap());
694						let webview_handle =
695							Parameters.get(2).and_then(Value::as_str).unwrap_or("webview-123").to_string();
696						provider
697							.ResolveCustomEditor(view_type, resource_uri, webview_handle)
698							.await
699							.map(|_| json!(null))
700							.map_err(|e| e.to_string())
701					})
702				};
703			Ok(Box::new(effect))
704		},
705
706		// Debug
707		"Debug.Start" => {
708			let effect =
709				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
710					Box::pin(async move {
711						let provider:Arc<dyn DebugService> = run_time.Environment.Require();
712						let folder_uri_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
713						let folder_uri = if folder_uri_str.is_empty() { None } else { Url::parse(folder_uri_str).ok() };
714						let configuration = Parameters.get(1).cloned().unwrap_or_else(|| json!({ "type": "node" }));
715						provider
716							.StartDebugging(folder_uri, configuration)
717							.await
718							.map(|session_id| json!(session_id))
719							.map_err(|e| e.to_string())
720					})
721				};
722			Ok(Box::new(effect))
723		},
724
725		"Debug.RegisterConfigurationProvider" => {
726			let effect =
727				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
728					Box::pin(async move {
729						let provider:Arc<dyn DebugService> = run_time.Environment.Require();
730						let debug_type = Parameters.get(0).and_then(Value::as_str).unwrap_or("node").to_string();
731						let provider_handle = Parameters.get(1).and_then(Value::as_i64).map(|n| n as u32).unwrap_or(1);
732						let sidecar_id = Parameters.get(2).and_then(Value::as_str).unwrap_or("cocoon-main").to_string();
733						provider
734							.RegisterDebugConfigurationProvider(debug_type, provider_handle, sidecar_id)
735							.await
736							.map(|_| json!(null))
737							.map_err(|e| e.to_string())
738					})
739				};
740			Ok(Box::new(effect))
741		},
742
743		// Tree View
744		"$tree:register" => {
745			let effect =
746				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
747					Box::pin(async move {
748						let provider:Arc<dyn TreeViewProvider> = run_time.Environment.Require();
749						let view_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("viewId").to_string();
750						let options = Parameters.get(1).cloned().unwrap_or_default();
751						provider
752							.RegisterTreeDataProvider(view_id, options)
753							.await
754							.map(|_| json!(null))
755							.map_err(|e| e.to_string())
756					})
757				};
758			Ok(Box::new(effect))
759		},
760
761		// Source Control Management
762		"$scm:createSourceControl" => {
763			let effect =
764				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
765					Box::pin(async move {
766						let provider:Arc<dyn SourceControlManagementProvider> = run_time.Environment.Require();
767						let resource = Parameters.get(0).cloned().unwrap_or_default();
768						provider
769							.CreateSourceControl(resource)
770							.await
771							.map(|handle| json!(handle))
772							.map_err(|e| e.to_string())
773					})
774				};
775			Ok(Box::new(effect))
776		},
777
778		"$scm:updateSourceControl" => {
779			let effect =
780				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
781					Box::pin(async move {
782						let provider:Arc<dyn SourceControlManagementProvider> = run_time.Environment.Require();
783						let handle = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u32).unwrap_or(0);
784						let update = Parameters.get(1).cloned().unwrap_or_default();
785						provider
786							.UpdateSourceControl(handle, update)
787							.await
788							.map(|_| json!(null))
789							.map_err(|e| e.to_string())
790					})
791				};
792			Ok(Box::new(effect))
793		},
794
795		"$scm:updateGroup" => {
796			let effect =
797				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
798					Box::pin(async move {
799						let provider:Arc<dyn SourceControlManagementProvider> = run_time.Environment.Require();
800						let handle = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u32).unwrap_or(0);
801						let group_data = Parameters.get(1).cloned().unwrap_or_default();
802						provider
803							.UpdateSourceControlGroup(handle, group_data)
804							.await
805							.map(|_| json!(null))
806							.map_err(|e| e.to_string())
807					})
808				};
809			Ok(Box::new(effect))
810		},
811
812		"$scm:registerInputBox" => {
813			let effect =
814				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
815					Box::pin(async move {
816						let provider:Arc<dyn SourceControlManagementProvider> = run_time.Environment.Require();
817						let handle = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u32).unwrap_or(0);
818						let options = Parameters.get(1).cloned().unwrap_or_default();
819						provider
820							.RegisterInputBox(handle, options)
821							.await
822							.map(|_| json!(null))
823							.map_err(|e| e.to_string())
824					})
825				};
826			Ok(Box::new(effect))
827		},
828
829		// Unknown command
830		_ => {
831			warn!("[EffectCreation] Unknown method: {}", MethodName);
832			Err(format!("Unknown method: {}", MethodName))
833		},
834	}
835}
836