1#![allow(unused_variables, dead_code)]
2
3use std::{collections::HashMap, path::PathBuf, sync::Arc};
173
174use serde_json::{Value, json};
175use tauri::{AppHandle, Manager};
176use CommonLibrary::Configuration::DTO::{
178 ConfigurationOverridesDTO as ConfigurationOverridesDTOModule,
179 ConfigurationTarget as ConfigurationTargetModule,
180};
181
182use crate::dev_log;
183type ConfigurationOverridesDTO = ConfigurationOverridesDTOModule::ConfigurationOverridesDTO;
184type ConfigurationTarget = ConfigurationTargetModule::ConfigurationTarget;
185
186use CommonLibrary::{
187 Command::CommandExecutor::CommandExecutor,
188 Configuration::ConfigurationProvider::ConfigurationProvider,
189 Environment::Requires::Requires,
190 Error::CommonError::CommonError,
191 ExtensionManagement::ExtensionManagementService::ExtensionManagementService,
192 FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
193 Storage::StorageProvider::StorageProvider,
194};
195
196use crate::{
197 ApplicationState::DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO,
198 RunTime::ApplicationRunTime::ApplicationRunTime,
199};
200
201pub fn extract_path_from_arg(Arg:&Value) -> Result<String, String> {
208 if let Some(Path) = Arg.as_str() {
210 return Ok(normalize_uri_path(Path));
211 }
212 if let Some(Object) = Arg.as_object() {
214 if let Some(FsPath) = Object.get("fsPath").and_then(|V| V.as_str()) {
216 if !FsPath.is_empty() {
217 return Ok(FsPath.to_string());
218 }
219 }
220 if let Some(Path) = Object.get("path").and_then(|V| V.as_str()) {
222 if !Path.is_empty() {
223 return Ok(normalize_uri_path(Path));
224 }
225 }
226 if let Some(External) = Object.get("external").and_then(|V| V.as_str()) {
228 if External.starts_with("file://") {
229 let Stripped = External.trim_start_matches("file://");
230 return Ok(normalize_uri_path(Stripped));
231 }
232 }
233 }
234 Err("File path must be a string or URI object with path/fsPath field".to_string())
235}
236
237fn normalize_uri_path(Path:&str) -> String {
243 let Decoded = percent_decode(Path);
244
245 let Resolved = resolve_userdata_path(&Decoded);
250
251 let Resolved = resolve_static_application_path(&Resolved);
256
257 #[cfg(target_os = "windows")]
258 {
259 let Trimmed = if Resolved.len() >= 3 && Resolved.starts_with('/') && Resolved.as_bytes().get(2) == Some(&b':') {
261 Resolved[1..].to_string()
263 } else {
264 Resolved
265 };
266 Trimmed.replace('/', "\\")
268 }
269
270 #[cfg(not(target_os = "windows"))]
271 {
272 Resolved
273 }
274}
275
276fn resolve_userdata_path(Path:&str) -> String {
281 if !Path.starts_with("/User/") && Path != "/User" {
282 return Path.to_string();
283 }
284
285 let UserDataBase = get_userdata_base_dir();
286 let Resolved = format!("{}{}", UserDataBase, Path);
287 dev_log!("vfs", "resolve_userdata: {} -> {}", Path, Resolved);
288 Resolved
289}
290
291static STATIC_APPLICATION_ROOT:std::sync::OnceLock<String> = std::sync::OnceLock::new();
301
302pub fn set_static_application_root(Path:String) { let _ = STATIC_APPLICATION_ROOT.set(Path); }
305
306pub fn get_static_application_root() -> Option<String> { STATIC_APPLICATION_ROOT.get().cloned() }
309
310fn resolve_static_application_path(Path:&str) -> String {
311 if !Path.starts_with("/Static/Application/") && Path != "/Static/Application" {
312 return Path.to_string();
313 }
314
315 if let Some(Root) = STATIC_APPLICATION_ROOT.get() {
316 let Relative = Path.strip_prefix("/Static/Application").unwrap_or("");
317 let Resolved = format!("{}/Static/Application{}", Root, Relative);
318 dev_log!("vfs", "resolve_static: {} -> {}", Path, Resolved);
319 Resolved
320 } else {
321 Path.to_string()
323 }
324}
325
326static USERDATA_BASE_DIR:std::sync::OnceLock<String> = std::sync::OnceLock::new();
330
331static SESSION_TIMESTAMP:std::sync::OnceLock<String> = std::sync::OnceLock::new();
334
335pub fn set_userdata_base_dir(Path:String) { let _ = USERDATA_BASE_DIR.set(Path); }
337
338fn get_userdata_base_dir() -> String {
341 if let Some(Dir) = USERDATA_BASE_DIR.get() {
342 return Dir.clone();
343 }
344 if let Ok(Home) = std::env::var("HOME") {
346 #[cfg(target_os = "macos")]
347 return format!("{}/Library/Application Support/Land", Home);
348 #[cfg(target_os = "linux")]
349 return format!("{}/.local/share/Land", Home);
350 }
351 "/tmp/Land".to_string()
352}
353
354fn percent_decode(Input:&str) -> String {
357 let mut Result = String::with_capacity(Input.len());
358 let Bytes = Input.as_bytes();
359 let mut I = 0;
360
361 while I < Bytes.len() {
362 if Bytes[I] == b'%' && I + 2 < Bytes.len() {
363 let High = hex_digit(Bytes[I + 1]);
364 let Low = hex_digit(Bytes[I + 2]);
365 if let (Some(H), Some(L)) = (High, Low) {
366 Result.push((H * 16 + L) as char);
367 I += 3;
368 continue;
369 }
370 }
371 Result.push(Bytes[I] as char);
372 I += 1;
373 }
374
375 Result
376}
377
378fn hex_digit(Byte:u8) -> Option<u8> {
379 match Byte {
380 b'0'..=b'9' => Some(Byte - b'0'),
381 b'a'..=b'f' => Some(Byte - b'a' + 10),
382 b'A'..=b'F' => Some(Byte - b'A' + 10),
383 _ => None,
384 }
385}
386
387pub fn metadata_to_istat(Metadata:&std::fs::Metadata) -> Value {
389 let FileType = if Metadata.is_symlink() {
390 64 } else if Metadata.is_dir() {
392 2 } else {
394 1 };
396
397 let Size = Metadata.len();
398
399 let Mtime = Metadata
400 .modified()
401 .ok()
402 .and_then(|T| T.duration_since(std::time::UNIX_EPOCH).ok())
403 .map(|D| D.as_millis() as u64)
404 .unwrap_or(0);
405
406 let Ctime = Metadata
407 .created()
408 .ok()
409 .and_then(|T| T.duration_since(std::time::UNIX_EPOCH).ok())
410 .map(|D| D.as_millis() as u64)
411 .unwrap_or(Mtime);
412
413 json!({
414 "type": FileType,
415 "size": Size,
416 "mtime": Mtime,
417 "ctime": Ctime
418 })
419}
420
421static USERDATA_INITIALIZED:std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
424
425fn ensure_userdata_dirs() {
426 if USERDATA_INITIALIZED.swap(true, std::sync::atomic::Ordering::Relaxed) {
427 return; }
429
430 let Base = get_userdata_base_dir();
431 let Dirs = [
432 format!("{}/User", Base),
433 format!("{}/User/globalStorage", Base),
434 format!("{}/User/profiles/__default__profile__", Base),
435 format!("{}/User/snippets", Base),
436 format!("{}/User/prompts", Base),
437 format!("{}/User/cacheHome", Base),
438 format!("{}/logs", Base),
439 format!("{}/User/workspaceStorage", Base),
440 format!(
441 "{}/CachedConfigurations/defaults/__default__profile__-configurationDefaultsOverrides",
442 Base
443 ),
444 ];
445
446 for Dir in &Dirs {
447 if let Err(E) = std::fs::create_dir_all(Dir) {
448 dev_log!("lifecycle", "Failed to create userdata dir {}: {}", Dir, E);
449 }
450 }
451
452 let DefaultFiles = [
454 (format!("{}/User/settings.json", Base), "{}"),
455 (format!("{}/User/keybindings.json", Base), "[]"),
456 (format!("{}/User/tasks.json", Base), "{}"),
457 (format!("{}/User/extensions.json", Base), "[]"),
458 (format!("{}/User/mcp.json", Base), "{}"),
459 ];
460
461 for (FilePath, DefaultContent) in &DefaultFiles {
462 if !std::path::Path::new(FilePath).exists() {
463 if let Err(E) = std::fs::write(FilePath, DefaultContent) {
464 dev_log!("lifecycle", "Failed to create default file {}: {}", FilePath, E);
465 }
466 }
467 }
468
469 dev_log!("lifecycle", "userdata dirs initialized at: {}/User/", Base);
470}
471
472#[tauri::command]
475pub async fn mountain_ipc_invoke(app_handle:AppHandle, command:String, args:Vec<Value>) -> Result<Value, String> {
476 let OTLPStart = crate::IPC::DevLog::NowNano();
477 dev_log!("ipc", "invoke: {} args_count={}", command, args.len());
478
479 ensure_userdata_dirs();
481
482 let runtime = app_handle.state::<Arc<ApplicationRunTime>>();
484
485 let Result = match command.as_str() {
496 "configuration:get" => {
498 dev_log!("config", "configuration:get");
499 handle_configuration_get(runtime.inner().clone(), args).await
500 },
501 "configuration:update" => {
502 dev_log!("config", "configuration:update");
503 handle_configuration_update(runtime.inner().clone(), args).await
504 },
505
506 "logger:log"
508 | "logger:warn"
509 | "logger:error"
510 | "logger:info"
511 | "logger:debug"
512 | "logger:trace"
513 | "logger:critical"
514 | "logger:flush"
515 | "logger:setLevel"
516 | "logger:getLevel"
517 | "logger:createLogger"
518 | "logger:registerLogger"
519 | "logger:deregisterLogger"
520 | "logger:getRegisteredLoggers"
521 | "logger:setVisibility" => Ok(Value::Null),
522
523 "file:read" => handle_file_read_native(args).await,
525 "file:write" => handle_file_write_native(args).await,
526 "file:stat" => handle_file_stat_native(args).await,
527 "file:exists" => handle_file_exists_native(args).await,
528 "file:delete" => handle_file_delete_native(args).await,
529 "file:copy" => handle_file_clone_native(args).await,
530 "file:move" => handle_file_rename_native(args).await,
531 "file:mkdir" => handle_file_mkdir_native(args).await,
532 "file:readdir" => handle_file_readdir_native(args).await,
533 "file:readBinary" => handle_file_read_binary(runtime.inner().clone(), args).await,
534 "file:writeBinary" => handle_file_write_binary(runtime.inner().clone(), args).await,
535
536 "storage:get" => handle_storage_get(runtime.inner().clone(), args).await,
538 "storage:set" => handle_storage_set(runtime.inner().clone(), args).await,
539 "storage:getItems" => {
540 dev_log!("storage", "storage:getItems");
541 handle_storage_get_items(runtime.inner().clone(), args).await
542 },
543 "storage:updateItems" => {
544 dev_log!("storage", "storage:updateItems");
545 handle_storage_update_items(runtime.inner().clone(), args).await
546 },
547 "storage:optimize" => {
548 dev_log!("storage", "storage:optimize");
549 Ok(Value::Null)
550 },
551 "storage:isUsed" => {
552 dev_log!("storage", "storage:isUsed");
553 Ok(Value::Null)
554 },
555 "storage:close" => {
556 dev_log!("storage", "storage:close");
557 Ok(Value::Null)
558 },
559
560 "environment:get" => {
562 dev_log!("config", "environment:get");
563 handle_environment_get(runtime.inner().clone(), args).await
564 },
565
566 "native:showItemInFolder" => handle_show_item_in_folder(runtime.inner().clone(), args).await,
568 "native:openExternal" => handle_open_external(runtime.inner().clone(), args).await,
569
570 "workbench:getConfiguration" => handle_workbench_configuration(runtime.inner().clone(), args).await,
572
573 "commands:execute" => handle_commands_execute(runtime.inner().clone(), args).await,
575 "commands:getAll" => {
576 dev_log!("commands", "commands:getAll");
577 handle_commands_get_all(runtime.inner().clone()).await
578 },
579
580 "extensions:getAll" => {
582 dev_log!("extensions", "extensions:getAll");
583 handle_extensions_get_all(runtime.inner().clone()).await
584 },
585 "extensions:get" => {
586 dev_log!("extensions", "extensions:get");
587 handle_extensions_get(runtime.inner().clone(), args).await
588 },
589 "extensions:isActive" => {
590 dev_log!("extensions", "extensions:isActive");
591 handle_extensions_is_active(runtime.inner().clone(), args).await
592 },
593
594 "terminal:create" => {
596 dev_log!("terminal", "terminal:create");
597 handle_terminal_create(runtime.inner().clone(), args).await
598 },
599 "terminal:sendText" => {
600 dev_log!("terminal", "terminal:sendText");
601 handle_terminal_send_text(runtime.inner().clone(), args).await
602 },
603 "terminal:dispose" => {
604 dev_log!("terminal", "terminal:dispose");
605 handle_terminal_dispose(runtime.inner().clone(), args).await
606 },
607 "terminal:show" => {
608 dev_log!("terminal", "terminal:show");
609 handle_terminal_show(runtime.inner().clone(), args).await
610 },
611 "terminal:hide" => {
612 dev_log!("terminal", "terminal:hide");
613 handle_terminal_hide(runtime.inner().clone(), args).await
614 },
615
616 "output:create" => handle_output_create(app_handle.clone(), args).await,
618 "output:append" => {
619 dev_log!("output", "output:append");
620 handle_output_append(app_handle.clone(), args).await
621 },
622 "output:appendLine" => {
623 dev_log!("output", "output:appendLine");
624 handle_output_append_line(app_handle.clone(), args).await
625 },
626 "output:clear" => {
627 dev_log!("output", "output:clear");
628 handle_output_clear(app_handle.clone(), args).await
629 },
630 "output:show" => {
631 dev_log!("output", "output:show");
632 handle_output_show(app_handle.clone(), args).await
633 },
634
635 "textFile:read" => {
637 dev_log!("textfile", "textFile:read");
638 handle_textfile_read(runtime.inner().clone(), args).await
639 },
640 "textFile:write" => {
641 dev_log!("textfile", "textFile:write");
642 handle_textfile_write(runtime.inner().clone(), args).await
643 },
644 "textFile:save" => handle_textfile_save(runtime.inner().clone(), args).await,
645
646 "storage:delete" => {
648 dev_log!("storage", "storage:delete");
649 handle_storage_delete(runtime.inner().clone(), args).await
650 },
651 "storage:keys" => {
652 dev_log!("storage", "storage:keys");
653 handle_storage_keys(runtime.inner().clone()).await
654 },
655
656 "notification:show" => {
658 dev_log!("notification", "notification:show");
659 handle_notification_show(app_handle.clone(), args).await
660 },
661 "notification:showProgress" => {
662 dev_log!("notification", "notification:showProgress");
663 handle_notification_show_progress(app_handle.clone(), args).await
664 },
665 "notification:updateProgress" => {
666 dev_log!("notification", "notification:updateProgress");
667 handle_notification_update_progress(app_handle.clone(), args).await
668 },
669 "notification:endProgress" => {
670 dev_log!("notification", "notification:endProgress");
671 handle_notification_end_progress(app_handle.clone(), args).await
672 },
673
674 "progress:begin" => {
676 dev_log!("progress", "progress:begin");
677 handle_progress_begin(app_handle.clone(), args).await
678 },
679 "progress:report" => {
680 dev_log!("progress", "progress:report");
681 handle_progress_report(app_handle.clone(), args).await
682 },
683 "progress:end" => {
684 dev_log!("progress", "progress:end");
685 handle_progress_end(app_handle.clone(), args).await
686 },
687
688 "quickInput:showQuickPick" => {
690 dev_log!("quickinput", "quickInput:showQuickPick");
691 handle_quick_input_show_quick_pick(runtime.inner().clone(), args).await
692 },
693 "quickInput:showInputBox" => {
694 dev_log!("quickinput", "quickInput:showInputBox");
695 handle_quick_input_show_input_box(runtime.inner().clone(), args).await
696 },
697
698 "workspaces:getFolders" => {
700 dev_log!("workspaces", "workspaces:getFolders");
701 handle_workspaces_get_folders(runtime.inner().clone()).await
702 },
703 "workspaces:addFolder" => {
704 dev_log!("workspaces", "workspaces:addFolder");
705 handle_workspaces_add_folder(runtime.inner().clone(), args).await
706 },
707 "workspaces:removeFolder" => {
708 dev_log!("workspaces", "workspaces:removeFolder");
709 handle_workspaces_remove_folder(runtime.inner().clone(), args).await
710 },
711 "workspaces:getName" => {
712 dev_log!("workspaces", "workspaces:getName");
713 handle_workspaces_get_name(runtime.inner().clone()).await
714 },
715
716 "themes:getActive" => {
718 dev_log!("themes", "themes:getActive");
719 handle_themes_get_active(runtime.inner().clone()).await
720 },
721 "themes:list" => {
722 dev_log!("themes", "themes:list");
723 handle_themes_list(runtime.inner().clone()).await
724 },
725 "themes:set" => {
726 dev_log!("themes", "themes:set");
727 handle_themes_set(runtime.inner().clone(), args).await
728 },
729
730 "search:findInFiles" => {
732 dev_log!("search", "search:findInFiles");
733 handle_search_find_in_files(runtime.inner().clone(), args).await
734 },
735 "search:findFiles" => {
736 dev_log!("search", "search:findFiles");
737 handle_search_find_files(runtime.inner().clone(), args).await
738 },
739
740 "decorations:get" => {
742 dev_log!("decorations", "decorations:get");
743 handle_decorations_get(runtime.inner().clone(), args).await
744 },
745 "decorations:getMany" => {
746 dev_log!("decorations", "decorations:getMany");
747 handle_decorations_get_many(runtime.inner().clone(), args).await
748 },
749 "decorations:set" => {
750 dev_log!("decorations", "decorations:set");
751 handle_decorations_set(runtime.inner().clone(), args).await
752 },
753 "decorations:clear" => {
754 dev_log!("decorations", "decorations:clear");
755 handle_decorations_clear(runtime.inner().clone(), args).await
756 },
757
758 "workingCopy:isDirty" => {
760 dev_log!("workingcopy", "workingCopy:isDirty");
761 handle_working_copy_is_dirty(runtime.inner().clone(), args).await
762 },
763 "workingCopy:setDirty" => {
764 dev_log!("workingcopy", "workingCopy:setDirty");
765 handle_working_copy_set_dirty(runtime.inner().clone(), args).await
766 },
767 "workingCopy:getAllDirty" => {
768 dev_log!("workingcopy", "workingCopy:getAllDirty");
769 handle_working_copy_get_all_dirty(runtime.inner().clone()).await
770 },
771 "workingCopy:getDirtyCount" => {
772 dev_log!("workingcopy", "workingCopy:getDirtyCount");
773 handle_working_copy_get_dirty_count(runtime.inner().clone()).await
774 },
775
776 "keybinding:add" => {
778 dev_log!("keybinding", "keybinding:add");
779 handle_keybinding_add(runtime.inner().clone(), args).await
780 },
781 "keybinding:remove" => {
782 dev_log!("keybinding", "keybinding:remove");
783 handle_keybinding_remove(runtime.inner().clone(), args).await
784 },
785 "keybinding:lookup" => {
786 dev_log!("keybinding", "keybinding:lookup");
787 handle_keybinding_lookup(runtime.inner().clone(), args).await
788 },
789 "keybinding:getAll" => {
790 dev_log!("keybinding", "keybinding:getAll");
791 handle_keybinding_get_all(runtime.inner().clone()).await
792 },
793
794 "lifecycle:getPhase" => {
796 dev_log!("lifecycle", "lifecycle:getPhase");
797 handle_lifecycle_get_phase(runtime.inner().clone()).await
798 },
799 "lifecycle:whenPhase" => {
800 dev_log!("lifecycle", "lifecycle:whenPhase");
801 handle_lifecycle_when_phase(runtime.inner().clone(), args).await
802 },
803 "lifecycle:requestShutdown" => {
804 dev_log!("lifecycle", "lifecycle:requestShutdown");
805 handle_lifecycle_request_shutdown(app_handle.clone()).await
806 },
807
808 "label:getUri" => {
810 dev_log!("label", "label:getUri");
811 handle_label_get_uri(runtime.inner().clone(), args).await
812 },
813 "label:getWorkspace" => {
814 dev_log!("label", "label:getWorkspace");
815 handle_label_get_workspace(runtime.inner().clone()).await
816 },
817 "label:getBase" => {
818 dev_log!("label", "label:getBase");
819 handle_label_get_base(args).await
820 },
821
822 "model:open" => {
824 dev_log!("model", "model:open");
825 handle_model_open(runtime.inner().clone(), args).await
826 },
827 "model:close" => {
828 dev_log!("model", "model:close");
829 handle_model_close(runtime.inner().clone(), args).await
830 },
831 "model:get" => {
832 dev_log!("model", "model:get");
833 handle_model_get(runtime.inner().clone(), args).await
834 },
835 "model:getAll" => {
836 dev_log!("model", "model:getAll");
837 handle_model_get_all(runtime.inner().clone()).await
838 },
839 "model:updateContent" => {
840 dev_log!("model", "model:updateContent");
841 handle_model_update_content(runtime.inner().clone(), args).await
842 },
843
844 "history:goBack" => {
846 dev_log!("history", "history:goBack");
847 handle_history_go_back(runtime.inner().clone()).await
848 },
849 "history:goForward" => {
850 dev_log!("history", "history:goForward");
851 handle_history_go_forward(runtime.inner().clone()).await
852 },
853 "history:canGoBack" => {
854 dev_log!("history", "history:canGoBack");
855 handle_history_can_go_back(runtime.inner().clone()).await
856 },
857 "history:canGoForward" => {
858 dev_log!("history", "history:canGoForward");
859 handle_history_can_go_forward(runtime.inner().clone()).await
860 },
861 "history:push" => {
862 dev_log!("history", "history:push");
863 handle_history_push(runtime.inner().clone(), args).await
864 },
865 "history:clear" => {
866 dev_log!("history", "history:clear");
867 handle_history_clear(runtime.inner().clone()).await
868 },
869 "history:getStack" => {
870 dev_log!("history", "history:getStack");
871 handle_history_get_stack(runtime.inner().clone()).await
872 },
873
874 "mountain_get_status" => {
876 let status = json!({
877 "connected": true,
878 "version": "1.0.0"
879 });
880 Ok(status)
881 },
882 "mountain_get_configuration" => {
883 let config = json!({
884 "editor": { "theme": "dark" },
885 "extensions": { "installed": [] }
886 });
887 Ok(config)
888 },
889 "mountain_get_services_status" => {
890 let services = json!({
891 "editor": { "status": "running" },
892 "extensionHost": { "status": "running" }
893 });
894 Ok(services)
895 },
896 "mountain_get_state" => {
897 let state = json!({
898 "ui": {},
899 "editor": {},
900 "workspace": {}
901 });
902 Ok(state)
903 },
904
905 "file:readFile" => handle_file_read_native(args).await,
911 "file:writeFile" => handle_file_write_native(args).await,
912 "file:rename" => handle_file_rename_native(args).await,
913 "file:realpath" => handle_file_realpath(args).await,
914 "file:watch" => {
915 dev_log!("vfs", "file:watch stub - no-op");
916 Ok(Value::Null)
917 },
918 "file:unwatch" => {
919 dev_log!("vfs", "file:unwatch stub - no-op");
920 Ok(Value::Null)
921 },
922 "file:open" => {
923 dev_log!("vfs", "file:open stub - no fd support yet");
924 Ok(json!(0))
925 },
926 "file:close" => {
927 dev_log!("vfs", "file:close stub");
928 Ok(Value::Null)
929 },
930 "file:cloneFile" => handle_file_clone_native(args).await,
931
932 "nativeHost:pickFolderAndOpen" => handle_native_pick_folder(app_handle.clone(), args).await,
938 "nativeHost:pickFileAndOpen" => handle_native_pick_folder(app_handle.clone(), args).await,
939 "nativeHost:pickFileFolderAndOpen" => handle_native_pick_folder(app_handle.clone(), args).await,
940 "nativeHost:pickWorkspaceAndOpen" => handle_native_pick_folder(app_handle.clone(), args).await,
941 "nativeHost:showOpenDialog" => handle_native_show_open_dialog(app_handle.clone(), args).await,
942 "nativeHost:showSaveDialog" => Ok(json!({ "canceled": true })),
943 "nativeHost:showMessageBox" => Ok(json!({ "response": 0 })),
944
945 "nativeHost:getEnvironmentPaths" => {
949 let PathResolver = app_handle.path();
950 let AppDataDir = PathResolver.app_data_dir().unwrap_or_default();
951 let HomeDir = PathResolver.home_dir().unwrap_or_default();
952 let TmpDir = std::env::temp_dir();
953
954 let SessionTimestamp =
960 SESSION_TIMESTAMP.get_or_init(|| chrono::Local::now().format("%Y%m%dT%H%M%S").to_string());
961 let SessionLogRoot = AppDataDir.join("logs").join(SessionTimestamp);
962 let SessionLogWindowDir = SessionLogRoot.join("window1");
963 let _ = std::fs::create_dir_all(&SessionLogWindowDir);
964
965 dev_log!(
966 "config",
967 "getEnvironmentPaths: userDataDir={} logsPath={} homeDir={}",
968 AppDataDir.display(),
969 SessionLogRoot.display(),
970 HomeDir.display()
971 );
972 let DevLogEnv = std::env::var("LAND_DEV_LOG").unwrap_or_default();
973 Ok(json!({
974 "userDataDir": AppDataDir.to_string_lossy(),
975 "logsPath": SessionLogRoot.to_string_lossy(),
976 "homeDir": HomeDir.to_string_lossy(),
977 "tmpDir": TmpDir.to_string_lossy(),
978 "devLog": if DevLogEnv.is_empty() { Value::Null } else { json!(DevLogEnv) },
979 }))
980 },
981
982 "nativeHost:getOSColorScheme" => {
984 dev_log!("nativehost", "nativeHost:getOSColorScheme");
985 handle_native_get_color_scheme().await
986 },
987 "nativeHost:getOSProperties" => {
988 dev_log!("nativehost", "nativeHost:getOSProperties");
989 handle_native_os_properties().await
990 },
991 "nativeHost:getOSStatistics" => {
992 dev_log!("nativehost", "nativeHost:getOSStatistics");
993 handle_native_os_statistics().await
994 },
995 "nativeHost:getOSVirtualMachineHint" => {
996 dev_log!("nativehost", "nativeHost:getOSVirtualMachineHint");
997 Ok(json!(0))
998 },
999
1000 "nativeHost:isWindowAlwaysOnTop" => {
1002 dev_log!("window", "nativeHost:isWindowAlwaysOnTop");
1003 Ok(json!(false))
1004 },
1005 "nativeHost:isFullScreen" => {
1006 dev_log!("window", "nativeHost:isFullScreen");
1007 handle_native_is_fullscreen(app_handle.clone()).await
1008 },
1009 "nativeHost:isMaximized" => {
1010 dev_log!("window", "nativeHost:isMaximized");
1011 handle_native_is_maximized(app_handle.clone()).await
1012 },
1013 "nativeHost:getActiveWindowId" => {
1014 dev_log!("window", "nativeHost:getActiveWindowId");
1015 Ok(json!(1))
1016 },
1017 "nativeHost:getWindows" => Ok(json!([{ "id": 1, "title": "Land", "filename": "" }])),
1018 "nativeHost:getWindowCount" => Ok(json!(1)),
1019
1020 "nativeHost:updateWindowControls"
1022 | "nativeHost:setMinimumSize"
1023 | "nativeHost:notifyReady"
1024 | "nativeHost:saveWindowSplash"
1025 | "nativeHost:updateTouchBar"
1026 | "nativeHost:focusWindow"
1027 | "nativeHost:maximizeWindow"
1028 | "nativeHost:minimizeWindow"
1029 | "nativeHost:unmaximizeWindow"
1030 | "nativeHost:moveWindowTop"
1031 | "nativeHost:positionWindow"
1032 | "nativeHost:toggleFullScreen"
1033 | "nativeHost:setWindowAlwaysOnTop"
1034 | "nativeHost:toggleWindowAlwaysOnTop"
1035 | "nativeHost:closeWindow"
1036 | "nativeHost:setDocumentEdited"
1037 | "nativeHost:setRepresentedFilename"
1038 | "nativeHost:setBackgroundThrottling"
1039 | "nativeHost:updateWindowAccentColor" => {
1040 dev_log!("window", "{}", command);
1041 Ok(Value::Null)
1042 },
1043
1044 "nativeHost:isAdmin" => Ok(json!(false)),
1046 "nativeHost:isRunningUnderARM64Translation" => {
1047 #[cfg(target_os = "macos")]
1048 {
1049 let Output = std::process::Command::new("sysctl")
1051 .args(["-n", "sysctl.proc_translated"])
1052 .output();
1053 let IsTranslated = Output
1054 .ok()
1055 .map(|O| String::from_utf8_lossy(&O.stdout).trim() == "1")
1056 .unwrap_or(false);
1057 Ok(json!(IsTranslated))
1058 }
1059 #[cfg(not(target_os = "macos"))]
1060 {
1061 Ok(json!(false))
1062 }
1063 },
1064 "nativeHost:hasWSLFeatureInstalled" => {
1065 #[cfg(target_os = "windows")]
1066 {
1067 Ok(json!(std::path::Path::new("C:\\Windows\\System32\\wsl.exe").exists()))
1068 }
1069 #[cfg(not(target_os = "windows"))]
1070 {
1071 Ok(json!(false))
1072 }
1073 },
1074 "nativeHost:showItemInFolder" => handle_show_item_in_folder(runtime.inner().clone(), args).await,
1075 "nativeHost:openExternal" => handle_open_external(runtime.inner().clone(), args).await,
1076 "nativeHost:moveItemToTrash" => Ok(Value::Null),
1077
1078 "nativeHost:readClipboardText" => {
1080 dev_log!("clipboard", "readClipboardText");
1081 Ok(json!(""))
1082 },
1083 "nativeHost:writeClipboardText" => {
1084 dev_log!("clipboard", "writeClipboardText");
1085 Ok(Value::Null)
1086 },
1087 "nativeHost:readClipboardFindText" => {
1088 dev_log!("clipboard", "readClipboardFindText");
1089 Ok(json!(""))
1090 },
1091 "nativeHost:writeClipboardFindText" => {
1092 dev_log!("clipboard", "writeClipboardFindText");
1093 Ok(Value::Null)
1094 },
1095 "nativeHost:readClipboardBuffer" => {
1096 dev_log!("clipboard", "readClipboardBuffer");
1097 Ok(json!([]))
1098 },
1099 "nativeHost:writeClipboardBuffer" => {
1100 dev_log!("clipboard", "writeClipboardBuffer");
1101 Ok(Value::Null)
1102 },
1103 "nativeHost:hasClipboard" => {
1104 dev_log!("clipboard", "hasClipboard");
1105 Ok(json!(false))
1106 },
1107 "nativeHost:readImage" => {
1108 dev_log!("clipboard", "readImage");
1109 Ok(json!([]))
1110 },
1111 "nativeHost:triggerPaste" => {
1112 dev_log!("clipboard", "triggerPaste");
1113 Ok(Value::Null)
1114 },
1115
1116 "nativeHost:getProcessId" => Ok(json!(std::process::id())),
1118 "nativeHost:killProcess" => Ok(Value::Null),
1119
1120 "nativeHost:findFreePort" => handle_native_find_free_port(args).await,
1122 "nativeHost:isPortFree" => Ok(json!(true)),
1123 "nativeHost:resolveProxy" => Ok(Value::Null),
1124 "nativeHost:lookupAuthorization" => Ok(Value::Null),
1125 "nativeHost:lookupKerberosAuthorization" => Ok(Value::Null),
1126 "nativeHost:loadCertificates" => Ok(json!([])),
1127
1128 "nativeHost:relaunch" => Ok(Value::Null),
1130 "nativeHost:reload" => Ok(Value::Null),
1131 "nativeHost:quit" => Ok(Value::Null),
1132 "nativeHost:exit" => Ok(Value::Null),
1133
1134 "nativeHost:openDevTools" => Ok(Value::Null),
1136 "nativeHost:toggleDevTools" => Ok(Value::Null),
1137
1138 "nativeHost:getSystemIdleState" => Ok(json!("active")),
1140 "nativeHost:getSystemIdleTime" => Ok(json!(0)),
1141 "nativeHost:getCurrentThermalState" => Ok(json!("nominal")),
1142 "nativeHost:isOnBatteryPower" => Ok(json!(false)),
1143 "nativeHost:startPowerSaveBlocker" => Ok(json!(0)),
1144 "nativeHost:stopPowerSaveBlocker" => Ok(json!(false)),
1145 "nativeHost:isPowerSaveBlockerStarted" => Ok(json!(false)),
1146
1147 "nativeHost:newWindowTab" => Ok(Value::Null),
1149 "nativeHost:showPreviousWindowTab" => Ok(Value::Null),
1150 "nativeHost:showNextWindowTab" => Ok(Value::Null),
1151 "nativeHost:moveWindowTabToNewWindow" => Ok(Value::Null),
1152 "nativeHost:mergeAllWindowTabs" => Ok(Value::Null),
1153 "nativeHost:toggleWindowTabsBar" => Ok(Value::Null),
1154 "nativeHost:installShellCommand" => Ok(Value::Null),
1155 "nativeHost:uninstallShellCommand" => Ok(Value::Null),
1156
1157 "localPty:getProfiles" => {
1161 dev_log!("terminal", "localPty:getProfiles");
1162 handle_local_pty_get_profiles().await
1163 },
1164 "localPty:getDefaultSystemShell" => {
1165 dev_log!("terminal", "localPty:getDefaultSystemShell");
1166 handle_local_pty_get_default_shell().await
1167 },
1168 "localPty:getTerminalLayoutInfo" => {
1169 dev_log!("terminal", "localPty:getTerminalLayoutInfo");
1170 Ok(Value::Null)
1171 },
1172 "localPty:setTerminalLayoutInfo" => {
1173 dev_log!("terminal", "localPty:setTerminalLayoutInfo");
1174 Ok(Value::Null)
1175 },
1176 "localPty:getPerformanceMarks" => {
1177 dev_log!("terminal", "localPty:getPerformanceMarks");
1178 Ok(json!([]))
1179 },
1180 "localPty:reduceConnectionGraceTime" => {
1181 dev_log!("terminal", "localPty:reduceConnectionGraceTime");
1182 Ok(Value::Null)
1183 },
1184 "localPty:listProcesses" => {
1185 dev_log!("terminal", "localPty:listProcesses");
1186 Ok(json!([]))
1187 },
1188 "localPty:getEnvironment" => {
1189 dev_log!("terminal", "localPty:getEnvironment");
1190 handle_local_pty_get_environment().await
1191 },
1192
1193 "update:_getInitialState" => {
1197 dev_log!("update", "update:_getInitialState");
1198 Ok(json!({ "type": "idle", "updateType": 0 }))
1199 },
1200 "update:isLatestVersion" => {
1201 dev_log!("update", "update:isLatestVersion");
1202 Ok(json!(true))
1203 },
1204 "update:checkForUpdates" => {
1205 dev_log!("update", "update:checkForUpdates");
1206 Ok(Value::Null)
1207 },
1208 "update:downloadUpdate" => {
1209 dev_log!("update", "update:downloadUpdate");
1210 Ok(Value::Null)
1211 },
1212 "update:applyUpdate" => {
1213 dev_log!("update", "update:applyUpdate");
1214 Ok(Value::Null)
1215 },
1216 "update:quitAndInstall" => {
1217 dev_log!("update", "update:quitAndInstall");
1218 Ok(Value::Null)
1219 },
1220
1221 "menubar:updateMenubar" => {
1225 dev_log!("menubar", "menubar:updateMenubar");
1226 Ok(Value::Null)
1227 },
1228
1229 "url:registerExternalUriOpener" => {
1233 dev_log!("url", "url:registerExternalUriOpener");
1234 Ok(Value::Null)
1235 },
1236
1237 "encryption:encrypt" => {
1241 dev_log!("encryption", "encryption:encrypt");
1242 Ok(json!(""))
1243 },
1244 "encryption:decrypt" => {
1245 dev_log!("encryption", "encryption:decrypt");
1246 Ok(json!(""))
1247 },
1248
1249 "extensionHostStarter:createExtensionHost" => {
1253 dev_log!("exthost", "extensionHostStarter:createExtensionHost");
1254 Ok(json!({ "id": "1" }))
1255 },
1256 "extensionHostStarter:start" => {
1257 dev_log!("exthost", "extensionHostStarter:start pid={}", std::process::id());
1258 Ok(json!({ "pid": std::process::id() }))
1259 },
1260 "extensionHostStarter:kill" => {
1261 dev_log!("exthost", "extensionHostStarter:kill");
1262 Ok(Value::Null)
1263 },
1264 "extensionHostStarter:getExitInfo" => {
1265 dev_log!("exthost", "extensionHostStarter:getExitInfo");
1266 Ok(json!({ "code": null, "signal": null }))
1267 },
1268
1269 "cocoon:extensionHostMessage" => {
1273 let ByteCount = args
1274 .first()
1275 .map(|P| P.get("data").and_then(|D| D.as_array()).map(|A| A.len()).unwrap_or(0))
1276 .unwrap_or(0);
1277 dev_log!("exthost", "cocoon:extensionHostMessage bytes={}", ByteCount);
1278
1279 let Payload = args.first().cloned().unwrap_or(Value::Null);
1282 tokio::spawn(async move {
1283 if let Err(Error) = crate::Vine::Client::SendNotification(
1284 "cocoon-main".to_string(),
1285 "extensionHostMessage".to_string(),
1286 Payload,
1287 )
1288 .await
1289 {
1290 dev_log!("exthost", "cocoon:extensionHostMessage forward failed: {}", Error);
1291 }
1292 });
1293 Ok(Value::Null)
1294 },
1295
1296 "extensionhostdebugservice:reload" => {
1300 dev_log!("exthost", "extensionhostdebugservice:reload");
1301 Ok(Value::Null)
1302 },
1303 "extensionhostdebugservice:close" => {
1304 dev_log!("exthost", "extensionhostdebugservice:close");
1305 Ok(Value::Null)
1306 },
1307
1308 "workspaces:getRecentlyOpened" => {
1312 dev_log!("workspaces", "workspaces:getRecentlyOpened");
1313 Ok(json!({
1314 "workspaces": [],
1315 "files": []
1316 }))
1317 },
1318 "workspaces:removeRecentlyOpened" => {
1319 dev_log!("workspaces", "workspaces:removeRecentlyOpened");
1320 Ok(Value::Null)
1321 },
1322 "workspaces:addRecentlyOpened" => {
1323 dev_log!("workspaces", "workspaces:addRecentlyOpened");
1324 Ok(Value::Null)
1325 },
1326 "workspaces:clearRecentlyOpened" => {
1327 dev_log!("workspaces", "workspaces:clearRecentlyOpened");
1328 Ok(Value::Null)
1329 },
1330 "workspaces:enterWorkspace" => {
1331 dev_log!("workspaces", "workspaces:enterWorkspace");
1332 Ok(Value::Null)
1333 },
1334 "workspaces:createUntitledWorkspace" => {
1335 dev_log!("workspaces", "workspaces:createUntitledWorkspace");
1336 Ok(Value::Null)
1337 },
1338 "workspaces:deleteUntitledWorkspace" => {
1339 dev_log!("workspaces", "workspaces:deleteUntitledWorkspace");
1340 Ok(Value::Null)
1341 },
1342 "workspaces:getWorkspaceIdentifier" => Ok(Value::Null),
1343 "workspaces:getDirtyWorkspaces" => Ok(json!([])),
1344
1345 _ => {
1347 dev_log!("ipc", "error: [WindServiceHandlers] Unknown IPC command: {}", command);
1348 Err(format!("Unknown IPC command: {}", command))
1349 },
1350 };
1351
1352 let IsErr = Result.is_err();
1354 let SpanName = if IsErr {
1355 format!("ipc:{}:error", command)
1356 } else {
1357 format!("ipc:{}", command)
1358 };
1359 crate::otel_span!(&SpanName, OTLPStart, &[("ipc.command", command.as_str())]);
1360
1361 Result
1362}
1363
1364async fn handle_configuration_get(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1366 let key = args
1367 .get(0)
1368 .ok_or("Missing configuration key".to_string())?
1369 .as_str()
1370 .ok_or("Configuration key must be a string".to_string())?;
1371
1372 let provider:Arc<dyn ConfigurationProvider> = runtime.Environment.Require();
1374
1375 let value = provider
1376 .GetConfigurationValue(Some(key.to_string()), ConfigurationOverridesDTO::default())
1377 .await
1378 .map_err(|e| format!("Failed to get configuration: {}", e))?;
1379
1380 dev_log!("config", "get: {} = {:?}", key, value);
1381 Ok(value)
1382}
1383
1384async fn handle_configuration_update(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1386 let key = args
1387 .get(0)
1388 .ok_or("Missing configuration key".to_string())?
1389 .as_str()
1390 .ok_or("Configuration key must be a string".to_string())?;
1391
1392 let value = args.get(1).ok_or("Missing configuration value".to_string())?.clone();
1393
1394 let provider:Arc<dyn ConfigurationProvider> = runtime.Environment.Require();
1396
1397 provider
1398 .UpdateConfigurationValue(
1399 key.to_string(),
1400 value,
1401 ConfigurationTarget::User,
1402 ConfigurationOverridesDTO::default(),
1403 None,
1404 )
1405 .await
1406 .map_err(|e| format!("Failed to update configuration: {}", e))?;
1407
1408 dev_log!("config", "updated: {}", key);
1409 Ok(Value::Null)
1410}
1411
1412async fn handle_file_read(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1414 let path = args
1415 .get(0)
1416 .ok_or("Missing file path".to_string())?
1417 .as_str()
1418 .ok_or("File path must be a string".to_string())?;
1419
1420 let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
1422
1423 let content = provider
1424 .ReadFile(&PathBuf::from(path))
1425 .await
1426 .map_err(|e| format!("Failed to read file: {}", e))?;
1427
1428 dev_log!("vfs", "read: {} ({} bytes)", path, content.len());
1429 Ok(json!(content))
1430}
1431
1432async fn handle_file_write(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1434 let path = args
1435 .get(0)
1436 .ok_or("Missing file path".to_string())?
1437 .as_str()
1438 .ok_or("File path must be a string".to_string())?;
1439
1440 let content = args
1441 .get(1)
1442 .ok_or("Missing file content".to_string())?
1443 .as_str()
1444 .ok_or("File content must be a string".to_string())?;
1445
1446 let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
1448
1449 provider
1450 .WriteFile(&PathBuf::from(path), content.as_bytes().to_vec(), true, true)
1451 .await
1452 .map_err(|e:CommonError| format!("Failed to write file: {}", e))?;
1453
1454 dev_log!("vfs", "written: {} ({} bytes)", path, content.len());
1455 Ok(Value::Null)
1456}
1457
1458async fn handle_file_stat(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1460 let path = args
1461 .get(0)
1462 .ok_or("Missing file path".to_string())?
1463 .as_str()
1464 .ok_or("File path must be a string".to_string())?;
1465
1466 let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
1468
1469 let stats = provider
1470 .StatFile(&PathBuf::from(path))
1471 .await
1472 .map_err(|e| format!("Failed to stat file: {}", e))?;
1473
1474 dev_log!("vfs", "legacy_stat: {}", path);
1475 Ok(json!(stats))
1476}
1477
1478async fn handle_file_exists(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1480 let path = args
1481 .get(0)
1482 .ok_or("Missing file path".to_string())?
1483 .as_str()
1484 .ok_or("File path must be a string".to_string())?;
1485
1486 let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
1488
1489 let exists = provider.StatFile(&PathBuf::from(path)).await.is_ok();
1490
1491 dev_log!("vfs", "exists: {} = {}", path, exists);
1492 Ok(json!(exists))
1493}
1494
1495async fn handle_file_delete(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1497 let path = args
1498 .get(0)
1499 .ok_or("Missing file path".to_string())?
1500 .as_str()
1501 .ok_or("File path must be a string".to_string())?;
1502
1503 let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
1505
1506 provider
1507 .Delete(&PathBuf::from(path), false, false)
1508 .await
1509 .map_err(|e:CommonError| format!("Failed to delete file: {}", e))?;
1510
1511 dev_log!("vfs", "deleted: {}", path);
1512 Ok(Value::Null)
1513}
1514
1515async fn handle_file_copy(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1517 let source = args
1518 .get(0)
1519 .ok_or("Missing source path".to_string())?
1520 .as_str()
1521 .ok_or("Source path must be a string".to_string())?;
1522
1523 let destination = args
1524 .get(1)
1525 .ok_or("Missing destination path".to_string())?
1526 .as_str()
1527 .ok_or("Destination path must be a string".to_string())?;
1528
1529 let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
1531
1532 provider
1533 .Copy(&PathBuf::from(source), &PathBuf::from(destination), false)
1534 .await
1535 .map_err(|e:CommonError| format!("Failed to copy file: {} -> {}", source, destination))?;
1536
1537 dev_log!("vfs", "copied: {} -> {}", source, destination);
1538 Ok(Value::Null)
1539}
1540
1541async fn handle_file_move(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1543 let source = args
1544 .get(0)
1545 .ok_or("Missing source path".to_string())?
1546 .as_str()
1547 .ok_or("Source path must be a string".to_string())?;
1548
1549 let destination = args
1550 .get(1)
1551 .ok_or("Missing destination path".to_string())?
1552 .as_str()
1553 .ok_or("Destination path must be a string".to_string())?;
1554
1555 let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
1557
1558 provider
1559 .Rename(&PathBuf::from(source), &PathBuf::from(destination), false)
1560 .await
1561 .map_err(|e:CommonError| format!("Failed to move file: {} -> {}", source, destination))?;
1562
1563 dev_log!("vfs", "moved: {} -> {}", source, destination);
1564 Ok(Value::Null)
1565}
1566
1567async fn handle_file_mkdir(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1569 let path = args
1570 .get(0)
1571 .ok_or("Missing directory path".to_string())?
1572 .as_str()
1573 .ok_or("Directory path must be a string".to_string())?;
1574
1575 let recursive = args.get(1).and_then(|v| v.as_bool()).unwrap_or(true);
1576
1577 let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
1579
1580 provider
1581 .CreateDirectory(&PathBuf::from(path), recursive)
1582 .await
1583 .map_err(|e:CommonError| format!("Failed to create directory: {}", e))?;
1584
1585 dev_log!("vfs", "mkdir: {} (recursive: {})", path, recursive);
1586 Ok(Value::Null)
1587}
1588
1589async fn handle_file_readdir(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1591 let path = args
1592 .get(0)
1593 .ok_or("Missing directory path".to_string())?
1594 .as_str()
1595 .ok_or("Directory path must be a string".to_string())?;
1596
1597 let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
1599
1600 let entries = provider
1601 .ReadDirectory(&PathBuf::from(path))
1602 .await
1603 .map_err(|e| format!("Failed to read directory: {}", e))?;
1604
1605 dev_log!("vfs", "readdir_legacy: {} ({} entries)", path, entries.len());
1606 Ok(json!(entries))
1607}
1608
1609async fn handle_file_read_binary(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1611 let path = args
1612 .get(0)
1613 .ok_or("Missing file path".to_string())?
1614 .as_str()
1615 .ok_or("File path must be a string".to_string())?;
1616
1617 let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
1619
1620 let content = provider
1621 .ReadFile(&PathBuf::from(path))
1622 .await
1623 .map_err(|e| format!("Failed to read binary file: {}", e))?;
1624
1625 dev_log!("vfs", "readBinary: {} ({} bytes)", path, content.len());
1626 Ok(json!(content))
1627}
1628
1629async fn handle_file_write_binary(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1631 let path = args
1632 .get(0)
1633 .ok_or("Missing file path".to_string())?
1634 .as_str()
1635 .ok_or("File path must be a string".to_string())?;
1636
1637 let content = args
1638 .get(1)
1639 .ok_or("Missing file content".to_string())?
1640 .as_str()
1641 .ok_or("File content must be a string".to_string())?;
1642
1643 let content_bytes = content.as_bytes().to_vec();
1645 let content_len = content_bytes.len();
1646
1647 let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
1649
1650 provider
1651 .WriteFile(&PathBuf::from(path), content_bytes.clone(), true, true)
1652 .await
1653 .map_err(|e:CommonError| format!("Failed to write binary file: {}", e))?;
1654
1655 dev_log!("vfs", "writeBinary: {} ({} bytes)", path, content_len);
1656 Ok(Value::Null)
1657}
1658
1659async fn handle_storage_get(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1661 let key = args
1662 .get(0)
1663 .ok_or("Missing storage key".to_string())?
1664 .as_str()
1665 .ok_or("Storage key must be a string".to_string())?;
1666
1667 let provider:Arc<dyn StorageProvider> = runtime.Environment.Require();
1669
1670 let value = provider
1671 .GetStorageValue(false, key)
1672 .await
1673 .map_err(|e| format!("Failed to get storage item: {}", e))?;
1674
1675 dev_log!("storage", "get: {}", key);
1676 Ok(value.unwrap_or(Value::Null))
1677}
1678
1679async fn handle_storage_set(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1681 let key = args
1682 .get(0)
1683 .ok_or("Missing storage key".to_string())?
1684 .as_str()
1685 .ok_or("Storage key must be a string".to_string())?;
1686
1687 let value = args.get(1).ok_or("Missing storage value".to_string())?.clone();
1688
1689 let provider:Arc<dyn StorageProvider> = runtime.Environment.Require();
1691
1692 provider
1693 .UpdateStorageValue(false, key.to_string(), Some(value))
1694 .await
1695 .map_err(|e| format!("Failed to set storage item: {}", e))?;
1696
1697 dev_log!("storage", "set: {}", key);
1698 Ok(Value::Null)
1699}
1700
1701async fn handle_environment_get(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1703 let key = args
1704 .get(0)
1705 .ok_or("Missing environment key".to_string())?
1706 .as_str()
1707 .ok_or("Environment key must be a string".to_string())?;
1708
1709 let value = std::env::var(key).map_err(|e| format!("Failed to get environment variable: {}", e))?;
1711
1712 dev_log!("config", "env_get: {}", key);
1713 Ok(json!(value))
1714}
1715
1716async fn handle_show_item_in_folder(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1718 let path_str = args
1719 .get(0)
1720 .ok_or("Missing file path".to_string())?
1721 .as_str()
1722 .ok_or("File path must be a string".to_string())?;
1723
1724 dev_log!("vfs", "showInFolder: {}", path_str);
1726
1727 let path = std::path::PathBuf::from(path_str);
1728
1729 if !path.exists() {
1731 return Err(format!("Path does not exist: {}", path_str));
1732 }
1733
1734 #[cfg(target_os = "macos")]
1735 {
1736 use std::process::Command;
1737
1738 let result = Command::new("open")
1740 .arg("-R")
1741 .arg(&path)
1742 .output()
1743 .map_err(|e| format!("Failed to execute open command: {}", e))?;
1744
1745 if !result.status.success() {
1746 return Err(format!(
1747 "Failed to show item in folder: {}",
1748 String::from_utf8_lossy(&result.stderr)
1749 ));
1750 }
1751 }
1752
1753 #[cfg(target_os = "windows")]
1754 {
1755 use std::process::Command;
1756
1757 let result = Command::new("explorer")
1759 .arg("/select,")
1760 .arg(&path)
1761 .output()
1762 .map_err(|e| format!("Failed to execute explorer command: {}", e))?;
1763
1764 if !result.status.success() {
1765 return Err(format!(
1766 "Failed to show item in folder: {}",
1767 String::from_utf8_lossy(&result.stderr)
1768 ));
1769 }
1770 }
1771
1772 #[cfg(target_os = "linux")]
1773 {
1774 use std::process::Command;
1775
1776 let file_managers = ["nautilus", "dolphin", "thunar", "pcmanfm", "nemo"];
1778 let mut last_error = String::new();
1779
1780 for manager in file_managers.iter() {
1781 let result = Command::new(manager).arg(&path).output();
1782
1783 match result {
1784 Ok(output) if output.status.success() => {
1785 dev_log!("lifecycle", "opened with {}", manager);
1786 break;
1787 },
1788 Err(e) => {
1789 last_error = e.to_string();
1790 continue;
1791 },
1792 _ => continue,
1793 }
1794 }
1795
1796 if !last_error.is_empty() {
1797 return Err(format!("Failed to show item in folder with any file manager: {}", last_error));
1798 }
1799 }
1800
1801 dev_log!("vfs", "showed in folder: {}", path_str);
1802 Ok(Value::Bool(true))
1803}
1804
1805async fn handle_open_external(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1807 let url_str = args
1808 .get(0)
1809 .ok_or("Missing URL".to_string())?
1810 .as_str()
1811 .ok_or("URL must be a string".to_string())?;
1812
1813 dev_log!("lifecycle", "openExternal: {}", url_str);
1815
1816 if !url_str.starts_with("http://") && !url_str.starts_with("https://") {
1818 return Err(format!("Invalid URL format. Must start with http:// or https://: {}", url_str));
1819 }
1820
1821 #[cfg(target_os = "macos")]
1822 {
1823 use std::process::Command;
1824
1825 let result = Command::new("open")
1827 .arg(url_str)
1828 .output()
1829 .map_err(|e| format!("Failed to execute open command: {}", e))?;
1830
1831 if !result.status.success() {
1832 return Err(format!("Failed to open URL: {}", String::from_utf8_lossy(&result.stderr)));
1833 }
1834 }
1835
1836 #[cfg(target_os = "windows")]
1837 {
1838 use std::process::Command;
1839
1840 let result = Command::new("cmd")
1842 .arg("/c")
1843 .arg("start")
1844 .arg(url_str)
1845 .output()
1846 .map_err(|e| format!("Failed to execute start command: {}", e))?;
1847
1848 if !result.status.success() {
1849 return Err(format!("Failed to open URL: {}", String::from_utf8_lossy(&result.stderr)));
1850 }
1851 }
1852
1853 #[cfg(target_os = "linux")]
1854 {
1855 use std::process::Command;
1856
1857 let handlers = ["xdg-open", "gnome-open", "kde-open", "x-www-browser"];
1859 let mut last_error = String::new();
1860
1861 for handler in handlers.iter() {
1862 let result = Command::new(handler).arg(url_str).output();
1863
1864 match result {
1865 Ok(output) if output.status.success() => {
1866 dev_log!("lifecycle", "opened with {}", handler);
1867 break;
1868 },
1869 Err(e) => {
1870 last_error = e.to_string();
1871 continue;
1872 },
1873 _ => continue,
1874 }
1875 }
1876
1877 if !last_error.is_empty() {
1878 return Err(format!("Failed to open URL with any handler: {}", last_error));
1879 }
1880 }
1881
1882 dev_log!("lifecycle", "opened URL: {}", url_str);
1883 Ok(Value::Bool(true))
1884}
1885
1886async fn handle_workbench_configuration(runtime:Arc<ApplicationRunTime>, _args:Vec<Value>) -> Result<Value, String> {
1888 let provider:Arc<dyn ConfigurationProvider> = runtime.Environment.Require();
1890
1891 let config = provider
1892 .GetConfigurationValue(None, ConfigurationOverridesDTO::default())
1893 .await
1894 .map_err(|e| format!("Failed to get workbench configuration: {}", e))?;
1895
1896 dev_log!("config", "workbench config retrieved");
1897 Ok(config)
1898}
1899
1900async fn handle_terminal_create(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1906 use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
1907
1908 let Options = args.first().cloned().unwrap_or(Value::Null);
1909 runtime
1910 .Environment
1911 .CreateTerminal(Options)
1912 .await
1913 .map_err(|Error| format!("terminal:create failed: {}", Error))
1914}
1915
1916async fn handle_terminal_send_text(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1918 use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
1919
1920 let TerminalId = args
1921 .first()
1922 .and_then(|V| V.as_u64())
1923 .ok_or_else(|| "terminal:sendText requires terminal_id as first argument".to_string())?;
1924 let Text = args.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
1925
1926 runtime
1927 .Environment
1928 .SendTextToTerminal(TerminalId, Text)
1929 .await
1930 .map(|()| Value::Null)
1931 .map_err(|Error| format!("terminal:sendText failed: {}", Error))
1932}
1933
1934async fn handle_terminal_dispose(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1936 use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
1937
1938 let TerminalId = args
1939 .first()
1940 .and_then(|V| V.as_u64())
1941 .ok_or_else(|| "terminal:dispose requires terminal_id as first argument".to_string())?;
1942
1943 runtime
1944 .Environment
1945 .DisposeTerminal(TerminalId)
1946 .await
1947 .map(|()| Value::Null)
1948 .map_err(|Error| format!("terminal:dispose failed: {}", Error))
1949}
1950
1951async fn handle_terminal_show(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1953 use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
1954
1955 let TerminalId = args.first().and_then(|V| V.as_u64()).unwrap_or(0);
1956 let PreserveFocus = args.get(1).and_then(|V| V.as_bool()).unwrap_or(false);
1957
1958 runtime
1959 .Environment
1960 .ShowTerminal(TerminalId, PreserveFocus)
1961 .await
1962 .map(|()| Value::Null)
1963 .map_err(|Error| format!("terminal:show failed: {}", Error))
1964}
1965
1966async fn handle_terminal_hide(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
1968 use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
1969
1970 let TerminalId = args.first().and_then(|V| V.as_u64()).unwrap_or(0);
1971
1972 runtime
1973 .Environment
1974 .HideTerminal(TerminalId)
1975 .await
1976 .map(|()| Value::Null)
1977 .map_err(|Error| format!("terminal:hide failed: {}", Error))
1978}
1979
1980async fn handle_output_create(_app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
1986 let ChannelName = args.first().and_then(|V| V.as_str()).unwrap_or("Output").to_string();
1987 dev_log!("ipc", "output:create channel='{}'", ChannelName);
1988 Ok(json!({ "channelName": ChannelName }))
1990}
1991
1992async fn handle_output_append(app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
1994 use tauri::Emitter;
1995
1996 let ChannelName = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1997 let Text = args.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
1998
1999 let _ = app_handle.emit("sky://output/append", json!({ "channel": ChannelName, "text": Text }));
2000 Ok(Value::Null)
2001}
2002
2003async fn handle_output_append_line(app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
2005 use tauri::Emitter;
2006
2007 let ChannelName = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2008 let Text = args.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
2009 let Line = format!("{}\n", Text);
2010
2011 let _ = app_handle.emit("sky://output/append", json!({ "channel": ChannelName, "text": Line }));
2012 Ok(Value::Null)
2013}
2014
2015async fn handle_output_clear(app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
2017 use tauri::Emitter;
2018
2019 let ChannelName = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2020 let _ = app_handle.emit("sky://output/clear", json!({ "channel": ChannelName }));
2021 Ok(Value::Null)
2022}
2023
2024async fn handle_output_show(app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
2026 use tauri::Emitter;
2027
2028 let ChannelName = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2029 let _ = app_handle.emit("sky://output/show", json!({ "channel": ChannelName }));
2030 Ok(Value::Null)
2031}
2032
2033async fn handle_textfile_read(_runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2039 let Path = args
2040 .first()
2041 .and_then(|V| V.as_str())
2042 .ok_or_else(|| "textFile:read requires path as first argument".to_string())?;
2043
2044 tokio::fs::read_to_string(Path)
2045 .await
2046 .map(Value::String)
2047 .map_err(|Error| format!("textFile:read failed: {}", Error))
2048}
2049
2050async fn handle_textfile_write(_runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2052 let Path = args
2053 .first()
2054 .and_then(|V| V.as_str())
2055 .ok_or_else(|| "textFile:write requires path as first argument".to_string())?;
2056 let Content = args.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
2057
2058 tokio::fs::write(Path, Content.as_bytes())
2059 .await
2060 .map(|()| Value::Null)
2061 .map_err(|Error| format!("textFile:write failed: {}", Error))
2062}
2063
2064async fn handle_textfile_save(_runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2066 let _Uri = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2068 dev_log!("vfs", "textFile:save uri={:?}", _Uri);
2069 Ok(Value::Null)
2070}
2071
2072pub fn register_wind_ipc_handlers(app_handle:&tauri::AppHandle) -> Result<(), String> {
2074 dev_log!("lifecycle", "registering IPC handlers");
2075
2076 Ok(())
2080}
2081
2082async fn handle_commands_execute(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2088 let CommandId = args
2089 .first()
2090 .and_then(|V| V.as_str())
2091 .ok_or_else(|| "commands:execute requires string command_id as first argument".to_string())?
2092 .to_string();
2093
2094 let Argument = args.get(1).cloned().unwrap_or(Value::Null);
2095
2096 dev_log!("ipc", "commands:execute id={}", CommandId);
2097
2098 runtime
2099 .Environment
2100 .ExecuteCommand(CommandId, Argument)
2101 .await
2102 .map_err(|Error| format!("commands:execute failed: {}", Error))
2103}
2104
2105async fn handle_commands_get_all(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2107 let Commands = runtime
2108 .Environment
2109 .GetAllCommands()
2110 .await
2111 .map_err(|Error| format!("commands:getAll failed: {}", Error))?;
2112
2113 Ok(json!(Commands))
2114}
2115
2116async fn handle_extensions_get_all(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2122 let Extensions = runtime
2123 .Environment
2124 .GetExtensions()
2125 .await
2126 .map_err(|Error| format!("extensions:getAll failed: {}", Error))?;
2127
2128 dev_log!("extensions", "extensions:getAll returning {} extensions", Extensions.len());
2129 if let Some(First) = Extensions.first() {
2130 dev_log!(
2131 "extensions",
2132 "extensions:getAll sample: {}",
2133 serde_json::to_string(First)
2134 .unwrap_or_default()
2135 .chars()
2136 .take(300)
2137 .collect::<String>()
2138 );
2139 }
2140 Ok(json!(Extensions))
2141}
2142
2143async fn handle_extensions_get(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2145 let Id = args
2146 .first()
2147 .and_then(|V| V.as_str())
2148 .ok_or_else(|| "extensions:get requires string id as first argument".to_string())?
2149 .to_string();
2150
2151 let Extension = runtime
2152 .Environment
2153 .GetExtension(Id)
2154 .await
2155 .map_err(|Error| format!("extensions:get failed: {}", Error))?;
2156
2157 Ok(Extension.unwrap_or(Value::Null))
2158}
2159
2160async fn handle_extensions_is_active(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2162 let Id = args
2163 .first()
2164 .and_then(|V| V.as_str())
2165 .ok_or_else(|| "extensions:isActive requires string id as first argument".to_string())?
2166 .to_string();
2167
2168 let Extension = runtime
2169 .Environment
2170 .GetExtension(Id)
2171 .await
2172 .map_err(|Error| format!("extensions:isActive failed: {}", Error))?;
2173
2174 Ok(json!(Extension.is_some()))
2175}
2176
2177async fn handle_storage_delete(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2183 use CommonLibrary::Storage::StorageProvider::StorageProvider;
2184
2185 let Key = args
2186 .first()
2187 .and_then(|V| V.as_str())
2188 .ok_or("storage:delete requires key as first argument".to_string())?
2189 .to_string();
2190
2191 runtime
2192 .Environment
2193 .UpdateStorageValue(true, Key, None)
2194 .await
2195 .map_err(|Error| format!("storage:delete failed: {}", Error))?;
2196
2197 Ok(Value::Null)
2198}
2199
2200async fn handle_storage_keys(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2202 use CommonLibrary::Storage::StorageProvider::StorageProvider;
2203
2204 let Storage = runtime
2205 .Environment
2206 .GetAllStorage(true)
2207 .await
2208 .map_err(|Error| format!("storage:keys failed: {}", Error))?;
2209
2210 let Keys:Vec<String> = Storage.as_object().map(|O| O.keys().cloned().collect()).unwrap_or_default();
2211 Ok(json!(Keys))
2212}
2213
2214async fn handle_notification_show(app_handle:tauri::AppHandle, args:Vec<Value>) -> Result<Value, String> {
2221 use tauri::Emitter;
2222
2223 let Message = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2224 let Severity = args.get(1).and_then(|V| V.as_str()).unwrap_or("info").to_string();
2225 let Actions = args.get(2).cloned().unwrap_or(json!([]));
2226
2227 let Id = format!(
2228 "notification-{}",
2229 std::time::SystemTime::now()
2230 .duration_since(std::time::UNIX_EPOCH)
2231 .map(|D| D.as_millis())
2232 .unwrap_or(0)
2233 );
2234
2235 let _ = app_handle.emit(
2236 "sky://notification/show",
2237 json!({
2238 "id": Id,
2239 "message": Message,
2240 "severity": Severity,
2241 "actions": Actions,
2242 }),
2243 );
2244
2245 Ok(json!(Id))
2246}
2247
2248async fn handle_notification_show_progress(app_handle:tauri::AppHandle, args:Vec<Value>) -> Result<Value, String> {
2250 use tauri::Emitter;
2251
2252 let Title = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2253 let Cancellable = args.get(1).and_then(|V| V.as_bool()).unwrap_or(false);
2254
2255 let Id = format!(
2256 "progress-{}",
2257 std::time::SystemTime::now()
2258 .duration_since(std::time::UNIX_EPOCH)
2259 .map(|D| D.as_millis())
2260 .unwrap_or(0)
2261 );
2262
2263 let _ = app_handle.emit(
2264 "sky://notification/progress-begin",
2265 json!({
2266 "id": Id,
2267 "title": Title,
2268 "cancellable": Cancellable,
2269 }),
2270 );
2271
2272 Ok(json!(Id))
2273}
2274
2275async fn handle_notification_update_progress(app_handle:tauri::AppHandle, args:Vec<Value>) -> Result<Value, String> {
2277 use tauri::Emitter;
2278
2279 let Id = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2280 let Increment = args.get(1).and_then(|V| V.as_f64()).unwrap_or(0.0);
2281 let Message = args.get(2).and_then(|V| V.as_str()).unwrap_or("").to_string();
2282
2283 let _ = app_handle.emit(
2284 "sky://notification/progress-update",
2285 json!({
2286 "id": Id,
2287 "increment": Increment,
2288 "message": Message,
2289 }),
2290 );
2291
2292 Ok(Value::Null)
2293}
2294
2295async fn handle_notification_end_progress(app_handle:tauri::AppHandle, args:Vec<Value>) -> Result<Value, String> {
2297 use tauri::Emitter;
2298
2299 let Id = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2300
2301 let _ = app_handle.emit("sky://notification/progress-end", json!({ "id": Id }));
2302
2303 Ok(Value::Null)
2304}
2305
2306async fn handle_progress_begin(app_handle:tauri::AppHandle, args:Vec<Value>) -> Result<Value, String> {
2312 use tauri::Emitter;
2313
2314 let Location = args.first().and_then(|V| V.as_str()).unwrap_or("notification").to_string();
2315 let Title = args.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
2316 let Cancellable = args.get(2).and_then(|V| V.as_bool()).unwrap_or(false);
2317
2318 let Id = format!(
2319 "progress-{}",
2320 std::time::SystemTime::now()
2321 .duration_since(std::time::UNIX_EPOCH)
2322 .map(|D| D.as_millis())
2323 .unwrap_or(0)
2324 );
2325
2326 let _ = app_handle.emit(
2327 "sky://progress/begin",
2328 json!({
2329 "id": Id,
2330 "location": Location,
2331 "title": Title,
2332 "cancellable": Cancellable,
2333 }),
2334 );
2335
2336 Ok(json!(Id))
2337}
2338
2339async fn handle_progress_report(app_handle:tauri::AppHandle, args:Vec<Value>) -> Result<Value, String> {
2341 use tauri::Emitter;
2342
2343 let Id = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2344 let Increment = args.get(1).and_then(|V| V.as_f64()).unwrap_or(0.0);
2345 let Message = args.get(2).and_then(|V| V.as_str()).unwrap_or("").to_string();
2346
2347 let _ = app_handle.emit(
2348 "sky://progress/report",
2349 json!({
2350 "id": Id,
2351 "increment": Increment,
2352 "message": Message,
2353 }),
2354 );
2355
2356 Ok(Value::Null)
2357}
2358
2359async fn handle_progress_end(app_handle:tauri::AppHandle, args:Vec<Value>) -> Result<Value, String> {
2361 use tauri::Emitter;
2362
2363 let Id = args.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2364
2365 let _ = app_handle.emit("sky://progress/end", json!({ "id": Id }));
2366
2367 Ok(Value::Null)
2368}
2369
2370async fn handle_quick_input_show_quick_pick(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2377 use CommonLibrary::UserInterface::{
2378 DTO::{QuickPickItemDTO::QuickPickItemDTO, QuickPickOptionsDTO::QuickPickOptionsDTO},
2379 UserInterfaceProvider::UserInterfaceProvider,
2380 };
2381
2382 let Items:Vec<QuickPickItemDTO> = args
2383 .first()
2384 .and_then(|V| V.as_array())
2385 .map(|Arr| {
2386 Arr.iter()
2387 .filter_map(|Item| {
2388 let Label = Item.get("label").and_then(|L| L.as_str()).unwrap_or("").to_string();
2389 let Description = Item.get("description").and_then(|D| D.as_str()).map(|S| S.to_string());
2390 let Detail = Item.get("detail").and_then(|D| D.as_str()).map(|S| S.to_string());
2391 let Picked = Item.get("picked").and_then(|P| P.as_bool()).unwrap_or(false);
2392 Some(QuickPickItemDTO { Label, Description, Detail, Picked:Some(Picked), AlwaysShow:Some(false) })
2393 })
2394 .collect()
2395 })
2396 .unwrap_or_default();
2397
2398 let Options = QuickPickOptionsDTO {
2399 PlaceHolder:args
2400 .get(1)
2401 .and_then(|V| V.get("placeholder"))
2402 .and_then(|P| P.as_str())
2403 .map(|S| S.to_string()),
2404 CanPickMany:Some(
2405 args.get(1)
2406 .and_then(|V| V.get("canPickMany"))
2407 .and_then(|B| B.as_bool())
2408 .unwrap_or(false),
2409 ),
2410 Title:args
2411 .get(1)
2412 .and_then(|V| V.get("title"))
2413 .and_then(|T| T.as_str())
2414 .map(|S| S.to_string()),
2415 ..Default::default()
2416 };
2417
2418 let Result = runtime
2419 .Environment
2420 .ShowQuickPick(Items, Some(Options))
2421 .await
2422 .map_err(|Error| format!("quickInput:showQuickPick failed: {}", Error))?;
2423
2424 match Result {
2425 Some(Labels) => Ok(Labels.into_iter().next().map(|S| json!(S)).unwrap_or(Value::Null)),
2426 None => Ok(Value::Null),
2427 }
2428}
2429
2430async fn handle_quick_input_show_input_box(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2433 use CommonLibrary::UserInterface::{
2434 DTO::InputBoxOptionsDTO::InputBoxOptionsDTO,
2435 UserInterfaceProvider::UserInterfaceProvider,
2436 };
2437
2438 let Opts = args.first();
2439 let Options = InputBoxOptionsDTO {
2440 Prompt:Opts
2441 .and_then(|V| V.get("prompt"))
2442 .and_then(|P| P.as_str())
2443 .map(|S| S.to_string()),
2444 PlaceHolder:Opts
2445 .and_then(|V| V.get("placeholder"))
2446 .and_then(|P| P.as_str())
2447 .map(|S| S.to_string()),
2448 IsPassword:Some(Opts.and_then(|V| V.get("password")).and_then(|B| B.as_bool()).unwrap_or(false)),
2449 Value:Opts
2450 .and_then(|V| V.get("value"))
2451 .and_then(|V| V.as_str())
2452 .map(|S| S.to_string()),
2453 Title:Opts
2454 .and_then(|V| V.get("title"))
2455 .and_then(|T| T.as_str())
2456 .map(|S| S.to_string()),
2457 IgnoreFocusOut:None,
2458 };
2459
2460 let Result = runtime
2461 .Environment
2462 .ShowInputBox(Some(Options))
2463 .await
2464 .map_err(|Error| format!("quickInput:showInputBox failed: {}", Error))?;
2465
2466 Ok(Result.map(|S| json!(S)).unwrap_or(Value::Null))
2467}
2468
2469async fn handle_workspaces_get_folders(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2475 let Workspace = &runtime.Environment.ApplicationState.Workspace;
2476 let Folders = Workspace.GetWorkspaceFolders();
2477
2478 let FolderList:Vec<Value> = Folders
2479 .iter()
2480 .enumerate()
2481 .map(|(Index, Folder)| {
2482 json!({
2483 "uri": Folder.URI.to_string(),
2484 "name": Folder.Name,
2485 "index": Index,
2486 })
2487 })
2488 .collect();
2489
2490 Ok(json!(FolderList))
2491}
2492
2493async fn handle_workspaces_add_folder(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2495 use url::Url;
2496
2497 let UriStr = args
2498 .first()
2499 .and_then(|V| V.as_str())
2500 .ok_or("workspaces:addFolder requires uri as first argument".to_string())?
2501 .to_string();
2502
2503 let Name = args.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
2504
2505 let Workspace = &runtime.Environment.ApplicationState.Workspace;
2506 let mut Folders = Workspace.GetWorkspaceFolders();
2507 let Index = Folders.len();
2508 let URI = Url::parse(&UriStr).map_err(|E| format!("workspaces:addFolder invalid URI: {}", E))?;
2509 if let Ok(Folder) = WorkspaceFolderStateDTO::New(URI, Name, Index) {
2510 Folders.push(Folder);
2511 Workspace.SetWorkspaceFolders(Folders);
2512 }
2513
2514 Ok(Value::Null)
2515}
2516
2517async fn handle_workspaces_remove_folder(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2519 let UriStr = args
2520 .first()
2521 .and_then(|V| V.as_str())
2522 .ok_or("workspaces:removeFolder requires uri as first argument".to_string())?
2523 .to_string();
2524
2525 let Workspace = &runtime.Environment.ApplicationState.Workspace;
2526 let mut Folders = Workspace.GetWorkspaceFolders();
2527 Folders.retain(|F| F.URI.to_string() != UriStr);
2528 for (I, F) in Folders.iter_mut().enumerate() {
2529 F.Index = I;
2530 }
2531 Workspace.SetWorkspaceFolders(Folders);
2532
2533 Ok(Value::Null)
2534}
2535
2536async fn handle_workspaces_get_name(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2538 let Name = runtime
2539 .Environment
2540 .ApplicationState
2541 .Workspace
2542 .GetWorkspaceFolders()
2543 .into_iter()
2544 .next()
2545 .map(|F| F.GetDisplayName());
2546
2547 Ok(Name.map(|N| json!(N)).unwrap_or(Value::Null))
2548}
2549
2550async fn handle_themes_get_active(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2556 use CommonLibrary::Configuration::{
2557 ConfigurationProvider::ConfigurationProvider,
2558 DTO::ConfigurationOverridesDTO::ConfigurationOverridesDTO,
2559 };
2560
2561 let ThemeId = runtime
2562 .Environment
2563 .GetConfigurationValue(Some("workbench.colorTheme".to_string()), ConfigurationOverridesDTO::default())
2564 .await
2565 .map_err(|Error| format!("themes:getActive failed: {}", Error))?;
2566
2567 let Id = ThemeId.as_str().unwrap_or("Default Dark Modern").to_string();
2568
2569 let Kind = if Id.to_lowercase().contains("light") {
2571 "light"
2572 } else if Id.to_lowercase().contains("high contrast light") {
2573 "highContrastLight"
2574 } else if Id.to_lowercase().contains("high contrast") {
2575 "highContrast"
2576 } else {
2577 "dark"
2578 };
2579
2580 Ok(json!({ "id": Id, "label": Id, "kind": Kind }))
2581}
2582
2583async fn handle_themes_list(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2585 let Themes = vec![
2588 json!({ "id": "Default Dark Modern", "label": "Default Dark Modern", "kind": "dark" }),
2589 json!({ "id": "Default Light Modern", "label": "Default Light Modern", "kind": "light" }),
2590 json!({ "id": "Default Dark+", "label": "Default Dark+", "kind": "dark" }),
2591 json!({ "id": "Default Light+", "label": "Default Light+", "kind": "light" }),
2592 json!({ "id": "High Contrast", "label": "High Contrast", "kind": "highContrast" }),
2593 json!({ "id": "High Contrast Light", "label": "High Contrast Light", "kind": "highContrastLight" }),
2594 ];
2595
2596 Ok(json!(Themes))
2597}
2598
2599async fn handle_themes_set(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2601 use CommonLibrary::Configuration::{
2602 ConfigurationProvider::ConfigurationProvider,
2603 DTO::{ConfigurationOverridesDTO::ConfigurationOverridesDTO, ConfigurationTarget::ConfigurationTarget},
2604 };
2605 use tauri::Emitter;
2606
2607 let ThemeId = args
2608 .first()
2609 .and_then(|V| V.as_str())
2610 .ok_or("themes:set requires themeId as first argument".to_string())?
2611 .to_string();
2612
2613 runtime
2614 .Environment
2615 .UpdateConfigurationValue(
2616 "workbench.colorTheme".to_string(),
2617 json!(ThemeId),
2618 ConfigurationTarget::User,
2619 ConfigurationOverridesDTO::default(),
2620 None,
2621 )
2622 .await
2623 .map_err(|Error| format!("themes:set failed: {}", Error))?;
2624
2625 let _ = runtime
2626 .Environment
2627 .ApplicationHandle
2628 .emit("sky://theme/change", json!({ "themeId": ThemeId }));
2629
2630 Ok(Value::Null)
2631}
2632
2633async fn handle_search_find_in_files(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2640 use std::path::PathBuf;
2641
2642 use globset::GlobBuilder;
2643 use tokio::fs;
2644
2645 let Pattern = args
2646 .first()
2647 .and_then(|V| V.as_str())
2648 .ok_or("search:findInFiles requires pattern".to_string())?
2649 .to_string();
2650 let IsRegex = args.get(1).and_then(|V| V.as_bool()).unwrap_or(false);
2651 let IsCaseSensitive = args.get(2).and_then(|V| V.as_bool()).unwrap_or(false);
2652 let _IsWordMatch = args.get(3).and_then(|V| V.as_bool()).unwrap_or(false);
2653 let IncludeGlob = args.get(4).and_then(|V| V.as_str()).unwrap_or("**").to_string();
2654 let ExcludeGlob = args.get(5).and_then(|V| V.as_str()).unwrap_or("").to_string();
2655 let MaxResults = args.get(6).and_then(|V| V.as_u64()).unwrap_or(1000) as usize;
2656
2657 let WorkspaceFolders = runtime.Environment.ApplicationState.Workspace.GetWorkspaceFolders();
2658
2659 if WorkspaceFolders.is_empty() {
2660 return Ok(json!([]));
2661 }
2662
2663 let RootPath = PathBuf::from(&WorkspaceFolders[0].URI.to_string().replace("file://", ""));
2664
2665 let IncludeMatcher = GlobBuilder::new(&IncludeGlob)
2667 .literal_separator(false)
2668 .build()
2669 .map(|G| G.compile_matcher())
2670 .ok();
2671
2672 let ExcludeMatcher = if !ExcludeGlob.is_empty() {
2674 GlobBuilder::new(&ExcludeGlob)
2675 .literal_separator(false)
2676 .build()
2677 .map(|G| G.compile_matcher())
2678 .ok()
2679 } else {
2680 None
2681 };
2682
2683 let SearchText = Pattern.clone();
2684 let mut Matches = Vec::new();
2685
2686 let mut Stack = vec![RootPath.clone()];
2688 while let Some(Dir) = Stack.pop() {
2689 let mut Entries = match fs::read_dir(&Dir).await {
2690 Ok(E) => E,
2691 Err(_) => continue,
2692 };
2693
2694 while let Ok(Some(Entry)) = Entries.next_entry().await {
2695 let Path = Entry.path();
2696 let RelPath = Path.strip_prefix(&RootPath).unwrap_or(&Path).to_string_lossy().to_string();
2697
2698 if Path.file_name().map(|N| N.to_string_lossy().starts_with('.')).unwrap_or(false) {
2700 continue;
2701 }
2702
2703 if Path.is_dir() {
2704 Stack.push(Path);
2705 continue;
2706 }
2707
2708 if let Some(Ref) = &IncludeMatcher {
2710 if !Ref.is_match(&RelPath) {
2711 continue;
2712 }
2713 }
2714 if let Some(Ref) = &ExcludeMatcher {
2715 if Ref.is_match(&RelPath) {
2716 continue;
2717 }
2718 }
2719
2720 let Content = match fs::read_to_string(&Path).await {
2722 Ok(C) => C,
2723 Err(_) => continue,
2724 };
2725
2726 for (LineIndex, Line) in Content.lines().enumerate() {
2727 let Hit = if IsRegex {
2728 Line.contains(&SearchText)
2730 } else if IsCaseSensitive {
2731 Line.contains(&SearchText)
2732 } else {
2733 Line.to_lowercase().contains(&SearchText.to_lowercase())
2734 };
2735
2736 if Hit {
2737 let Uri = format!("file://{}", Path.to_string_lossy());
2738 Matches.push(json!({
2739 "uri": Uri,
2740 "lineNumber": LineIndex + 1,
2741 "preview": Line.trim(),
2742 }));
2743
2744 if Matches.len() >= MaxResults {
2745 return Ok(json!(Matches));
2746 }
2747 }
2748 }
2749 }
2750 }
2751
2752 Ok(json!(Matches))
2753}
2754
2755async fn handle_search_find_files(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2757 use std::path::PathBuf;
2758
2759 use globset::GlobBuilder;
2760 use tokio::fs;
2761
2762 let Pattern = args
2763 .first()
2764 .and_then(|V| V.as_str())
2765 .ok_or("search:findFiles requires pattern".to_string())?
2766 .to_string();
2767 let MaxResults = args.get(1).and_then(|V| V.as_u64()).unwrap_or(500) as usize;
2768
2769 let WorkspaceFolders = runtime.Environment.ApplicationState.Workspace.GetWorkspaceFolders();
2770
2771 if WorkspaceFolders.is_empty() {
2772 return Ok(json!([]));
2773 }
2774
2775 let RootPath = PathBuf::from(&WorkspaceFolders[0].URI.to_string().replace("file://", ""));
2776
2777 let Matcher = GlobBuilder::new(&Pattern)
2778 .literal_separator(false)
2779 .build()
2780 .map(|G| G.compile_matcher())
2781 .map_err(|Error| format!("Invalid glob pattern: {}", Error))?;
2782
2783 let mut Files = Vec::new();
2784 let mut Stack = vec![RootPath.clone()];
2785
2786 while let Some(Dir) = Stack.pop() {
2787 let mut Entries = match fs::read_dir(&Dir).await {
2788 Ok(E) => E,
2789 Err(_) => continue,
2790 };
2791
2792 while let Ok(Some(Entry)) = Entries.next_entry().await {
2793 let Path = Entry.path();
2794
2795 if Path.file_name().map(|N| N.to_string_lossy().starts_with('.')).unwrap_or(false) {
2796 continue;
2797 }
2798
2799 if Path.is_dir() {
2800 Stack.push(Path);
2801 continue;
2802 }
2803
2804 let RelPath = Path.strip_prefix(&RootPath).unwrap_or(&Path).to_string_lossy().to_string();
2805
2806 if Matcher.is_match(&RelPath) {
2807 Files.push(format!("file://{}", Path.to_string_lossy()));
2808
2809 if Files.len() >= MaxResults {
2810 return Ok(json!(Files));
2811 }
2812 }
2813 }
2814 }
2815
2816 Ok(json!(Files))
2817}
2818
2819async fn handle_decorations_get(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2827 let Uri = args
2828 .first()
2829 .and_then(|V| V.as_str())
2830 .ok_or("decorations:get requires uri".to_string())?;
2831 let Decoration = runtime.Environment.ApplicationState.Feature.Decorations.GetDecoration(Uri);
2832 Ok(Decoration.unwrap_or(Value::Null))
2833}
2834
2835async fn handle_decorations_get_many(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2837 let Uris:Vec<String> = args
2838 .first()
2839 .and_then(|V| V.as_array())
2840 .map(|Arr| Arr.iter().filter_map(|U| U.as_str().map(str::to_owned)).collect())
2841 .unwrap_or_default();
2842
2843 let mut Result = serde_json::Map::new();
2844 for Uri in &Uris {
2845 if let Some(Decoration) = runtime.Environment.ApplicationState.Feature.Decorations.GetDecoration(Uri) {
2846 Result.insert(Uri.clone(), Decoration);
2847 }
2848 }
2849 Ok(Value::Object(Result))
2850}
2851
2852async fn handle_decorations_set(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2854 let Uri = args
2855 .first()
2856 .and_then(|V| V.as_str())
2857 .ok_or("decorations:set requires uri".to_string())?;
2858 let Decoration = args.get(1).cloned().unwrap_or(Value::Null);
2859 runtime
2860 .Environment
2861 .ApplicationState
2862 .Feature
2863 .Decorations
2864 .SetDecoration(Uri, Decoration);
2865 Ok(Value::Null)
2866}
2867
2868async fn handle_decorations_clear(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2870 let Uri = args
2871 .first()
2872 .and_then(|V| V.as_str())
2873 .ok_or("decorations:clear requires uri".to_string())?;
2874 runtime.Environment.ApplicationState.Feature.Decorations.ClearDecoration(Uri);
2875 Ok(Value::Null)
2876}
2877
2878async fn handle_working_copy_is_dirty(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2884 let Uri = args
2885 .first()
2886 .and_then(|V| V.as_str())
2887 .ok_or("workingCopy:isDirty requires uri".to_string())?;
2888 let IsDirty = runtime.Environment.ApplicationState.Feature.WorkingCopy.IsDirty(Uri);
2889 Ok(json!(IsDirty))
2890}
2891
2892async fn handle_working_copy_set_dirty(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2894 let Uri = args
2895 .first()
2896 .and_then(|V| V.as_str())
2897 .ok_or("workingCopy:setDirty requires uri".to_string())?;
2898 let Dirty = args.get(1).and_then(|V| V.as_bool()).unwrap_or(true);
2899 runtime.Environment.ApplicationState.Feature.WorkingCopy.SetDirty(Uri, Dirty);
2900 Ok(Value::Null)
2901}
2902
2903async fn handle_working_copy_get_all_dirty(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2905 let Dirty = runtime.Environment.ApplicationState.Feature.WorkingCopy.GetAllDirty();
2906 Ok(json!(Dirty))
2907}
2908
2909async fn handle_working_copy_get_dirty_count(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2911 let Count = runtime.Environment.ApplicationState.Feature.WorkingCopy.GetDirtyCount();
2912 Ok(json!(Count))
2913}
2914
2915async fn handle_keybinding_add(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2921 let CommandId = args
2922 .first()
2923 .and_then(|V| V.as_str())
2924 .ok_or("keybinding:add requires commandId".to_string())?
2925 .to_owned();
2926 let KeyExpression = args
2927 .get(1)
2928 .and_then(|V| V.as_str())
2929 .ok_or("keybinding:add requires keybinding".to_string())?
2930 .to_owned();
2931 let When = args.get(2).and_then(|V| V.as_str()).map(str::to_owned);
2932 runtime
2933 .Environment
2934 .ApplicationState
2935 .Feature
2936 .Keybindings
2937 .AddKeybinding(CommandId, KeyExpression, When);
2938 Ok(Value::Null)
2939}
2940
2941async fn handle_keybinding_remove(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2943 let CommandId = args
2944 .first()
2945 .and_then(|V| V.as_str())
2946 .ok_or("keybinding:remove requires commandId".to_string())?;
2947 runtime
2948 .Environment
2949 .ApplicationState
2950 .Feature
2951 .Keybindings
2952 .RemoveKeybinding(CommandId);
2953 Ok(Value::Null)
2954}
2955
2956async fn handle_keybinding_lookup(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2958 let CommandId = args
2959 .first()
2960 .and_then(|V| V.as_str())
2961 .ok_or("keybinding:lookup requires commandId".to_string())?;
2962 let Binding = runtime
2963 .Environment
2964 .ApplicationState
2965 .Feature
2966 .Keybindings
2967 .LookupKeybinding(CommandId);
2968 Ok(Binding.map(|B| json!(B)).unwrap_or(Value::Null))
2969}
2970
2971async fn handle_keybinding_get_all(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2973 let All = runtime.Environment.ApplicationState.Feature.Keybindings.GetAllKeybindings();
2974 Ok(json!(All))
2975}
2976
2977async fn handle_lifecycle_get_phase(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
2983 let Phase = runtime.Environment.ApplicationState.Feature.Lifecycle.GetPhase();
2984 Ok(json!(Phase))
2985}
2986
2987async fn handle_lifecycle_when_phase(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
2990 let RequestedPhase = args.first().and_then(|V| V.as_u64()).unwrap_or(1) as u8;
2991 let CurrentPhase = runtime.Environment.ApplicationState.Feature.Lifecycle.GetPhase();
2992 if CurrentPhase >= RequestedPhase {
2993 return Ok(Value::Null);
2994 }
2995 let mut Retries = 0u8;
2997 loop {
2998 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2999 let Phase = runtime.Environment.ApplicationState.Feature.Lifecycle.GetPhase();
3000 if Phase >= RequestedPhase || Retries >= 50 {
3001 break;
3002 }
3003 Retries += 1;
3004 }
3005 Ok(Value::Null)
3006}
3007
3008async fn handle_lifecycle_request_shutdown(app_handle:AppHandle) -> Result<Value, String> {
3010 app_handle.exit(0);
3011 Ok(Value::Null)
3012}
3013
3014async fn handle_history_go_back(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
3020 let Uri = runtime.Environment.ApplicationState.Feature.NavigationHistory.GoBack();
3021 Ok(Uri.map(Value::String).unwrap_or(Value::Null))
3022}
3023
3024async fn handle_history_go_forward(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
3026 let Uri = runtime.Environment.ApplicationState.Feature.NavigationHistory.GoForward();
3027 Ok(Uri.map(Value::String).unwrap_or(Value::Null))
3028}
3029
3030async fn handle_history_can_go_back(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
3032 Ok(Value::Bool(
3033 runtime.Environment.ApplicationState.Feature.NavigationHistory.CanGoBack(),
3034 ))
3035}
3036
3037async fn handle_history_can_go_forward(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
3039 Ok(Value::Bool(
3040 runtime.Environment.ApplicationState.Feature.NavigationHistory.CanGoForward(),
3041 ))
3042}
3043
3044async fn handle_history_push(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
3046 let Uri = args
3047 .first()
3048 .and_then(|V| V.as_str())
3049 .ok_or("history:push requires uri".to_string())?
3050 .to_owned();
3051
3052 runtime.Environment.ApplicationState.Feature.NavigationHistory.Push(Uri);
3053 Ok(Value::Null)
3054}
3055
3056async fn handle_history_clear(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
3058 runtime.Environment.ApplicationState.Feature.NavigationHistory.Clear();
3059 Ok(Value::Null)
3060}
3061
3062async fn handle_history_get_stack(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
3064 let Stack = runtime.Environment.ApplicationState.Feature.NavigationHistory.GetStack();
3065 Ok(Value::Array(Stack.into_iter().map(Value::String).collect()))
3066}
3067
3068async fn handle_label_get_uri(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
3077 let Uri = args
3078 .first()
3079 .and_then(|V| V.as_str())
3080 .ok_or("label:getUri requires uri".to_string())?
3081 .to_owned();
3082
3083 let Relative = args.get(1).and_then(|V| V.as_bool()).unwrap_or(false);
3084
3085 if !Relative {
3086 let Label = if Uri.starts_with("file://") {
3088 Uri.trim_start_matches("file://").to_owned()
3089 } else {
3090 Uri.clone()
3091 };
3092 return Ok(Value::String(Label));
3093 }
3094
3095 let WorkspaceRoot = runtime
3097 .Environment
3098 .ApplicationState
3099 .Workspace
3100 .GetWorkspaceFolders()
3101 .into_iter()
3102 .next()
3103 .map(|F| F.URI.to_string())
3104 .unwrap_or_default();
3105
3106 let RawPath = if Uri.starts_with("file://") {
3107 Uri.trim_start_matches("file://").to_owned()
3108 } else {
3109 Uri.clone()
3110 };
3111
3112 let RootPath = if WorkspaceRoot.starts_with("file://") {
3113 WorkspaceRoot.trim_start_matches("file://").to_owned()
3114 } else {
3115 WorkspaceRoot
3116 };
3117
3118 let Label = if !RootPath.is_empty() && RawPath.starts_with(&RootPath) {
3119 RawPath[RootPath.len()..].trim_start_matches('/').to_owned()
3120 } else {
3121 RawPath
3122 };
3123
3124 Ok(Value::String(Label))
3125}
3126
3127async fn handle_label_get_workspace(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
3129 let Label = runtime
3130 .Environment
3131 .ApplicationState
3132 .Workspace
3133 .GetWorkspaceFolders()
3134 .into_iter()
3135 .next()
3136 .map(|F| {
3137 if !F.Name.is_empty() {
3138 F.Name
3139 } else {
3140 F.URI
3141 .path_segments()
3142 .and_then(|mut S| S.next_back())
3143 .map(|S| S.to_owned())
3144 .unwrap_or_else(|| F.URI.to_string())
3145 }
3146 })
3147 .unwrap_or_default();
3148
3149 Ok(Value::String(Label))
3150}
3151
3152async fn handle_label_get_base(args:Vec<Value>) -> Result<Value, String> {
3154 let Uri = args
3155 .first()
3156 .and_then(|V| V.as_str())
3157 .ok_or("label:getBase requires uri".to_string())?;
3158
3159 let Base = Uri.split('/').next_back().unwrap_or(Uri);
3160 Ok(Value::String(Base.to_owned()))
3161}
3162
3163async fn handle_model_open(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
3170 let Uri = args
3171 .first()
3172 .and_then(|V| V.as_str())
3173 .ok_or("model:open requires uri".to_string())?
3174 .to_owned();
3175
3176 let FilePath = if Uri.starts_with("file://") {
3178 Uri.trim_start_matches("file://").to_owned()
3179 } else {
3180 Uri.clone()
3181 };
3182
3183 let Content = tokio::fs::read_to_string(&FilePath).await.unwrap_or_default();
3185
3186 let LanguageId = std::path::Path::new(&FilePath)
3188 .extension()
3189 .and_then(|E| E.to_str())
3190 .map(|Ext| {
3191 match Ext {
3192 "rs" => "rust",
3193 "ts" | "tsx" => "typescript",
3194 "js" | "jsx" | "mjs" | "cjs" => "javascript",
3195 "json" | "jsonc" => "json",
3196 "toml" => "toml",
3197 "yaml" | "yml" => "yaml",
3198 "md" => "markdown",
3199 "html" | "htm" => "html",
3200 "css" | "scss" | "less" => "css",
3201 "sh" | "bash" | "zsh" => "shellscript",
3202 "py" => "python",
3203 "go" => "go",
3204 "c" | "h" => "c",
3205 "cpp" | "cc" | "cxx" | "hpp" => "cpp",
3206 _ => "plaintext",
3207 }
3208 })
3209 .unwrap_or("plaintext")
3210 .to_owned();
3211
3212 let Version = runtime
3214 .Environment
3215 .ApplicationState
3216 .Feature
3217 .Documents
3218 .Get(&Uri)
3219 .map(|D| D.Version + 1)
3220 .unwrap_or(1);
3221
3222 {
3224 use crate::ApplicationState::DTO::DocumentStateDTO::DocumentStateDTO;
3225
3226 if let Ok(ParsedUri) = url::Url::parse(&Uri) {
3227 let Lines:Vec<String> = Content.lines().map(|L| L.to_owned()).collect();
3228 let Eol = if Content.contains("\r\n") { "\r\n" } else { "\n" }.to_owned();
3229
3230 let Document = DocumentStateDTO {
3231 URI:ParsedUri,
3232 LanguageIdentifier:LanguageId.clone(),
3233 Version,
3234 Lines,
3235 EOL:Eol,
3236 IsDirty:false,
3237 Encoding:"utf-8".to_owned(),
3238 VersionIdentifier:Version,
3239 };
3240
3241 runtime
3242 .Environment
3243 .ApplicationState
3244 .Feature
3245 .Documents
3246 .AddOrUpdate(Uri.clone(), Document);
3247 }
3248 }
3249
3250 Ok(json!({
3251 "uri": Uri,
3252 "content": Content,
3253 "version": Version,
3254 "languageId": LanguageId,
3255 }))
3256}
3257
3258async fn handle_model_close(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
3260 let Uri = args
3261 .first()
3262 .and_then(|V| V.as_str())
3263 .ok_or("model:close requires uri".to_string())?;
3264
3265 runtime.Environment.ApplicationState.Feature.Documents.Remove(Uri);
3266 Ok(Value::Null)
3267}
3268
3269async fn handle_model_get(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
3271 let Uri = args
3272 .first()
3273 .and_then(|V| V.as_str())
3274 .ok_or("model:get requires uri".to_string())?;
3275
3276 match runtime.Environment.ApplicationState.Feature.Documents.Get(Uri) {
3277 None => Ok(Value::Null),
3278 Some(Document) => {
3279 Ok(json!({
3280 "uri": Uri,
3281 "content": Document.Lines.join(&Document.EOL),
3282 "version": Document.Version,
3283 "languageId": Document.LanguageIdentifier,
3284 }))
3285 },
3286 }
3287}
3288
3289async fn handle_model_get_all(runtime:Arc<ApplicationRunTime>) -> Result<Value, String> {
3291 let All = runtime
3292 .Environment
3293 .ApplicationState
3294 .Feature
3295 .Documents
3296 .GetAll()
3297 .into_iter()
3298 .map(|(Uri, Document)| {
3299 json!({
3300 "uri": Uri,
3301 "content": Document.Lines.join(&Document.EOL),
3302 "version": Document.Version,
3303 "languageId": Document.LanguageIdentifier,
3304 })
3305 })
3306 .collect::<Vec<_>>();
3307
3308 Ok(Value::Array(All))
3309}
3310
3311async fn handle_model_update_content(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
3313 let Uri = args
3314 .first()
3315 .and_then(|V| V.as_str())
3316 .ok_or("model:updateContent requires uri".to_string())?
3317 .to_owned();
3318
3319 let NewContent = args
3320 .get(1)
3321 .and_then(|V| V.as_str())
3322 .ok_or("model:updateContent requires content".to_string())?
3323 .to_owned();
3324
3325 let (NewVersion, LanguageId) = match runtime.Environment.ApplicationState.Feature.Documents.Get(&Uri) {
3326 None => return Err(format!("model:updateContent - model not open: {}", Uri)),
3327 Some(mut Document) => {
3328 Document.Version += 1;
3329 Document.Lines = NewContent.lines().map(|L| L.to_owned()).collect();
3330 Document.IsDirty = true;
3331 let Version = Document.Version;
3332 let LangId = Document.LanguageIdentifier.clone();
3333 runtime
3334 .Environment
3335 .ApplicationState
3336 .Feature
3337 .Documents
3338 .AddOrUpdate(Uri.clone(), Document);
3339 (Version, LangId)
3340 },
3341 };
3342
3343 Ok(json!({
3344 "uri": Uri,
3345 "content": NewContent,
3346 "version": NewVersion,
3347 "languageId": LanguageId,
3348 }))
3349}
3350
3351async fn handle_file_read_native(args:Vec<Value>) -> Result<Value, String> {
3360 let Path = extract_path_from_arg(args.get(0).ok_or("Missing file path")?)?;
3361
3362 dev_log!("vfs", "readFile: {}", Path);
3363
3364 let Bytes = tokio::fs::read(&Path)
3366 .await
3367 .map_err(|E| format!("Failed to read file: {} (path: {})", E, Path))?;
3368
3369 dev_log!("vfs", "readFile OK: {} ({} bytes)", Path, Bytes.len());
3370
3371 let ByteArray:Vec<Value> = Bytes.iter().map(|B| json!(*B)).collect();
3374 Ok(json!({ "buffer": ByteArray }))
3375}
3376
3377async fn handle_file_write_native(args:Vec<Value>) -> Result<Value, String> {
3379 let Path = extract_path_from_arg(args.get(0).ok_or("Missing file path")?)?;
3380
3381 let Content = args.get(1).ok_or("Missing file content")?;
3383
3384 let Bytes = if let Some(S) = Content.as_str() {
3385 S.as_bytes().to_vec()
3386 } else if let Some(Obj) = Content.as_object() {
3387 if let Some(Buf) = Obj.get("buffer") {
3389 if let Some(Arr) = Buf.as_array() {
3390 Arr.iter().filter_map(|V| V.as_u64().map(|N| N as u8)).collect()
3391 } else if let Some(S) = Buf.as_str() {
3392 S.as_bytes().to_vec()
3393 } else {
3394 return Err("Unsupported buffer format".to_string());
3395 }
3396 } else {
3397 serde_json::to_string(Content).unwrap_or_default().into_bytes()
3398 }
3399 } else {
3400 return Err("File content must be a string or VSBuffer".to_string());
3401 };
3402
3403 if let Some(Parent) = std::path::Path::new(&Path).parent() {
3405 tokio::fs::create_dir_all(Parent).await.ok();
3406 }
3407
3408 tokio::fs::write(&Path, &Bytes)
3409 .await
3410 .map_err(|E| format!("Failed to write file: {} (path: {})", E, Path))?;
3411
3412 Ok(Value::Null)
3413}
3414
3415async fn handle_file_rename_native(args:Vec<Value>) -> Result<Value, String> {
3417 let Source = extract_path_from_arg(args.get(0).ok_or("Missing source path")?)?;
3418 let Target = extract_path_from_arg(args.get(1).ok_or("Missing target path")?)?;
3419
3420 tokio::fs::rename(&Source, &Target)
3421 .await
3422 .map_err(|E| format!("Failed to rename: {} -> {} ({})", Source, Target, E))?;
3423
3424 Ok(Value::Null)
3425}
3426
3427async fn handle_file_realpath(args:Vec<Value>) -> Result<Value, String> {
3429 let Path = extract_path_from_arg(args.get(0).ok_or("Missing path")?)?;
3430
3431 let Canonical = tokio::fs::canonicalize(&Path)
3432 .await
3433 .map_err(|E| format!("Failed to realpath: {} ({})", Path, E))?;
3434
3435 Ok(json!({
3436 "scheme": "file",
3437 "path": Canonical.to_string_lossy(),
3438 "authority": ""
3439 }))
3440}
3441
3442async fn handle_file_clone_native(args:Vec<Value>) -> Result<Value, String> {
3444 let Source = extract_path_from_arg(args.get(0).ok_or("Missing source path")?)?;
3445 let Target = extract_path_from_arg(args.get(1).ok_or("Missing target path")?)?;
3446
3447 tokio::fs::copy(&Source, &Target)
3448 .await
3449 .map_err(|E| format!("Failed to clone: {} -> {} ({})", Source, Target, E))?;
3450
3451 Ok(Value::Null)
3452}
3453
3454async fn handle_native_pick_folder(app_handle:AppHandle, _args:Vec<Value>) -> Result<Value, String> {
3463 use tauri_plugin_dialog::DialogExt;
3464 use tauri::WebviewWindow;
3465
3466 dev_log!("folder", "pickFolderAndOpen requested");
3467
3468 dev_log!("folder", "pickFolderAndOpen requested");
3469
3470 let Handle = app_handle.clone();
3471 tokio::task::spawn_blocking(move || {
3472 let FolderPath = Handle.dialog().file().blocking_pick_folder();
3473
3474 if let Some(Path) = FolderPath {
3475 let PathStr = Path.to_string();
3476 dev_log!("folder", "picked: {}", PathStr);
3477
3478 if let Some(Window) = Handle.get_webview_window("main") {
3481 if let Ok(CurrentUrl) = Window.url() {
3482 let Origin = CurrentUrl.origin().unicode_serialization();
3483 let EncodedPath = url::form_urlencoded::Serializer::new(String::new())
3484 .append_pair("folder", &PathStr)
3485 .finish();
3486 let NewUrl = format!("{}/?{}", Origin, EncodedPath);
3487 dev_log!("folder", "navigating: {}", NewUrl);
3488 let _ = Window.navigate(NewUrl.parse().unwrap());
3489 }
3490 }
3491 }
3492 });
3493
3494 Ok(Value::Null)
3495}
3496
3497async fn handle_native_show_open_dialog(app_handle:AppHandle, args:Vec<Value>) -> Result<Value, String> {
3499 dev_log!("folder", "showOpenDialog: {:?}", args);
3500 Ok(json!({ "canceled": true, "filePaths": [] }))
3502}
3503
3504async fn handle_native_os_properties() -> Result<Value, String> {
3506 use sysinfo::System;
3507
3508 let OsType = match std::env::consts::OS {
3509 "macos" => "Darwin",
3510 "windows" => "Windows_NT",
3511 "linux" => "Linux",
3512 _ => std::env::consts::OS,
3513 };
3514
3515 let Release = {
3517 #[cfg(target_os = "macos")]
3518 {
3519 std::process::Command::new("sw_vers")
3520 .arg("-productVersion")
3521 .output()
3522 .ok()
3523 .map(|O| String::from_utf8_lossy(&O.stdout).trim().to_string())
3524 .unwrap_or_else(|| "14.0".to_string())
3525 }
3526 #[cfg(target_os = "windows")]
3527 {
3528 std::process::Command::new("cmd")
3530 .args(["/c", "ver"])
3531 .output()
3532 .ok()
3533 .map(|O| {
3534 let Output = String::from_utf8_lossy(&O.stdout);
3535 Output
3537 .split('[')
3538 .nth(1)
3539 .and_then(|S| S.split(']').next())
3540 .and_then(|S| S.strip_prefix("Version "))
3541 .unwrap_or("10.0.0")
3542 .to_string()
3543 })
3544 .unwrap_or_else(|| "10.0.0".to_string())
3545 }
3546 #[cfg(target_os = "linux")]
3547 {
3548 std::process::Command::new("uname")
3550 .arg("-r")
3551 .output()
3552 .ok()
3553 .map(|O| String::from_utf8_lossy(&O.stdout).trim().to_string())
3554 .unwrap_or_else(|| "6.1.0".to_string())
3555 }
3556 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
3557 {
3558 "0.0.0".to_string()
3559 }
3560 };
3561
3562 let mut Sys = System::new();
3564 Sys.refresh_cpu_all();
3565 let Cpus:Vec<Value> = Sys
3566 .cpus()
3567 .iter()
3568 .map(|Cpu| {
3569 json!({
3570 "model": Cpu.brand(),
3571 "speed": Cpu.frequency()
3572 })
3573 })
3574 .collect();
3575
3576 Ok(json!({
3577 "type": OsType,
3578 "release": Release,
3579 "arch": std::env::consts::ARCH,
3580 "platform": std::env::consts::OS,
3581 "cpus": Cpus
3582 }))
3583}
3584
3585async fn handle_native_os_statistics() -> Result<Value, String> {
3587 use sysinfo::System;
3588
3589 let mut Sys = System::new();
3590 Sys.refresh_memory();
3591
3592 let TotalMem = Sys.total_memory();
3593 let FreeMem = Sys.available_memory();
3594
3595 let LoadAvg = {
3597 #[cfg(unix)]
3598 {
3599 let Load = System::load_average();
3600 vec![Load.one, Load.five, Load.fifteen]
3601 }
3602 #[cfg(not(unix))]
3603 {
3604 vec![0.0, 0.0, 0.0]
3605 }
3606 };
3607
3608 Ok(json!({
3609 "totalmem": TotalMem,
3610 "freemem": FreeMem,
3611 "loadavg": LoadAvg
3612 }))
3613}
3614
3615async fn handle_native_is_fullscreen(app_handle:AppHandle) -> Result<Value, String> {
3617 use tauri::Manager;
3618 let Window = app_handle.get_webview_window("main");
3619 if let Some(W) = Window {
3620 Ok(json!(W.is_fullscreen().unwrap_or(false)))
3621 } else {
3622 Ok(json!(false))
3623 }
3624}
3625
3626async fn handle_native_is_maximized(app_handle:AppHandle) -> Result<Value, String> {
3628 use tauri::Manager;
3629 let Window = app_handle.get_webview_window("main");
3630 if let Some(W) = Window {
3631 Ok(json!(W.is_maximized().unwrap_or(false)))
3632 } else {
3633 Ok(json!(false))
3634 }
3635}
3636
3637async fn handle_native_find_free_port(args:Vec<Value>) -> Result<Value, String> {
3639 let StartPort = args.get(0).and_then(|V| V.as_u64()).unwrap_or(9000) as u16;
3640
3641 for Port in StartPort..StartPort + 100 {
3642 if std::net::TcpListener::bind(("127.0.0.1", Port)).is_ok() {
3643 return Ok(json!(Port));
3644 }
3645 }
3646 Ok(json!(0))
3647}
3648
3649async fn handle_local_pty_get_profiles() -> Result<Value, String> {
3655 let mut Profiles = Vec::new();
3656
3657 #[cfg(unix)]
3658 {
3659 let DefaultShell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
3660
3661 let UnixShells = [
3663 "/bin/zsh",
3664 "/bin/bash",
3665 "/bin/sh",
3666 "/usr/bin/zsh",
3667 "/usr/bin/bash",
3668 "/usr/bin/fish",
3669 "/usr/local/bin/fish",
3670 "/usr/local/bin/zsh",
3671 "/usr/local/bin/bash",
3672 "/bin/dash", "/usr/bin/ksh", "/usr/bin/tcsh", "/bin/csh", "/usr/bin/pwsh", "/usr/local/bin/pwsh",
3678 ];
3679
3680 for Shell in &UnixShells {
3681 if std::path::Path::new(Shell).exists() {
3682 let Name = std::path::Path::new(Shell)
3683 .file_name()
3684 .and_then(|N| N.to_str())
3685 .unwrap_or("shell");
3686
3687 Profiles.push(json!({
3688 "profileName": Name,
3689 "path": Shell,
3690 "isDefault": *Shell == DefaultShell.as_str(),
3691 "args": [],
3692 "env": {},
3693 "icon": "terminal"
3694 }));
3695 }
3696 }
3697
3698 if let Ok(ShellsFile) = std::fs::read_to_string("/etc/shells") {
3700 for Line in ShellsFile.lines() {
3701 let Trimmed = Line.trim();
3702 if Trimmed.starts_with('/') && !Trimmed.starts_with('#') {
3703 let AlreadyAdded = Profiles.iter().any(|P| P.get("path").and_then(|V| V.as_str()) == Some(Trimmed));
3704 if !AlreadyAdded && std::path::Path::new(Trimmed).exists() {
3705 let Name = std::path::Path::new(Trimmed)
3706 .file_name()
3707 .and_then(|N| N.to_str())
3708 .unwrap_or("shell");
3709
3710 Profiles.push(json!({
3711 "profileName": Name,
3712 "path": Trimmed,
3713 "isDefault": Trimmed == DefaultShell.as_str(),
3714 "args": [],
3715 "env": {},
3716 "icon": "terminal"
3717 }));
3718 }
3719 }
3720 }
3721 }
3722 }
3723
3724 #[cfg(target_os = "windows")]
3725 {
3726 let SystemRoot = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
3728 let ProgramFiles = std::env::var("ProgramFiles").unwrap_or_else(|_| "C:\\Program Files".to_string());
3729 let LocalAppData =
3730 std::env::var("LOCALAPPDATA").unwrap_or_else(|_| "C:\\Users\\User\\AppData\\Local".to_string());
3731
3732 let WindowsShells:Vec<(&str, String, Vec<&str>)> = vec![
3733 (
3734 "PowerShell",
3735 format!("{}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", SystemRoot),
3736 vec!["-NoLogo"],
3737 ),
3738 (
3739 "PowerShell 7",
3740 format!("{}\\PowerShell\\7\\pwsh.exe", ProgramFiles),
3741 vec!["-NoLogo"],
3742 ),
3743 ("Command Prompt", format!("{}\\System32\\cmd.exe", SystemRoot), vec![]),
3744 (
3745 "Git Bash",
3746 format!("{}\\Git\\bin\\bash.exe", ProgramFiles),
3747 vec!["--login", "-i"],
3748 ),
3749 (
3750 "Git Bash (User)",
3751 format!("{}\\Programs\\Git\\bin\\bash.exe", LocalAppData),
3752 vec!["--login", "-i"],
3753 ),
3754 ("WSL", format!("{}\\System32\\wsl.exe", SystemRoot), vec![]),
3755 ("MSYS2", "C:\\msys64\\usr\\bin\\bash.exe".to_string(), vec!["--login", "-i"]),
3756 ("Cygwin", "C:\\cygwin64\\bin\\bash.exe".to_string(), vec!["--login", "-i"]),
3757 ];
3758
3759 let mut IsFirstFound = true;
3760 for (Name, Path, Args) in &WindowsShells {
3761 if std::path::Path::new(Path).exists() {
3762 Profiles.push(json!({
3763 "profileName": Name,
3764 "path": Path,
3765 "isDefault": IsFirstFound,
3766 "args": Args,
3767 "env": {},
3768 "icon": "terminal"
3769 }));
3770 IsFirstFound = false;
3771 }
3772 }
3773 }
3774
3775 Ok(json!(Profiles))
3776}
3777
3778async fn handle_local_pty_get_default_shell() -> Result<Value, String> {
3780 #[cfg(unix)]
3781 {
3782 let Shell = std::env::var("SHELL").unwrap_or_else(|_| {
3783 for Path in &["/bin/zsh", "/bin/bash", "/bin/sh"] {
3785 if std::path::Path::new(Path).exists() {
3786 return Path.to_string();
3787 }
3788 }
3789 "/bin/sh".to_string()
3790 });
3791 Ok(json!(Shell))
3792 }
3793
3794 #[cfg(target_os = "windows")]
3795 {
3796 let SystemRoot = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
3797 let PwshPath = format!("{}\\PowerShell\\7\\pwsh.exe", std::env::var("ProgramFiles").unwrap_or_default());
3799 if std::path::Path::new(&PwshPath).exists() {
3800 return Ok(json!(PwshPath));
3801 }
3802 Ok(json!(format!(
3803 "{}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
3804 SystemRoot
3805 )))
3806 }
3807
3808 #[cfg(not(any(unix, target_os = "windows")))]
3809 {
3810 Ok(json!("/bin/sh"))
3811 }
3812}
3813
3814async fn handle_local_pty_get_environment() -> Result<Value, String> {
3816 let Env:HashMap<String, String> = std::env::vars().collect();
3817 Ok(json!(Env))
3818}
3819
3820async fn handle_native_get_color_scheme() -> Result<Value, String> {
3822 let Dark = detect_dark_mode();
3823 let HighContrast = {
3825 #[cfg(target_os = "windows")]
3826 {
3827 std::process::Command::new("reg")
3829 .args(["query", "HKCU\\Control Panel\\Accessibility\\HighContrast", "/v", "Flags"])
3830 .output()
3831 .ok()
3832 .map(|O| {
3833 let Output = String::from_utf8_lossy(&O.stdout);
3834 Output.contains("0x1") || Output.contains("REG_DWORD 1")
3836 })
3837 .unwrap_or(false)
3838 }
3839 #[cfg(not(target_os = "windows"))]
3840 {
3841 #[cfg(target_os = "linux")]
3844 {
3845 std::process::Command::new("gsettings")
3846 .args(["get", "org.gnome.desktop.a11y.interface", "high-contrast"])
3847 .output()
3848 .ok()
3849 .map(|O| String::from_utf8_lossy(&O.stdout).trim() == "true")
3850 .unwrap_or(false)
3851 }
3852 #[cfg(not(target_os = "linux"))]
3853 {
3854 false
3855 }
3856 }
3857 };
3858
3859 Ok(json!({ "dark": Dark, "highContrast": HighContrast }))
3860}
3861
3862fn detect_dark_mode() -> bool {
3864 #[cfg(target_os = "macos")]
3865 {
3866 std::process::Command::new("defaults")
3868 .args(["read", "-g", "AppleInterfaceStyle"])
3869 .output()
3870 .ok()
3871 .map(|O| String::from_utf8_lossy(&O.stdout).trim().to_lowercase().contains("dark"))
3872 .unwrap_or(false)
3873 }
3874
3875 #[cfg(target_os = "windows")]
3876 {
3877 std::process::Command::new("reg")
3880 .args([
3881 "query",
3882 "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
3883 "/v",
3884 "AppsUseLightTheme",
3885 ])
3886 .output()
3887 .ok()
3888 .map(|O| {
3889 let Output = String::from_utf8_lossy(&O.stdout);
3890 Output.contains("0x0") || Output.contains("REG_DWORD 0")
3891 })
3892 .unwrap_or(false)
3893 }
3894
3895 #[cfg(target_os = "linux")]
3896 {
3897 let GtkDark = std::process::Command::new("gsettings")
3900 .args(["get", "org.gnome.desktop.interface", "color-scheme"])
3901 .output()
3902 .ok()
3903 .map(|O| String::from_utf8_lossy(&O.stdout).contains("dark"))
3904 .unwrap_or(false);
3905
3906 if GtkDark {
3907 return true;
3908 }
3909
3910 let GtkTheme = std::process::Command::new("gsettings")
3912 .args(["get", "org.gnome.desktop.interface", "gtk-theme"])
3913 .output()
3914 .ok()
3915 .map(|O| String::from_utf8_lossy(&O.stdout).to_lowercase().contains("dark"))
3916 .unwrap_or(false);
3917
3918 if GtkTheme {
3919 return true;
3920 }
3921
3922 let KdeDark = std::env::var("KDE_COLOR_SCHEME")
3924 .ok()
3925 .map(|V| V.to_lowercase().contains("dark"))
3926 .unwrap_or(false);
3927
3928 if KdeDark {
3929 return true;
3930 }
3931
3932 let XfceDark = std::process::Command::new("xfconf-query")
3934 .args(["-c", "xsettings", "-p", "/Net/ThemeName"])
3935 .output()
3936 .ok()
3937 .map(|O| String::from_utf8_lossy(&O.stdout).to_lowercase().contains("dark"))
3938 .unwrap_or(false);
3939
3940 XfceDark
3941 }
3942
3943 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
3944 {
3945 false
3946 }
3947}
3948
3949async fn handle_file_stat_native(args:Vec<Value>) -> Result<Value, String> {
3955 let Path = extract_path_from_arg(args.get(0).ok_or("Missing file path")?)?;
3956
3957 dev_log!("vfs", "stat: {}", Path);
3958
3959 let Metadata = tokio::fs::symlink_metadata(&Path).await.map_err(|E| {
3960 dev_log!("vfs", "stat ENOENT: {}", Path);
3961 format!("Failed to stat file: {} (path: {})", E, Path)
3962 })?;
3963
3964 dev_log!("vfs", "stat OK: {} (dir={})", Path, Metadata.is_dir());
3965 Ok(metadata_to_istat(&Metadata))
3966}
3967
3968async fn handle_file_exists_native(args:Vec<Value>) -> Result<Value, String> {
3970 let Path = extract_path_from_arg(args.get(0).ok_or("Missing file path")?)?;
3971
3972 Ok(json!(tokio::fs::try_exists(&Path).await.unwrap_or(false)))
3973}
3974
3975async fn handle_file_delete_native(args:Vec<Value>) -> Result<Value, String> {
3977 let Path = extract_path_from_arg(args.get(0).ok_or("Missing file path")?)?;
3978
3979 let Recursive = args
3981 .get(1)
3982 .and_then(|V| V.as_object())
3983 .and_then(|O| O.get("recursive"))
3984 .and_then(|V| V.as_bool())
3985 .unwrap_or(false);
3986
3987 let PathBuf = std::path::Path::new(&Path);
3988
3989 if PathBuf.is_dir() {
3990 if Recursive {
3991 tokio::fs::remove_dir_all(&Path).await
3992 } else {
3993 tokio::fs::remove_dir(&Path).await
3994 }
3995 } else {
3996 tokio::fs::remove_file(&Path).await
3997 }
3998 .map_err(|E| format!("Failed to delete: {} ({})", Path, E))?;
3999
4000 Ok(Value::Null)
4001}
4002
4003async fn handle_file_mkdir_native(args:Vec<Value>) -> Result<Value, String> {
4005 let Path = extract_path_from_arg(args.get(0).ok_or("Missing directory path")?)?;
4006
4007 tokio::fs::create_dir_all(&Path)
4008 .await
4009 .map_err(|E| format!("Failed to mkdir: {} ({})", Path, E))?;
4010
4011 Ok(Value::Null)
4012}
4013
4014async fn handle_file_readdir_native(args:Vec<Value>) -> Result<Value, String> {
4017 let Path = extract_path_from_arg(args.get(0).ok_or("Missing directory path")?)?;
4018
4019 dev_log!("vfs", "readdir: {}", Path);
4020
4021 let mut Entries = tokio::fs::read_dir(&Path)
4022 .await
4023 .map_err(|E| format!("Failed to readdir: {} ({})", Path, E))?;
4024
4025 let mut Result = Vec::new();
4026
4027 while let Some(Entry) = Entries.next_entry().await.map_err(|E| E.to_string())? {
4028 let Name = Entry.file_name().to_string_lossy().to_string();
4029 let FileType = Entry.file_type().await.map_err(|E| E.to_string())?;
4030
4031 let TypeValue = if FileType.is_symlink() {
4032 64 } else if FileType.is_dir() {
4034 2 } else {
4036 1 };
4038
4039 Result.push(json!([Name, TypeValue]));
4040 }
4041
4042 Ok(json!(Result))
4043}
4044
4045async fn handle_storage_get_items(runtime:Arc<ApplicationRunTime>, _args:Vec<Value>) -> Result<Value, String> {
4052 let provider:Arc<dyn StorageProvider> = runtime.Environment.Require();
4053
4054 match provider.GetAllStorage(true).await {
4055 Ok(State) => {
4056 if let Some(Obj) = State.as_object() {
4058 let Tuples:Vec<Value> = Obj
4059 .iter()
4060 .map(|(K, V)| {
4061 let ValStr = match V {
4062 Value::String(S) => S.clone(),
4063 _ => V.to_string(),
4064 };
4065 json!([K, ValStr])
4066 })
4067 .collect();
4068 Ok(json!(Tuples))
4069 } else {
4070 Ok(json!([]))
4071 }
4072 },
4073 Err(_) => Ok(json!([])),
4074 }
4075}
4076
4077async fn handle_storage_update_items(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
4081 let provider:Arc<dyn StorageProvider> = runtime.Environment.Require();
4082
4083 if let Some(Updates) = args.get(0).and_then(|V| V.as_object()) {
4084 if let Some(Inserts) = Updates.get("insert") {
4086 if let Some(Arr) = Inserts.as_array() {
4087 for Item in Arr {
4088 if let Some(Pair) = Item.as_array() {
4089 if let (Some(Key), Some(Val)) = (Pair.get(0).and_then(|V| V.as_str()), Pair.get(1)) {
4090 let _ = provider.UpdateStorageValue(true, Key.to_string(), Some(Val.clone())).await;
4091 }
4092 }
4093 }
4094 } else if let Some(Obj) = Inserts.as_object() {
4095 for (Key, Val) in Obj {
4096 let _ = provider.UpdateStorageValue(true, Key.clone(), Some(Val.clone())).await;
4097 }
4098 }
4099 }
4100
4101 if let Some(Deletes) = Updates.get("delete").and_then(|V| V.as_array()) {
4103 for Key in Deletes {
4104 if let Some(K) = Key.as_str() {
4105 let _ = provider.UpdateStorageValue(true, K.to_string(), None).await;
4106 }
4107 }
4108 }
4109 }
4110
4111 Ok(Value::Null)
4112}