1use 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
142fn 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
161fn 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 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
191fn 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 let Options = json!({ "tabSize": 4, "insertSpaces": true });
219
220 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 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 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
262fn 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 dev_log!("commands", "[Native Command] Saving document: {}", URI);
295
296 Ok(Value::Null)
297 })
298}
299
300fn 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 dev_log!("commands", "[Native Command] Closing document: {}", URI);
333
334 Ok(Value::Null)
335 })
336}
337
338fn 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 dev_log!("commands-verbose", "[Native Command] setContext: {}", Argument);
358
359 Ok(Value::Null)
360 })
361}
362
363fn 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
384fn 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 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
409fn 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 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 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
522fn ValidateCommandParameters(CommandName:&str, Arguments:&Value) -> Result<(), String> {
524 match CommandName {
525 "mountain.openFile" | "workbench.action.files.openFile" => {
526 Ok(())
528 },
529
530 "editor.action.formatDocument" => {
531 Ok(())
533 },
534
535 _ => Ok(()),
536 }
537}
538
539pub fn RegisterNativeCommands(
543 AppHandle:&AppHandle<Wry>,
544
545 ApplicationState:&Arc<ApplicationState>,
546) -> Result<(), CommonError> {
547 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 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 CommandRegistry.insert("setContext".to_string(), CommandHandler::Native(CommandSetContext));
591
592 CommandRegistry.insert("vscode.open".to_string(), CommandHandler::Native(CommandVscodeOpen));
597
598 CommandRegistry.insert("vscode.openFolder".to_string(), CommandHandler::Native(CommandVscodeOpen));
599
600 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 dev_log!("commands", "[Bootstrap] Validating registered commands...");
621
622 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 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}