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 Ok(Value::Null)
359 })
360}
361
362fn 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
382fn 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 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
407fn 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 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#[allow(dead_code)]
473fn ValidateCommandParameters(CommandName:&str, Arguments:&Value) -> Result<(), String> {
474 match CommandName {
475 "mountain.openFile" | "workbench.action.files.openFile" => {
476 Ok(())
478 },
479 "editor.action.formatDocument" => {
480 Ok(())
482 },
483 _ => Ok(()),
484 }
485}
486
487pub fn RegisterNativeCommands(
491 AppHandle:&AppHandle<Wry>,
492
493 ApplicationState:&Arc<ApplicationState>,
494) -> Result<(), CommonError> {
495 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 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 CommandRegistry.insert("setContext".to_string(), CommandHandler::Native(CommandSetContext));
539
540 CommandRegistry.insert("vscode.open".to_string(), CommandHandler::Native(CommandVscodeOpen));
545 CommandRegistry.insert("vscode.openFolder".to_string(), CommandHandler::Native(CommandVscodeOpen));
546
547 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 dev_log!("commands", "[Bootstrap] Validating registered commands...");
567 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 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}