1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3pub mod Commands;
7pub mod Configuration;
8pub mod Extension;
9pub mod Extensions;
10pub mod FileSystem;
11pub mod Git;
12pub mod Model;
13pub mod NativeDialog;
14pub mod NativeHost;
15pub mod Navigation;
16pub mod Output;
17pub mod Search;
18pub mod Storage;
19pub mod Terminal;
20pub mod UI;
21pub mod Utilities;
22
23use std::{collections::HashMap, path::PathBuf, sync::Arc};
29
30use Commands::*;
31use Configuration::*;
32use Extensions::*;
33use FileSystem::{
34 Managed::{
35 FileCopy::*,
36 FileDelete::*,
37 FileExists::*,
38 FileMkdir::*,
39 FileMove::*,
40 FileRead::*,
41 FileReadBinary::*,
42 FileReaddir::*,
43 FileStat::*,
44 FileWrite::*,
45 FileWriteBinary::*,
46 },
47 Native::{
48 FileCloneNative::*,
49 FileDeleteNative::*,
50 FileExistsNative::*,
51 FileMkdirNative::*,
52 FileReadNative::*,
53 FileReaddirNative::*,
54 FileRealpath::*,
55 FileRenameNative::*,
56 FileStatNative::*,
57 FileWriteNative::*,
58 },
59};
60use Model::*;
61use NativeHost::{
62 FindFreePort::*,
63 GetColorScheme::*,
64 IsFullscreen::*,
65 IsMaximized::*,
66 OSProperties::*,
67 OSStatistics::*,
68 OpenExternal::*,
69 PickFolder::*,
70 ShowItemInFolder::*,
71 ShowOpenDialog::*,
72};
73use Navigation::*;
74use Output::*;
75use Search::*;
76use Storage::*;
77use Terminal::*;
78use UI::{
79 Decoration::*,
80 Keybinding::*,
81 Lifecycle::*,
82 Notification::*,
83 Progress::*,
84 QuickInput::*,
85 Theme::*,
86 WorkingCopy::*,
87 Workspace::*,
88};
89use Utilities::{
90 ApplicationRoot::*,
91 ChannelPriority::*,
92 JsonValueHelpers::*,
93 MetadataEncoding::*,
94 PathExtraction::*,
95 RecentlyOpened::*,
96 UserdataDir::*,
97};
98use Echo::Task::Priority::Priority as EchoPriority;
99use serde_json::{Value, json};
100use tauri::{AppHandle, Manager};
101use CommonLibrary::Configuration::DTO::{
103 ConfigurationOverridesDTO as ConfigurationOverridesDTOModule,
104 ConfigurationTarget as ConfigurationTargetModule,
105};
106
107use crate::dev_log;
108type ConfigurationOverridesDTO = ConfigurationOverridesDTOModule::ConfigurationOverridesDTO;
109type ConfigurationTarget = ConfigurationTargetModule::ConfigurationTarget;
110
111use CommonLibrary::{
112 Command::CommandExecutor::CommandExecutor,
113 Configuration::ConfigurationProvider::ConfigurationProvider,
114 Environment::Requires::Requires,
115 Error::CommonError::CommonError,
116 ExtensionManagement::ExtensionManagementService::ExtensionManagementService,
117 FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
118 IPC::SkyEvent::SkyEvent,
119 Storage::StorageProvider::StorageProvider,
120};
121
122use crate::{
123 ApplicationState::{
124 DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO,
125 State::{
126 ApplicationState::ApplicationState,
127 WorkspaceState::WorkspaceDelta::UpdateWorkspaceFoldersAndBroadcast,
128 },
129 },
130 RunTime::ApplicationRunTime::ApplicationRunTime,
131};
132
133pub async fn mountain_ipc_invoke(
150 ApplicationHandle:AppHandle,
151 command:String,
152 Arguments:Vec<Value>,
153) -> Result<Value, String> {
154 let OTLPStart = crate::IPC::DevLog::NowNano();
155 let IsHighFrequencyCommand = matches!(
170 command.as_str(),
171 "logger:log"
172 | "logger:registerLogger"
173 | "logger:createLogger"
174 | "log:registerLogger"
175 | "log:createLogger"
176 | "file:stat"
177 | "file:readFile"
178 | "file:readdir"
179 | "file:writeFile"
180 | "file:delete"
181 | "file:rename"
182 | "file:realpath"
183 | "file:read"
184 | "file:write"
185 | "storage:getItems"
186 | "storage:updateItems"
187 | "configuration:lookup"
188 | "configuration:inspect"
189 | "themes:getColorTheme"
190 | "output:append"
191 | "progress:report"
192 );
193 if !IsHighFrequencyCommand {
194 dev_log!("ipc", "invoke: {} args_count={}", command, Arguments.len());
195 }
196
197 ensure_userdata_dirs();
199
200 let RunTime:Arc<ApplicationRunTime> = ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
204
205 let CommandPriority = ResolveChannelPriority(&command);
226
227 let Scheduler = RunTime.Scheduler.clone();
228
229 let (ResultSender, ResultReceiver) = tokio::sync::oneshot::channel::<Result<Value, String>>();
230
231 let DispatchAppHandle = ApplicationHandle.clone();
232
233 let DispatchRuntime = RunTime.clone();
234
235 let DispatchCommand = command.clone();
236
237 let DispatchArgs = Arguments;
238
239 Scheduler.Submit(
240 async move {
241 let ApplicationHandle = DispatchAppHandle;
242 let RunTime = DispatchRuntime;
243 let command = DispatchCommand;
244 let Arguments = DispatchArgs;
245
246 let MatchResult = match command.as_str() {
247 "configuration:get" | "configuration:getValue" => {
253 dev_log!("config", "{}", command);
254 ConfigurationGet(RunTime.clone(), Arguments).await
255 },
256 "configuration:update" | "configuration:updateValue" => {
257 dev_log!("config", "{}", command);
258 ConfigurationUpdate(RunTime.clone(), Arguments).await
259 },
260 "configuration:onDidChange" => Ok(Value::Null),
266
267 "logger:log"
269 | "logger:warn"
270 | "logger:error"
271 | "logger:info"
272 | "logger:debug"
273 | "logger:trace"
274 | "logger:critical"
275 | "logger:flush"
276 | "logger:setLevel"
277 | "logger:getLevel"
278 | "logger:createLogger"
279 | "logger:registerLogger"
280 | "logger:deregisterLogger"
281 | "logger:getRegisteredLoggers"
282 | "logger:setVisibility" => Ok(Value::Null),
283
284 "file:read" | "file:readFile" => FileReadNative(Arguments).await,
296 "file:write" | "file:writeFile" => FileWriteNative(Arguments).await,
297 "file:stat" => FileStatNative(Arguments).await,
298 "file:exists" => FileExistsNative(Arguments).await,
299 "file:delete" => FileDeleteNative(Arguments).await,
300 "file:copy" => FileCloneNative(Arguments).await,
301 "file:move" | "file:rename" => FileRenameNative(Arguments).await,
302 "file:mkdir" => FileMkdirNative(Arguments).await,
303 "file:readdir" => FileReaddirNative(Arguments).await,
304 "file:readBinary" => FileReadBinary(RunTime.clone(), Arguments).await,
305 "file:writeBinary" => FileWriteBinary(RunTime.clone(), Arguments).await,
306 "file:watch" | "file:unwatch" => {
313 dev_log!("fs-route", "{} (stub-ack)", command);
314 Ok(Value::Null)
315 },
316
317 "storage:get" => StorageGet(RunTime.clone(), Arguments).await,
324 "storage:set" => StorageSet(RunTime.clone(), Arguments).await,
325 "storage:getItems" => {
326 dev_log!("storage-verbose", "storage:getItems");
330 StorageGetItems(RunTime.clone(), Arguments).await
331 },
332 "storage:updateItems" => {
333 dev_log!("storage-verbose", "storage:updateItems");
334 StorageUpdateItems(RunTime.clone(), Arguments).await
335 },
336 "storage:optimize" => {
337 dev_log!("storage", "storage:optimize");
338 Ok(Value::Null)
339 },
340 "storage:isUsed" => {
341 dev_log!("storage", "storage:isUsed");
342 Ok(Value::Null)
343 },
344 "storage:close" => {
345 dev_log!("storage", "storage:close");
346 Ok(Value::Null)
347 },
348 "storage:onDidChangeItems" | "storage:logStorage" => {
352 dev_log!("storage-verbose", "{} (stub-ack)", command);
353 Ok(Value::Null)
354 },
355
356 "environment:get" => {
358 dev_log!("config", "environment:get");
359 EnvironmentGet(RunTime.clone(), Arguments).await
360 },
361
362 "native:showItemInFolder" => ShowItemInFolder(RunTime.clone(), Arguments).await,
364 "native:openExternal" => OpenExternal(RunTime.clone(), Arguments).await,
365
366 "workbench:getConfiguration" => WorkbenchConfiguration(RunTime.clone(), Arguments).await,
368
369 "diagnostic:log" => {
375 let Tag = Arguments.first().and_then(|V| V.as_str()).unwrap_or("webview").to_string();
376 let Message = Arguments.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
377 let Extras = if Arguments.len() > 2 {
378 let Tail:Vec<String> = Arguments
379 .iter()
380 .skip(2)
381 .map(|V| {
382 let S = serde_json::to_string(V).unwrap_or_default();
383 if S.len() > 240 {
389 let CutAt = S
390 .char_indices()
391 .map(|(Index, _)| Index)
392 .take_while(|Index| *Index <= 240)
393 .last()
394 .unwrap_or(0);
395 format!("{}…", &S[..CutAt])
396 } else {
397 S
398 }
399 })
400 .collect();
401 format!(" {}", Tail.join(" "))
402 } else {
403 String::new()
404 };
405 dev_log!("diagnostic", "[{}] {}{}", Tag, Message, Extras);
406 Ok(Value::Null)
407 },
408
409 "commands:execute" | "commands:executeCommand" => CommandsExecute(RunTime.clone(), Arguments).await,
414 "commands:getAll" | "commands:getCommands" => {
415 dev_log!("commands", "{}", command);
416 CommandsGetAll(RunTime.clone()).await
417 },
418 "commands:registerCommand"
423 | "commands:unregisterCommand"
424 | "commands:onDidRegisterCommand"
425 | "commands:onDidExecuteCommand" => Ok(Value::Null),
426
427 "extensions:getAll" => {
429 dev_log!("extensions", "extensions:getAll");
430 ExtensionsGetAll(RunTime.clone()).await
431 },
432 "extensions:get" => {
433 dev_log!("extensions", "extensions:get");
434 ExtensionsGet(RunTime.clone(), Arguments).await
435 },
436 "extensions:isActive" => {
437 dev_log!("extensions", "extensions:isActive");
438 ExtensionsIsActive(RunTime.clone(), Arguments).await
439 },
440
441 "extensions:getInstalled" | "extensions:scanSystemExtensions" => {
454 let ArgsSummary = Arguments
460 .iter()
461 .enumerate()
462 .map(|(Idx, V)| {
463 let Preview = serde_json::to_string(V).unwrap_or_default();
464 let Trimmed = if Preview.len() > 180 {
467 let CutAt = Preview
468 .char_indices()
469 .map(|(Index, _)| Index)
470 .take_while(|Index| *Index <= 180)
471 .last()
472 .unwrap_or(0);
473 format!("{}…", &Preview[..CutAt])
474 } else {
475 Preview
476 };
477 format!("[{}]={}", Idx, Trimmed)
478 })
479 .collect::<Vec<_>>()
480 .join(" ");
481 dev_log!("extensions", "{} Arguments={}", command, ArgsSummary);
482 let EffectiveArgs = if command == "extensions:scanSystemExtensions" {
491 let mut Overridden = Arguments.clone();
492 if Overridden.is_empty() {
493 Overridden.push(Value::Null);
494 }
495 Overridden[0] = json!(0);
496 Overridden
497 } else {
498 Arguments.clone()
499 };
500 ExtensionsGetInstalled(RunTime.clone(), EffectiveArgs).await
501 },
502 "extensions:scanUserExtensions" => {
503 dev_log!("extensions", "{} (forwarded to getInstalled with type=User)", command);
512 let mut UserArgs = Arguments.clone();
513 if UserArgs.is_empty() {
514 UserArgs.push(Value::Null);
515 }
516 UserArgs[0] = json!(1);
517 ExtensionsGetInstalled(RunTime.clone(), UserArgs).await
518 },
519 "extensions:getUninstalled" => {
520 dev_log!("extensions", "{} (returning [])", command);
524 Ok(Value::Array(Vec::new()))
525 },
526 "extensions:query" | "extensions:getExtensions" | "extensions:getRecommendations" => {
530 dev_log!("extensions", "{} (offline gallery - returning [])", command);
531 Ok(Value::Array(Vec::new()))
532 },
533 "extensions:getExtensionsControlManifest" => {
539 dev_log!("extensions", "{} (offline gallery - empty manifest)", command);
540 Ok(json!({
541 "malicious": [],
542 "deprecated": {},
543 "search": [],
544 "autoUpdate": {},
545 }))
546 },
547 "extensions:resetPinnedStateForAllUserExtensions" => {
554 dev_log!("extensions", "{} (no-op, pin state is UI-local)", command);
555 Ok(Value::Null)
556 },
557 "extensions:install" => {
565 Extension::ExtensionInstall::ExtensionInstall(ApplicationHandle.clone(), RunTime.clone(), Arguments)
566 .await
567 },
568 "extensions:uninstall" => {
569 Extension::ExtensionUninstall::ExtensionUninstall(
570 ApplicationHandle.clone(),
571 RunTime.clone(),
572 Arguments,
573 )
574 .await
575 },
576
577 "extensions:getManifest" => {
586 let VsixPath = match Arguments.first() {
587 Some(serde_json::Value::String(Path)) => Path.clone(),
588 Some(Obj) => {
589 Obj.get("fsPath")
590 .and_then(|V| V.as_str())
591 .map(str::to_owned)
592 .or_else(|| Obj.get("path").and_then(|V| V.as_str()).map(str::to_owned))
593 .unwrap_or_default()
594 },
595 None => String::new(),
596 };
597 dev_log!("extensions", "extensions:getManifest vsix={}", VsixPath);
598 if VsixPath.is_empty() {
599 Err("extensions:getManifest: missing VSIX path argument".to_string())
600 } else {
601 let Path = std::path::PathBuf::from(&VsixPath);
602 match crate::ExtensionManagement::VsixInstaller::ReadFullManifest(&Path) {
603 Ok(Manifest) => Ok(Manifest),
604 Err(Error) => {
605 dev_log!(
606 "extensions",
607 "warn: [WindServiceHandlers] extensions:getManifest failed for '{}': {}",
608 VsixPath,
609 Error
610 );
611 Err(format!("extensions:getManifest failed: {}", Error))
612 },
613 }
614 }
615 },
616 "extensions:reinstall" | "extensions:updateMetadata" => {
621 dev_log!("extensions", "{} (no-op: no gallery backend)", command);
622 Ok(Value::Null)
623 },
624
625 "terminal:create" => {
627 dev_log!("terminal", "terminal:create");
628 TerminalCreate(RunTime.clone(), Arguments).await
629 },
630 "terminal:sendText" => {
631 dev_log!("terminal", "terminal:sendText");
632 TerminalSendText(RunTime.clone(), Arguments).await
633 },
634 "terminal:dispose" => {
635 dev_log!("terminal", "terminal:dispose");
636 TerminalDispose(RunTime.clone(), Arguments).await
637 },
638 "terminal:show" => {
639 dev_log!("terminal", "terminal:show");
640 TerminalShow(RunTime.clone(), Arguments).await
641 },
642 "terminal:hide" => {
643 dev_log!("terminal", "terminal:hide");
644 TerminalHide(RunTime.clone(), Arguments).await
645 },
646
647 "output:create" => OutputCreate(ApplicationHandle.clone(), Arguments).await,
649 "output:append" => {
650 dev_log!("output", "output:append");
651 OutputAppend(ApplicationHandle.clone(), Arguments).await
652 },
653 "output:appendLine" => {
654 dev_log!("output", "output:appendLine");
655 OutputAppendLine(ApplicationHandle.clone(), Arguments).await
656 },
657 "output:clear" => {
658 dev_log!("output", "output:clear");
659 OutputClear(ApplicationHandle.clone(), Arguments).await
660 },
661 "output:show" => {
662 dev_log!("output", "output:show");
663 OutputShow(ApplicationHandle.clone(), Arguments).await
664 },
665
666 "textFile:read" => {
668 dev_log!("textfile", "textFile:read");
669 TextfileRead(RunTime.clone(), Arguments).await
670 },
671 "textFile:write" => {
672 dev_log!("textfile", "textFile:write");
673 TextfileWrite(RunTime.clone(), Arguments).await
674 },
675 "textFile:save" => TextfileSave(RunTime.clone(), Arguments).await,
676
677 "storage:delete" => {
679 dev_log!("storage", "storage:delete");
680 StorageDelete(RunTime.clone(), Arguments).await
681 },
682 "storage:keys" => {
683 dev_log!("storage", "storage:keys");
684 StorageKeys(RunTime.clone()).await
685 },
686
687 "notification:show" => {
689 dev_log!("notification", "notification:show");
690 NotificationShow(ApplicationHandle.clone(), Arguments).await
691 },
692 "notification:showProgress" => {
693 dev_log!("notification", "notification:showProgress");
694 NotificationShowProgress(ApplicationHandle.clone(), Arguments).await
695 },
696 "notification:updateProgress" => {
697 dev_log!("notification", "notification:updateProgress");
698 NotificationUpdateProgress(ApplicationHandle.clone(), Arguments).await
699 },
700 "notification:endProgress" => {
701 dev_log!("notification", "notification:endProgress");
702 NotificationEndProgress(ApplicationHandle.clone(), Arguments).await
703 },
704
705 "progress:begin" => {
707 dev_log!("progress", "progress:begin");
708 ProgressBegin(ApplicationHandle.clone(), Arguments).await
709 },
710 "progress:report" => {
711 dev_log!("progress", "progress:report");
712 ProgressReport(ApplicationHandle.clone(), Arguments).await
713 },
714 "progress:end" => {
715 dev_log!("progress", "progress:end");
716 ProgressEnd(ApplicationHandle.clone(), Arguments).await
717 },
718
719 "quickInput:showQuickPick" => {
721 dev_log!("quickinput", "quickInput:showQuickPick");
722 QuickInputShowQuickPick(RunTime.clone(), Arguments).await
723 },
724 "quickInput:showInputBox" => {
725 dev_log!("quickinput", "quickInput:showInputBox");
726 QuickInputShowInputBox(RunTime.clone(), Arguments).await
727 },
728
729 "workspaces:getFolders" | "workspaces:getWorkspaceFolders" | "workspaces:getWorkspace" => {
734 dev_log!("workspaces", "{}", command);
735 WorkspacesGetFolders(RunTime.clone()).await
736 },
737 "workspaces:addFolder" | "workspaces:addWorkspaceFolders" => {
738 dev_log!("workspaces", "{}", command);
739 WorkspacesAddFolder(RunTime.clone(), Arguments).await
740 },
741 "workspaces:removeFolder" | "workspaces:removeWorkspaceFolders" => {
742 dev_log!("workspaces", "{}", command);
743 WorkspacesRemoveFolder(RunTime.clone(), Arguments).await
744 },
745 "workspaces:getName" => {
746 dev_log!("workspaces", "{}", command);
747 WorkspacesGetName(RunTime.clone()).await
748 },
749 "workspaces:onDidChangeWorkspaceFolders" | "workspaces:onDidChangeWorkspaceName" => {
753 dev_log!("workspaces", "{} (stub-ack)", command);
754 Ok(Value::Null)
755 },
756
757 "themes:getActive" => {
759 dev_log!("themes", "themes:getActive");
760 ThemesGetActive(RunTime.clone()).await
761 },
762 "themes:list" => {
763 dev_log!("themes", "themes:list");
764 ThemesList(RunTime.clone()).await
765 },
766 "themes:set" => {
767 dev_log!("themes", "themes:set");
768 ThemesSet(RunTime.clone(), Arguments).await
769 },
770
771 "search:findInFiles" | "search:textSearch" | "search:searchText" => {
775 dev_log!("search", "{}", command);
776 SearchFindInFiles(RunTime.clone(), Arguments).await
777 },
778 "search:findFiles" | "search:fileSearch" | "search:searchFile" => {
779 dev_log!("search", "{}", command);
780 SearchFindFiles(RunTime.clone(), Arguments).await
781 },
782 "search:cancel" | "search:clearCache" | "search:onDidChangeResult" => {
787 dev_log!("search", "{} (stub-ack)", command);
788 Ok(Value::Null)
789 },
790
791 "decorations:get" => {
793 dev_log!("decorations", "decorations:get");
794 DecorationsGet(RunTime.clone(), Arguments).await
795 },
796 "decorations:getMany" => {
797 dev_log!("decorations", "decorations:getMany");
798 DecorationsGetMany(RunTime.clone(), Arguments).await
799 },
800 "decorations:set" => {
801 dev_log!("decorations", "decorations:set");
802 DecorationsSet(RunTime.clone(), Arguments).await
803 },
804 "decorations:clear" => {
805 dev_log!("decorations", "decorations:clear");
806 DecorationsClear(RunTime.clone(), Arguments).await
807 },
808
809 "workingCopy:isDirty" => {
811 dev_log!("workingcopy", "workingCopy:isDirty");
812 WorkingCopyIsDirty(RunTime.clone(), Arguments).await
813 },
814 "workingCopy:setDirty" => {
815 dev_log!("workingcopy", "workingCopy:setDirty");
816 WorkingCopySetDirty(RunTime.clone(), Arguments).await
817 },
818 "workingCopy:getAllDirty" => {
819 dev_log!("workingcopy", "workingCopy:getAllDirty");
820 WorkingCopyGetAllDirty(RunTime.clone()).await
821 },
822 "workingCopy:getDirtyCount" => {
823 dev_log!("workingcopy", "workingCopy:getDirtyCount");
824 WorkingCopyGetDirtyCount(RunTime.clone()).await
825 },
826
827 "keybinding:add" => {
829 dev_log!("keybinding", "keybinding:add");
830 KeybindingAdd(RunTime.clone(), Arguments).await
831 },
832 "keybinding:remove" => {
833 dev_log!("keybinding", "keybinding:remove");
834 KeybindingRemove(RunTime.clone(), Arguments).await
835 },
836 "keybinding:lookup" => {
837 dev_log!("keybinding", "keybinding:lookup");
838 KeybindingLookup(RunTime.clone(), Arguments).await
839 },
840 "keybinding:getAll" => {
841 dev_log!("keybinding", "keybinding:getAll");
842 KeybindingGetAll(RunTime.clone()).await
843 },
844
845 "lifecycle:getPhase" => {
847 dev_log!("lifecycle", "lifecycle:getPhase");
848 LifecycleGetPhase(RunTime.clone()).await
849 },
850 "lifecycle:whenPhase" => {
851 dev_log!("lifecycle", "lifecycle:whenPhase");
852 LifecycleWhenPhase(RunTime.clone(), Arguments).await
853 },
854 "lifecycle:requestShutdown" => {
855 dev_log!("lifecycle", "lifecycle:requestShutdown");
856 LifecycleRequestShutdown(ApplicationHandle.clone()).await
857 },
858 "lifecycle:advancePhase" | "lifecycle:setPhase" => {
859 dev_log!("lifecycle", "{}", command);
860 let NewPhase = Arguments.first().and_then(|V| V.as_u64()).unwrap_or(1) as u8;
865 RunTime
866 .Environment
867 .ApplicationState
868 .Feature
869 .Lifecycle
870 .AdvanceAndBroadcast(NewPhase, &ApplicationHandle);
871
872 if NewPhase >= 3 {
886 if let Some(MainWindow) = ApplicationHandle.get_webview_window("main") {
887 if let Ok(false) = MainWindow.is_visible() {
888 if let Err(Error) = MainWindow.show() {
889 dev_log!(
890 "lifecycle",
891 "warn: [Lifecycle] main window show() failed on phase {}: {}",
892 NewPhase,
893 Error
894 );
895 } else {
896 dev_log!(
897 "lifecycle",
898 "[Lifecycle] main window revealed on phase {} (hidden-until-ready)",
899 NewPhase
900 );
901 let _ = MainWindow.set_focus();
902 }
903 }
904 }
905 }
906
907 Ok(json!(RunTime.Environment.ApplicationState.Feature.Lifecycle.GetPhase()))
908 },
909
910 "label:getUri" => {
912 dev_log!("label", "label:getUri");
913 LabelGetURI(RunTime.clone(), Arguments).await
914 },
915 "label:getWorkspace" => {
916 dev_log!("label", "label:getWorkspace");
917 LabelGetWorkspace(RunTime.clone()).await
918 },
919 "label:getBase" => {
920 dev_log!("label", "label:getBase");
921 LabelGetBase(Arguments).await
922 },
923
924 "model:open" => {
926 dev_log!("model", "model:open");
927 ModelOpen(RunTime.clone(), Arguments).await
928 },
929 "model:close" => {
930 dev_log!("model", "model:close");
931 ModelClose(RunTime.clone(), Arguments).await
932 },
933 "model:get" => {
934 dev_log!("model", "model:get");
935 ModelGet(RunTime.clone(), Arguments).await
936 },
937 "model:getAll" => {
938 dev_log!("model", "model:getAll");
939 ModelGetAll(RunTime.clone()).await
940 },
941 "model:updateContent" => {
942 dev_log!("model", "model:updateContent");
943 ModelUpdateContent(RunTime.clone(), Arguments).await
944 },
945
946 "history:goBack" => {
948 dev_log!("history", "history:goBack");
949 HistoryGoBack(RunTime.clone()).await
950 },
951 "history:goForward" => {
952 dev_log!("history", "history:goForward");
953 HistoryGoForward(RunTime.clone()).await
954 },
955 "history:canGoBack" => {
956 dev_log!("history", "history:canGoBack");
957 HistoryCanGoBack(RunTime.clone()).await
958 },
959 "history:canGoForward" => {
960 dev_log!("history", "history:canGoForward");
961 HistoryCanGoForward(RunTime.clone()).await
962 },
963 "history:push" => {
964 dev_log!("history", "history:push");
965 HistoryPush(RunTime.clone(), Arguments).await
966 },
967 "history:clear" => {
968 dev_log!("history", "history:clear");
969 HistoryClear(RunTime.clone()).await
970 },
971 "history:getStack" => {
972 dev_log!("history", "history:getStack");
973 HistoryGetStack(RunTime.clone()).await
974 },
975
976 "mountain_get_status" => {
978 let status = json!({
979 "connected": true,
980 "version": "1.0.0"
981 });
982 Ok(status)
983 },
984 "mountain_get_configuration" => {
985 let config = json!({
986 "editor": { "theme": "dark" },
987 "extensions": { "installed": [] }
988 });
989 Ok(config)
990 },
991 "mountain_get_services_status" => {
992 let services = json!({
993 "editor": { "status": "running" },
994 "extensionHost": { "status": "running" }
995 });
996 Ok(services)
997 },
998 "mountain_get_state" => {
999 let state = json!({
1000 "ui": {},
1001 "editor": {},
1002 "workspace": {}
1003 });
1004 Ok(state)
1005 },
1006
1007 "file:realpath" => FileRealpath(Arguments).await,
1013 "file:open" => {
1014 dev_log!("vfs", "file:open stub - no fd support yet");
1015 Ok(json!(0))
1016 },
1017 "file:close" => {
1018 dev_log!("vfs", "file:close stub");
1019 Ok(Value::Null)
1020 },
1021 "file:cloneFile" => FileCloneNative(Arguments).await,
1022
1023 "nativeHost:pickFolderAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1029 "nativeHost:pickFileAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1030 "nativeHost:pickFileFolderAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1031 "nativeHost:pickWorkspaceAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1032 "nativeHost:showOpenDialog" => NativeShowOpenDialog(ApplicationHandle.clone(), Arguments).await,
1033 "nativeHost:showSaveDialog" => {
1034 use tauri_plugin_dialog::DialogExt;
1035 let Options = Arguments.first().cloned().unwrap_or(Value::Null);
1036 let Title = Options.get("title").and_then(Value::as_str).unwrap_or("Save").to_string();
1037 let DefaultPath = Options.get("defaultPath").and_then(Value::as_str).map(str::to_string);
1038 let Handle = ApplicationHandle.clone();
1039 let Joined = tokio::task::spawn_blocking(move || -> Option<String> {
1040 let mut Builder = Handle.dialog().file().set_title(&Title);
1041 if let Some(Path) = DefaultPath.as_deref() {
1042 Builder = Builder.set_directory(Path);
1043 }
1044 Builder.blocking_save_file().map(|P| P.to_string())
1045 })
1046 .await;
1047 match Joined {
1048 Ok(Some(Path)) => Ok(json!({ "canceled": false, "filePath": Path })),
1049 Ok(None) => Ok(json!({ "canceled": true })),
1050 Err(Error) => Err(format!("showSaveDialog join error: {}", Error)),
1051 }
1052 },
1053 "nativeHost:showMessageBox" => {
1054 use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
1055 let Options = Arguments.first().cloned().unwrap_or(Value::Null);
1056 let Message = Options.get("message").and_then(Value::as_str).unwrap_or("").to_string();
1057 let Detail = Options.get("detail").and_then(Value::as_str).map(str::to_string);
1058 let DialogType = Options
1059 .get("type")
1060 .and_then(Value::as_str)
1061 .map(|S| S.to_lowercase())
1062 .unwrap_or_default();
1063 let Title = Options.get("title").and_then(Value::as_str).unwrap_or("").to_string();
1064 let Kind = match DialogType.as_str() {
1065 "warning" | "warn" => MessageDialogKind::Warning,
1066 "error" => MessageDialogKind::Error,
1067 _ => MessageDialogKind::Info,
1068 };
1069 let Handle = ApplicationHandle.clone();
1070 let Joined = tokio::task::spawn_blocking(move || -> bool {
1071 let mut Builder = Handle.dialog().message(&Message).kind(Kind);
1072 if !Title.is_empty() {
1073 Builder = Builder.title(&Title);
1074 }
1075 if let Some(DetailText) = Detail.as_deref() {
1076 Builder = Builder.title(DetailText);
1077 }
1078 Builder.blocking_show()
1079 })
1080 .await;
1081 match Joined {
1082 Ok(Answered) => Ok(json!({ "response": if Answered { 0 } else { 1 } })),
1083 Err(Error) => Err(format!("showMessageBox join error: {}", Error)),
1084 }
1085 },
1086
1087 "nativeHost:getEnvironmentPaths" => {
1091 let PathResolver = ApplicationHandle.path();
1092 let AppDataDir = PathResolver.app_data_dir().unwrap_or_default();
1093 let HomeDir = PathResolver.home_dir().unwrap_or_default();
1094 let TmpDir = std::env::temp_dir();
1095
1096 let SessionLogRoot = AppDataDir.join("logs").join(crate::IPC::DevLog::SessionTimestamp());
1104 let SessionLogWindowDir = SessionLogRoot.join("window1");
1105 let _ = std::fs::create_dir_all(&SessionLogWindowDir);
1106
1107 dev_log!(
1108 "config",
1109 "getEnvironmentPaths: userDataDir={} logsPath={} homeDir={}",
1110 AppDataDir.display(),
1111 SessionLogRoot.display(),
1112 HomeDir.display()
1113 );
1114 let DevLogEnv = std::env::var("Trace").unwrap_or_default();
1115 Ok(json!({
1116 "userDataDir": AppDataDir.to_string_lossy(),
1117 "logsPath": SessionLogRoot.to_string_lossy(),
1118 "homeDir": HomeDir.to_string_lossy(),
1119 "tmpDir": TmpDir.to_string_lossy(),
1120 "devLog": if DevLogEnv.is_empty() { Value::Null } else { json!(DevLogEnv) },
1121 }))
1122 },
1123
1124 "nativeHost:getOSColorScheme" => {
1126 dev_log!("nativehost", "nativeHost:getOSColorScheme");
1127 NativeGetColorScheme().await
1128 },
1129 "nativeHost:getOSProperties" => {
1130 dev_log!("nativehost", "nativeHost:getOSProperties");
1131 NativeOSProperties().await
1132 },
1133 "nativeHost:getOSStatistics" => {
1134 dev_log!("nativehost", "nativeHost:getOSStatistics");
1135 NativeOSStatistics().await
1136 },
1137 "nativeHost:getOSVirtualMachineHint" => {
1138 dev_log!("nativehost", "nativeHost:getOSVirtualMachineHint");
1139 Ok(json!(0))
1140 },
1141
1142 "nativeHost:isWindowAlwaysOnTop" => {
1144 dev_log!("window", "nativeHost:isWindowAlwaysOnTop");
1145 Ok(json!(false))
1146 },
1147 "nativeHost:isFullScreen" => {
1148 dev_log!("window", "nativeHost:isFullScreen");
1149 NativeIsFullscreen(ApplicationHandle.clone()).await
1150 },
1151 "nativeHost:isMaximized" => {
1152 dev_log!("window", "nativeHost:isMaximized");
1153 NativeIsMaximized(ApplicationHandle.clone()).await
1154 },
1155 "nativeHost:getActiveWindowId" => {
1156 dev_log!("window", "nativeHost:getActiveWindowId");
1157 Ok(json!(1))
1158 },
1159 "nativeHost:getCursorScreenPoint" => {
1174 dev_log!("window", "nativeHost:getCursorScreenPoint");
1175 Ok(json!({ "x": 0, "y": 0 }))
1176 },
1177 "nativeHost:getWindows" => Ok(json!([{ "id": 1, "title": "Land", "filename": "" }])),
1178 "nativeHost:getWindowCount" => Ok(json!(1)),
1179
1180 "nativeHost:openAgentsWindow" | "nativeHost:openDevToolsWindow" | "nativeHost:openAuxiliaryWindow" => {
1190 dev_log!("window", "{} (acknowledged, no-op - aux window unsupported)", command);
1191 Ok(Value::Null)
1192 },
1193
1194 "nativeHost:focusWindow" => {
1198 dev_log!("window", "{}", command);
1199 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1200 let _ = Window.set_focus();
1201 }
1202 Ok(Value::Null)
1203 },
1204 "nativeHost:maximizeWindow" => {
1205 dev_log!("window", "{}", command);
1206 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1207 let _ = Window.maximize();
1208 }
1209 Ok(Value::Null)
1210 },
1211 "nativeHost:unmaximizeWindow" => {
1212 dev_log!("window", "{}", command);
1213 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1214 let _ = Window.unmaximize();
1215 }
1216 Ok(Value::Null)
1217 },
1218 "nativeHost:minimizeWindow" => {
1219 dev_log!("window", "{}", command);
1220 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1221 let _ = Window.minimize();
1222 }
1223 Ok(Value::Null)
1224 },
1225 "nativeHost:toggleFullScreen" => {
1226 dev_log!("window", "{}", command);
1227 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1228 let IsFullscreen = Window.is_fullscreen().unwrap_or(false);
1229 let _ = Window.set_fullscreen(!IsFullscreen);
1230 }
1231 Ok(Value::Null)
1232 },
1233 "nativeHost:closeWindow" => {
1234 dev_log!("window", "{}", command);
1235 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1241 let _ = Window.destroy();
1242 }
1243 Ok(Value::Null)
1244 },
1245 "nativeHost:setWindowAlwaysOnTop" => {
1246 dev_log!("window", "{}", command);
1247 let OnTop = Arguments.first().and_then(|V| V.as_bool()).unwrap_or(false);
1248 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1249 let _ = Window.set_always_on_top(OnTop);
1250 }
1251 Ok(Value::Null)
1252 },
1253 "nativeHost:toggleWindowAlwaysOnTop" => {
1254 dev_log!("window", "{}", command);
1255 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1261 let _ = Window.set_always_on_top(true);
1262 }
1263 Ok(Value::Null)
1264 },
1265 "nativeHost:setRepresentedFilename" => {
1266 dev_log!("window", "{}", command);
1267 #[cfg(target_os = "macos")]
1268 {
1269 let Path = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1270 if !Path.is_empty() {
1271 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1272 let _ = Window.set_title(&Path);
1273 }
1274 }
1275 }
1276 let _ = (&Arguments, &ApplicationHandle);
1277 Ok(Value::Null)
1278 },
1279
1280 "nativeHost:updateWindowControls"
1285 | "nativeHost:setMinimumSize"
1286 | "nativeHost:notifyReady"
1287 | "nativeHost:saveWindowSplash"
1288 | "nativeHost:updateTouchBar"
1289 | "nativeHost:moveWindowTop"
1290 | "nativeHost:positionWindow"
1291 | "nativeHost:setDocumentEdited"
1292 | "nativeHost:setBackgroundThrottling"
1293 | "nativeHost:updateWindowAccentColor" => {
1294 dev_log!("window", "{}", command);
1295 Ok(Value::Null)
1296 },
1297
1298 "nativeHost:isAdmin" => Ok(json!(false)),
1300 "nativeHost:isRunningUnderARM64Translation" => {
1301 #[cfg(target_os = "macos")]
1302 {
1303 let Output = std::process::Command::new("sysctl")
1305 .args(["-n", "sysctl.proc_translated"])
1306 .output();
1307 let IsTranslated = Output
1308 .ok()
1309 .map(|O| String::from_utf8_lossy(&O.stdout).trim() == "1")
1310 .unwrap_or(false);
1311 Ok(json!(IsTranslated))
1312 }
1313 #[cfg(not(target_os = "macos"))]
1314 {
1315 Ok(json!(false))
1316 }
1317 },
1318 "nativeHost:hasWSLFeatureInstalled" => {
1319 #[cfg(target_os = "windows")]
1320 {
1321 Ok(json!(std::path::Path::new("C:\\Windows\\System32\\wsl.exe").exists()))
1322 }
1323 #[cfg(not(target_os = "windows"))]
1324 {
1325 Ok(json!(false))
1326 }
1327 },
1328 "nativeHost:showItemInFolder" => ShowItemInFolder(RunTime.clone(), Arguments).await,
1329 "nativeHost:openExternal" => OpenExternal(RunTime.clone(), Arguments).await,
1330 "nativeHost:moveItemToTrash" => {
1336 let Path = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1337 if Path.is_empty() {
1338 Ok(json!(false))
1339 } else {
1340 dev_log!("nativehost", "nativeHost:moveItemToTrash path={}", Path);
1341 let Moved = {
1342 #[cfg(target_os = "macos")]
1343 {
1344 tokio::process::Command::new("osascript")
1345 .args([
1346 "-e",
1347 &format!(
1348 "tell application \"Finder\" to delete POSIX file \"{}\"",
1349 Path.replace('"', "\\\"")
1350 ),
1351 ])
1352 .status()
1353 .await
1354 .map(|S| S.success())
1355 .unwrap_or(false)
1356 }
1357 #[cfg(target_os = "linux")]
1358 {
1359 let Gio = tokio::process::Command::new("gio")
1360 .args(["trash", &Path])
1361 .status()
1362 .await
1363 .map(|S| S.success())
1364 .unwrap_or(false);
1365 if Gio {
1366 true
1367 } else {
1368 tokio::process::Command::new("trash")
1369 .arg(&Path)
1370 .status()
1371 .await
1372 .map(|S| S.success())
1373 .unwrap_or(false)
1374 }
1375 }
1376 #[cfg(target_os = "windows")]
1377 {
1378 let Script = format!(
1379 "(new-object -comobject Shell.Application).NameSpace(0xA).MoveHere('{}')",
1380 Path.replace('\'', "''")
1381 );
1382 tokio::process::Command::new("powershell.exe")
1383 .args(["-NoProfile", "-Command", &Script])
1384 .status()
1385 .await
1386 .map(|S| S.success())
1387 .unwrap_or(false)
1388 }
1389 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
1390 {
1391 false
1392 }
1393 };
1394 Ok(json!(Moved))
1395 }
1396 },
1397
1398 "nativeHost:readClipboardText" => {
1403 dev_log!("clipboard", "readClipboardText");
1404 match arboard::Clipboard::new() {
1405 Ok(mut Cb) => Ok(json!(Cb.get_text().unwrap_or_default())),
1406 Err(_) => Ok(json!("")),
1407 }
1408 },
1409 "nativeHost:writeClipboardText" => {
1410 dev_log!("clipboard", "writeClipboardText");
1411 let Text = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1412 if let Ok(mut Cb) = arboard::Clipboard::new() {
1413 let _ = Cb.set_text(Text);
1414 }
1415 Ok(Value::Null)
1416 },
1417 "nativeHost:readClipboardFindText" => {
1418 dev_log!("clipboard", "readClipboardFindText");
1419 match arboard::Clipboard::new() {
1422 Ok(mut Cb) => Ok(json!(Cb.get_text().unwrap_or_default())),
1423 Err(_) => Ok(json!("")),
1424 }
1425 },
1426 "nativeHost:writeClipboardFindText" => {
1427 dev_log!("clipboard", "writeClipboardFindText");
1428 let Text = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1429 if let Ok(mut Cb) = arboard::Clipboard::new() {
1430 let _ = Cb.set_text(Text);
1431 }
1432 Ok(Value::Null)
1433 },
1434 "nativeHost:readClipboardBuffer" => {
1435 dev_log!("clipboard", "readClipboardBuffer");
1436 Ok(json!([]))
1437 },
1438 "nativeHost:writeClipboardBuffer" => {
1439 dev_log!("clipboard", "writeClipboardBuffer");
1440 Ok(Value::Null)
1441 },
1442 "nativeHost:hasClipboard" => {
1443 dev_log!("clipboard", "hasClipboard");
1444 Ok(json!(false))
1445 },
1446 "nativeHost:readImage" => {
1447 dev_log!("clipboard", "readImage");
1448 Ok(json!([]))
1449 },
1450 "nativeHost:triggerPaste" => {
1451 dev_log!("clipboard", "triggerPaste");
1452 Ok(Value::Null)
1453 },
1454
1455 "nativeHost:getProcessId" => Ok(json!(std::process::id())),
1457 "nativeHost:killProcess" => Ok(Value::Null),
1458
1459 "nativeHost:findFreePort" => NativeFindFreePort(Arguments).await,
1461 "nativeHost:isPortFree" => Ok(json!(true)),
1462 "nativeHost:resolveProxy" => Ok(Value::Null),
1463 "nativeHost:lookupAuthorization" => Ok(Value::Null),
1464 "nativeHost:lookupKerberosAuthorization" => Ok(Value::Null),
1465 "nativeHost:loadCertificates" => Ok(json!([])),
1466
1467 "nativeHost:relaunch" => Ok(Value::Null),
1469 "nativeHost:reload" => Ok(Value::Null),
1470 "nativeHost:quit" => Ok(Value::Null),
1471 "nativeHost:exit" => Ok(Value::Null),
1472
1473 "nativeHost:openDevTools" => Ok(Value::Null),
1475 "nativeHost:toggleDevTools" => Ok(Value::Null),
1476
1477 "nativeHost:getSystemIdleState" => Ok(json!("active")),
1479 "nativeHost:getSystemIdleTime" => Ok(json!(0)),
1480 "nativeHost:getCurrentThermalState" => Ok(json!("nominal")),
1481 "nativeHost:isOnBatteryPower" => Ok(json!(false)),
1482 "nativeHost:startPowerSaveBlocker" => Ok(json!(0)),
1483 "nativeHost:stopPowerSaveBlocker" => Ok(json!(false)),
1484 "nativeHost:isPowerSaveBlockerStarted" => Ok(json!(false)),
1485
1486 "nativeHost:newWindowTab" => Ok(Value::Null),
1488 "nativeHost:showPreviousWindowTab" => Ok(Value::Null),
1489 "nativeHost:showNextWindowTab" => Ok(Value::Null),
1490 "nativeHost:moveWindowTabToNewWindow" => Ok(Value::Null),
1491 "nativeHost:mergeAllWindowTabs" => Ok(Value::Null),
1492 "nativeHost:toggleWindowTabsBar" => Ok(Value::Null),
1493 "nativeHost:installShellCommand" => Ok(Value::Null),
1494 "nativeHost:uninstallShellCommand" => Ok(Value::Null),
1495
1496 "localPty:getProfiles" => {
1500 dev_log!("terminal", "localPty:getProfiles");
1501 LocalPTYGetProfiles().await
1502 },
1503 "localPty:getDefaultSystemShell" => {
1504 dev_log!("terminal", "localPty:getDefaultSystemShell");
1505 LocalPTYGetDefaultShell().await
1506 },
1507 "localPty:getTerminalLayoutInfo" => {
1508 dev_log!("terminal", "localPty:getTerminalLayoutInfo");
1509 Ok(Value::Null)
1510 },
1511 "localPty:setTerminalLayoutInfo" => {
1512 dev_log!("terminal", "localPty:setTerminalLayoutInfo");
1513 Ok(Value::Null)
1514 },
1515 "localPty:getPerformanceMarks" => {
1516 dev_log!("terminal", "localPty:getPerformanceMarks");
1517 Ok(json!([]))
1518 },
1519 "localPty:reduceConnectionGraceTime" => {
1520 dev_log!("terminal", "localPty:reduceConnectionGraceTime");
1521 Ok(Value::Null)
1522 },
1523 "localPty:listProcesses" => {
1524 dev_log!("terminal", "localPty:listProcesses");
1525 Ok(json!([]))
1526 },
1527 "localPty:getEnvironment" => {
1528 dev_log!("terminal", "localPty:getEnvironment");
1529 LocalPTYGetEnvironment().await
1530 },
1531 "localPty:getLatency" => {
1543 dev_log!("terminal", "localPty:getLatency");
1544 Ok(json!([]))
1545 },
1546
1547 "cocoon:request" => {
1558 dev_log!("ipc", "cocoon:request method={:?}", Arguments.first());
1559 let MethodOpt = Arguments.first().and_then(|V| V.as_str()).map(|S| S.to_string());
1560 match MethodOpt {
1561 None => Err("cocoon:request requires method string in slot 0".to_string()),
1562 Some(Method) => {
1563 let Payload = Arguments.get(1).cloned().unwrap_or(Value::Null);
1564 let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 1500).await;
1571 crate::Vine::Client::SendRequest::Fn("cocoon-main", Method.clone(), Payload, 30_000)
1572 .await
1573 .map_err(|Error| format!("cocoon:request {} failed: {:?}", Method, Error))
1574 },
1575 }
1576 },
1577
1578 "cocoon:notify" => {
1588 dev_log!("ipc", "cocoon:notify method={:?}", Arguments.first());
1589 let MethodOpt = Arguments.first().and_then(|V| V.as_str()).map(|S| S.to_string());
1590 match MethodOpt {
1591 None => Err("cocoon:notify requires method string in slot 0".to_string()),
1592 Some(Method) => {
1593 let Payload = Arguments.get(1).cloned().unwrap_or(Value::Null);
1594 if let Err(Error) = crate::Vine::Client::SendNotification::Fn(
1595 "cocoon-main".to_string(),
1596 Method.clone(),
1597 Payload,
1598 )
1599 .await
1600 {
1601 dev_log!("ipc", "warn: [cocoon:notify] {} failed: {:?}", Method, Error);
1602 }
1603 Ok(Value::Null)
1604 },
1605 }
1606 },
1607
1608 "localPty:spawn" => {
1625 dev_log!("terminal", "{}", command);
1630 TerminalCreate(RunTime.clone(), Arguments).await
1631 },
1632 "localPty:createProcess" => {
1633 dev_log!("terminal", "{}", command);
1634 match TerminalCreate(RunTime.clone(), Arguments).await {
1635 Ok(Response) => {
1636 let TerminalIdOption = Response.get("id").and_then(serde_json::Value::as_u64);
1640 match TerminalIdOption {
1641 Some(TerminalId) if TerminalId > 0 => Ok(serde_json::json!(TerminalId)),
1642 Some(_) | None => {
1643 dev_log!(
1654 "terminal",
1655 "error: [localPty:createProcess] CreateTerminal returned no usable id; \
1656 response={:?}",
1657 Response
1658 );
1659 Err(format!(
1660 "localPty:createProcess: CreateTerminal returned no terminal id (response={})",
1661 Response
1662 ))
1663 },
1664 }
1665 },
1666 Err(Error) => Err(Error),
1667 }
1668 },
1669 "localPty:start" => {
1670 dev_log!("terminal", "{} no-op (eager-spawn)", command);
1683 Ok(Value::Null)
1684 },
1685 "localPty:input" | "localPty:write" => {
1686 dev_log!("terminal", "{}", command);
1687 TerminalSendText(RunTime.clone(), Arguments).await
1688 },
1689 "localPty:shutdown" | "localPty:dispose" => {
1690 dev_log!("terminal", "{}", command);
1691 TerminalDispose(RunTime.clone(), Arguments).await
1692 },
1693 "localPty:resize" => {
1694 dev_log!("terminal", "localPty:resize");
1695 let (TerminalId, Columns, Rows) = {
1707 let First = Arguments.first().cloned().unwrap_or(Value::Null);
1708 if First.is_object() {
1709 let Id = First.get("id").and_then(|V| V.as_u64()).unwrap_or(0);
1710 let C = First.get("cols").and_then(|V| V.as_u64()).unwrap_or(80) as u16;
1711 let R = First.get("rows").and_then(|V| V.as_u64()).unwrap_or(24) as u16;
1712 (Id, C, R)
1713 } else {
1714 let Id = Arguments.get(0).and_then(|V| V.as_u64()).unwrap_or(0);
1715 let C = Arguments.get(1).and_then(|V| V.as_u64()).unwrap_or(80) as u16;
1716 let R = Arguments.get(2).and_then(|V| V.as_u64()).unwrap_or(24) as u16;
1717 (Id, C, R)
1718 }
1719 };
1720 if TerminalId == 0 {
1721 Ok(Value::Null)
1722 } else {
1723 let Columns = if Columns == 0 { 1 } else { Columns };
1724 let Rows = if Rows == 0 { 1 } else { Rows };
1725 use CommonLibrary::{
1726 Environment::Requires::Requires,
1727 Terminal::TerminalProvider::TerminalProvider,
1728 };
1729 let Provider:Arc<dyn TerminalProvider> = RunTime.Environment.Require();
1730 match Provider.ResizeTerminal(TerminalId, Columns, Rows).await {
1731 Ok(_) => Ok(Value::Null),
1732 Err(Error) => {
1733 dev_log!(
1743 "terminal",
1744 "warn: localPty:resize id={} cols={} rows={} failed: {}",
1745 TerminalId,
1746 Columns,
1747 Rows,
1748 Error
1749 );
1750 Ok(Value::Null)
1751 },
1752 }
1753 }
1754 },
1755 "localPty:acknowledgeDataEvent" => {
1756 Ok(Value::Null)
1758 },
1759 "localPty:processBinary"
1764 | "localPty:attachToProcess"
1765 | "localPty:detachFromProcess"
1766 | "localPty:orphanQuestionReply"
1767 | "localPty:updateTitle"
1768 | "localPty:updateIcon"
1769 | "localPty:refreshProperty"
1770 | "localPty:updateProperty"
1771 | "localPty:getRevivedPtyNewId"
1772 | "localPty:freePortKillProcess"
1773 | "localPty:reviveTerminalProcesses"
1774 | "localPty:getBackendOS"
1775 | "localPty:installAutoReply"
1776 | "localPty:uninstallAllAutoReplies"
1777 | "localPty:serializeTerminalState" => Ok(Value::Null),
1778
1779 "update:_getInitialState" => {
1783 dev_log!("update", "update:_getInitialState");
1784 Ok(json!({ "type": "idle", "updateType": 0 }))
1785 },
1786 "update:isLatestVersion" => {
1787 dev_log!("update", "update:isLatestVersion");
1788 Ok(json!(true))
1789 },
1790 "update:checkForUpdates" => {
1791 dev_log!("update", "update:checkForUpdates");
1792 Ok(Value::Null)
1793 },
1794 "update:downloadUpdate" => {
1795 dev_log!("update", "update:downloadUpdate");
1796 Ok(Value::Null)
1797 },
1798 "update:applyUpdate" => {
1799 dev_log!("update", "update:applyUpdate");
1800 Ok(Value::Null)
1801 },
1802 "update:quitAndInstall" => {
1803 dev_log!("update", "update:quitAndInstall");
1804 Ok(Value::Null)
1805 },
1806
1807 "menubar:updateMenubar" => {
1819 use std::{
1820 sync::{Arc, Mutex as StandardMutex, OnceLock},
1821 time::Duration,
1822 };
1823
1824 use tokio::task::JoinHandle;
1825 type MenubarCell = StandardMutex<(Option<JoinHandle<()>>, u64)>;
1826 static MENUBAR_DEBOUNCE:OnceLock<Arc<MenubarCell>> = OnceLock::new();
1827 let Cell = MENUBAR_DEBOUNCE.get_or_init(|| Arc::new(StandardMutex::new((None, 0)))).clone();
1828
1829 if let Ok(mut Guard) = Cell.lock() {
1830 if let Some(Pending) = Guard.0.take() {
1831 Pending.abort();
1832 }
1833 Guard.1 = Guard.1.saturating_add(1);
1834 let CellForTask = Cell.clone();
1835 Guard.0 = Some(tokio::spawn(async move {
1836 tokio::time::sleep(Duration::from_millis(50)).await;
1837 let Coalesced = if let Ok(mut Post) = CellForTask.lock() {
1838 let N = Post.1;
1839 Post.1 = 0;
1840 Post.0 = None;
1841 N
1842 } else {
1843 0
1844 };
1845 dev_log!("menubar", "menubar:updateMenubar (applied, coalesced {} pending)", Coalesced);
1846 }));
1847 } else {
1848 dev_log!("menubar", "menubar:updateMenubar (debouncer lock poisoned)");
1849 }
1850 Ok(Value::Null)
1851 },
1852
1853 "url:registerExternalUriOpener" => {
1857 dev_log!("url", "url:registerExternalUriOpener");
1858 Ok(Value::Null)
1859 },
1860
1861 "encryption:encrypt" => {
1865 dev_log!("encryption", "encryption:encrypt");
1866 Ok(json!(""))
1867 },
1868 "encryption:decrypt" => {
1869 dev_log!("encryption", "encryption:decrypt");
1870 Ok(json!(""))
1871 },
1872
1873 "extensionHostStarter:createExtensionHost" => {
1877 dev_log!("exthost", "extensionHostStarter:createExtensionHost");
1878 Ok(json!({ "id": "1" }))
1879 },
1880 "extensionHostStarter:start" => {
1881 let Pid =
1888 crate::ProcessManagement::CocoonManagement::GetCocoonPid().unwrap_or_else(std::process::id);
1889 dev_log!("exthost", "extensionHostStarter:start pid={}", Pid);
1890 Ok(json!({ "pid": Pid }))
1891 },
1892 "extensionHostStarter:kill" => {
1893 dev_log!("exthost", "extensionHostStarter:kill");
1894 Ok(Value::Null)
1895 },
1896 "extensionHostStarter:getExitInfo" => {
1897 dev_log!("exthost", "extensionHostStarter:getExitInfo");
1898 Ok(json!({ "code": null, "signal": null }))
1899 },
1900
1901 "cocoon:extensionHostMessage" => {
1905 let ByteCount = Arguments
1906 .first()
1907 .map(|P| P.get("data").and_then(|D| D.as_array()).map(|A| A.len()).unwrap_or(0))
1908 .unwrap_or(0);
1909 dev_log!("exthost", "cocoon:extensionHostMessage bytes={}", ByteCount);
1910
1911 let Payload = Arguments.first().cloned().unwrap_or(Value::Null);
1914 tokio::spawn(async move {
1915 if let Err(Error) = crate::Vine::Client::SendNotification::Fn(
1916 "cocoon-main".to_string(),
1917 "extensionHostMessage".to_string(),
1918 Payload,
1919 )
1920 .await
1921 {
1922 dev_log!("exthost", "cocoon:extensionHostMessage forward failed: {}", Error);
1923 }
1924 });
1925 Ok(Value::Null)
1926 },
1927
1928 "extensionhostdebugservice:reload" => {
1932 dev_log!("exthost", "extensionhostdebugservice:reload");
1933 use tauri::Emitter;
1938 if let Err(Error) = ApplicationHandle.emit(SkyEvent::ExtHostDebugReload.AsStr(), json!({})) {
1939 dev_log!("exthost", "warn: extensionhostdebugservice:reload emit failed: {}", Error);
1940 }
1941 Ok(Value::Null)
1942 },
1943 "extensionhostdebugservice:close" => {
1944 dev_log!("exthost", "extensionhostdebugservice:close");
1945 use tauri::Emitter;
1946 if let Err(Error) = ApplicationHandle.emit("sky://exthost/debug-close", json!({})) {
1947 dev_log!("exthost", "warn: extensionhostdebugservice:close emit failed: {}", Error);
1948 }
1949 Ok(Value::Null)
1950 },
1951 "extensionhostdebugservice:attachSession" | "extensionhostdebugservice:terminateSession" => {
1952 dev_log!("exthost", "{}", command);
1953 Ok(Value::Null)
1954 },
1955
1956 "workspaces:getRecentlyOpened" => {
1960 dev_log!("workspaces", "workspaces:getRecentlyOpened");
1961 ReadRecentlyOpened()
1962 },
1963 "workspaces:removeRecentlyOpened" => {
1964 dev_log!("workspaces", "workspaces:removeRecentlyOpened");
1965 let Uri = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1966 if !Uri.is_empty() {
1967 MutateRecentlyOpened(|List| {
1968 if let Some(Workspaces) = List.get_mut("workspaces").and_then(|V| V.as_array_mut()) {
1969 Workspaces
1970 .retain(|Entry| Entry.get("uri").and_then(|V| V.as_str()).unwrap_or("") != Uri);
1971 }
1972 if let Some(Files) = List.get_mut("files").and_then(|V| V.as_array_mut()) {
1973 Files.retain(|Entry| Entry.get("uri").and_then(|V| V.as_str()).unwrap_or("") != Uri);
1974 }
1975 });
1976 }
1977 Ok(Value::Null)
1978 },
1979 "workspaces:addRecentlyOpened" => {
1980 dev_log!("workspaces", "workspaces:addRecentlyOpened");
1981 let Entries:Vec<Value> = Arguments.first().and_then(|V| V.as_array()).cloned().unwrap_or_default();
1983 if !Entries.is_empty() {
1984 MutateRecentlyOpened(|List| {
1985 let Workspaces = List
1986 .get_mut("workspaces")
1987 .and_then(|V| V.as_array_mut())
1988 .map(|V| std::mem::take(V))
1989 .unwrap_or_default();
1990 let Files = List
1991 .get_mut("files")
1992 .and_then(|V| V.as_array_mut())
1993 .map(|V| std::mem::take(V))
1994 .unwrap_or_default();
1995 let mut MergedWorkspaces = Workspaces;
1996 let mut MergedFiles = Files;
1997 for Entry in Entries {
1998 let Folder = Entry
1999 .get("folderUri")
2000 .cloned()
2001 .or_else(|| Entry.get("workspace").and_then(|W| W.get("configPath").cloned()));
2002 let File = Entry.get("fileUri").cloned();
2003 if let Some(FolderUri) = Folder.and_then(|V| v_str(&V)) {
2004 MergedWorkspaces
2005 .retain(|E| E.get("uri").and_then(|V| V.as_str()).unwrap_or("") != FolderUri);
2006 let mut Item = serde_json::Map::new();
2007 Item.insert("uri".into(), json!(FolderUri));
2008 if let Some(Label) = Entry.get("label").and_then(|V| V.as_str()) {
2009 Item.insert("label".into(), json!(Label));
2010 }
2011 MergedWorkspaces.insert(0, Value::Object(Item));
2012 }
2013 if let Some(FileUri) = File.and_then(|V| v_str(&V)) {
2014 MergedFiles
2015 .retain(|E| E.get("uri").and_then(|V| V.as_str()).unwrap_or("") != FileUri);
2016 let mut Item = serde_json::Map::new();
2017 Item.insert("uri".into(), json!(FileUri));
2018 MergedFiles.insert(0, Value::Object(Item));
2019 }
2020 }
2021 MergedWorkspaces.truncate(50);
2024 MergedFiles.truncate(50);
2025 List.insert("workspaces".into(), Value::Array(MergedWorkspaces));
2026 List.insert("files".into(), Value::Array(MergedFiles));
2027 });
2028 }
2029 Ok(Value::Null)
2030 },
2031 "workspaces:clearRecentlyOpened" => {
2032 dev_log!("workspaces", "workspaces:clearRecentlyOpened");
2033 MutateRecentlyOpened(|List| {
2034 List.insert("workspaces".into(), json!([]));
2035 List.insert("files".into(), json!([]));
2036 });
2037 Ok(Value::Null)
2038 },
2039 "workspaces:enterWorkspace" => {
2040 dev_log!("workspaces", "workspaces:enterWorkspace");
2041 Ok(Value::Null)
2042 },
2043 "workspaces:createUntitledWorkspace" => {
2044 dev_log!("workspaces", "workspaces:createUntitledWorkspace");
2045 Ok(Value::Null)
2046 },
2047 "workspaces:deleteUntitledWorkspace" => {
2048 dev_log!("workspaces", "workspaces:deleteUntitledWorkspace");
2049 Ok(Value::Null)
2050 },
2051 "workspaces:getWorkspaceIdentifier" => {
2052 let Workspace = &RunTime.Environment.ApplicationState.Workspace;
2059 let Folders = Workspace.GetWorkspaceFolders();
2060 if let Some(First) = Folders.first() {
2061 use std::{
2062 collections::hash_map::DefaultHasher,
2063 hash::{Hash, Hasher},
2064 };
2065 let mut Hasher = DefaultHasher::new();
2066 First.URI.as_str().hash(&mut Hasher);
2067 let Id = format!("{:016x}", Hasher.finish());
2068 Ok(json!({
2069 "id": Id,
2070 "configPath": Value::Null,
2071 "uri": First.URI.to_string(),
2072 }))
2073 } else {
2074 Ok(Value::Null)
2075 }
2076 },
2077 "workspaces:getDirtyWorkspaces" => Ok(json!([])),
2078
2079 "git:exec" => {
2084 dev_log!("git", "git:exec");
2085 Git::HandleExec(Arguments).await
2086 },
2087 "git:clone" => {
2088 dev_log!("git", "git:clone");
2089 Git::HandleClone(Arguments).await
2090 },
2091 "git:pull" => {
2092 dev_log!("git", "git:pull");
2093 Git::HandlePull(Arguments).await
2094 },
2095 "git:checkout" => {
2096 dev_log!("git", "git:checkout");
2097 Git::HandleCheckout(Arguments).await
2098 },
2099 "git:revParse" => {
2100 dev_log!("git", "git:revParse");
2101 Git::HandleRevParse(Arguments).await
2102 },
2103 "git:fetch" => {
2104 dev_log!("git", "git:fetch");
2105 Git::HandleFetch(Arguments).await
2106 },
2107 "git:revListCount" => {
2108 dev_log!("git", "git:revListCount");
2109 Git::HandleRevListCount(Arguments).await
2110 },
2111 "git:cancel" => {
2112 dev_log!("git", "git:cancel");
2113 Git::HandleCancel(Arguments).await
2114 },
2115 "git:isAvailable" => {
2116 dev_log!("git", "git:isAvailable");
2117 Git::HandleIsAvailable(Arguments).await
2118 },
2119
2120 "tree:getChildren" => {
2127 let ViewId = Arguments
2128 .first()
2129 .and_then(|V| V.get("viewId").or_else(|| V.get(0)))
2130 .and_then(Value::as_str)
2131 .unwrap_or("")
2132 .to_string();
2133 let ItemHandle = Arguments
2134 .first()
2135 .and_then(|V| V.get("treeItemHandle").or_else(|| V.get(1)))
2136 .and_then(Value::as_str)
2137 .unwrap_or("")
2138 .to_string();
2139 dev_log!(
2140 "tree-view",
2141 "[TreeView] invoke:getChildren view={} parent={}",
2142 ViewId,
2143 ItemHandle
2144 );
2145 if ViewId.is_empty() {
2146 Err("tree:getChildren requires viewId".to_string())
2147 } else {
2148 let Parameters = json!({
2149 "viewId": ViewId,
2150 "treeItemHandle": ItemHandle,
2151 });
2152 let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 1500).await;
2164 match crate::Vine::Client::SendRequest::Fn(
2175 "cocoon-main",
2176 "$provideTreeChildren".to_string(),
2177 Parameters,
2178 1500,
2179 )
2180 .await
2181 {
2182 Ok(Value_) => {
2193 match &Value_ {
2194 Value::Object(_) | Value::Array(_) => Ok(Value_),
2195 _ => Ok(json!({ "items": [] })),
2196 }
2197 },
2198 Err(Error) => {
2199 crate::IPC::DevLog::DebugOnce(
2214 "tree-view",
2215 &format!("get-children-error:{}", ViewId),
2216 &format!(
2217 "[TreeView] invoke:getChildren error view={} err={:?} (further occurrences \
2218 silenced)",
2219 ViewId, Error
2220 ),
2221 );
2222 Ok(json!({ "items": [] }))
2223 },
2224 }
2225 }
2226 },
2227
2228 "sky:replay-events" => {
2240 use tauri::Emitter;
2241 let mut TreeViewCount:usize = 0;
2242 let mut ScmCount:usize = 0;
2243 let mut CommandCount:usize = 0;
2244 let mut TerminalCount:usize = 0;
2245 let mut TerminalDataBytes:usize = 0;
2246 if let Ok(TreeViews) = RunTime.Environment.ApplicationState.Feature.TreeViews.ActiveTreeViews.lock()
2247 {
2248 for (ViewId, Dto) in TreeViews.iter() {
2249 let Payload = serde_json::json!({
2250 "viewId": ViewId,
2251 "options": {
2252 "canSelectMany": Dto.CanSelectMany,
2253 "showCollapseAll": Dto.HasHandleDrag,
2254 "title": Dto.Title.clone().unwrap_or_default(),
2255 },
2256 });
2257 if ApplicationHandle.emit("sky://tree-view/create", Payload).is_ok() {
2258 TreeViewCount += 1;
2259 }
2260 }
2261 }
2262 if let Ok(ScmProviders) = RunTime
2272 .Environment
2273 .ApplicationState
2274 .Feature
2275 .Markers
2276 .SourceControlManagementProviders
2277 .lock()
2278 {
2279 for (Handle, Dto) in ScmProviders.iter() {
2280 let RootUriStr = Dto
2281 .RootURI
2282 .as_ref()
2283 .and_then(|V| V.get("external").or_else(|| V.get("path")))
2284 .and_then(serde_json::Value::as_str)
2285 .unwrap_or("")
2286 .to_string();
2287 let ScmId = if Dto.Identifier.is_empty() {
2288 "git".to_string()
2289 } else {
2290 Dto.Identifier.clone()
2291 };
2292 let Payload = serde_json::json!({
2293 "scmId": ScmId,
2294 "label": Dto.Label,
2295 "rootUri": RootUriStr,
2296 "extensionId": "",
2297 "handle": *Handle,
2298 });
2299 if ApplicationHandle.emit("sky://scm/register", Payload).is_ok() {
2300 ScmCount += 1;
2301 }
2302 }
2303 }
2304 if let Ok(Commands) = RunTime.Environment.ApplicationState.Extension.Registry.CommandRegistry.lock()
2320 {
2321 let mut Batch:Vec<serde_json::Value> = Vec::new();
2322 for (CommandId, Handler) in Commands.iter() {
2323 use crate::Environment::CommandProvider::CommandHandler;
2324 let Kind = match Handler {
2325 CommandHandler::Native(_) => continue,
2326 CommandHandler::Proxied { .. } => "extension",
2327 };
2328 Batch.push(serde_json::json!({
2329 "id": CommandId,
2330 "commandId": CommandId,
2331 "kind": Kind,
2332 }));
2333 }
2334 if !Batch.is_empty() {
2335 let Count = Batch.len();
2336 if ApplicationHandle
2337 .emit("sky://command/register", serde_json::json!({ "commands": Batch }))
2338 .is_ok()
2339 {
2340 CommandCount = Count;
2341 }
2342 }
2343 }
2344 if let Ok(Terminals) = RunTime.Environment.ApplicationState.Feature.Terminals.ActiveTerminals.lock()
2352 {
2353 for (TerminalId, Arc) in Terminals.iter() {
2354 let (Name, Pid) = if let Ok(State) = Arc.lock() {
2355 (State.Name.clone(), State.OSProcessIdentifier.unwrap_or(0))
2356 } else {
2357 (String::new(), 0)
2358 };
2359 let CreatePayload = serde_json::json!({
2360 "id": *TerminalId,
2361 "name": Name,
2362 "pid": Pid,
2363 });
2364 if ApplicationHandle.emit("sky://terminal/create", CreatePayload).is_ok() {
2365 TerminalCount += 1;
2366 }
2367 }
2368 }
2369 for (TerminalId, Bytes) in crate::Environment::TerminalProvider::DrainTerminalOutputBuffer() {
2370 let DataString = String::from_utf8_lossy(&Bytes).to_string();
2371 TerminalDataBytes += Bytes.len();
2372 let _ = ApplicationHandle.emit(
2373 "sky://terminal/data",
2374 serde_json::json!({ "id": TerminalId, "data": DataString }),
2375 );
2376 }
2377 dev_log!(
2378 "sky-emit",
2379 "[SkyEmit] replay-events tree-views={} scm={} commands={} terminals={} terminal-bytes={}",
2380 TreeViewCount,
2381 ScmCount,
2382 CommandCount,
2383 TerminalCount,
2384 TerminalDataBytes
2385 );
2386 Ok(serde_json::json!({
2387 "treeViews": TreeViewCount,
2388 "scmProviders": ScmCount,
2389 "commands": CommandCount,
2390 "terminals": TerminalCount,
2391 "terminalDataBytes": TerminalDataBytes,
2392 }))
2393 },
2394
2395 _ => {
2404 use std::str::FromStr;
2405 match CommonLibrary::IPC::Channel::Channel::from_str(&command) {
2406 Ok(KnownChannel) => {
2407 dev_log!(
2408 "ipc",
2409 "error: [WindServiceHandlers] Channel {:?} is registered but has no dispatch arm",
2410 KnownChannel
2411 );
2412 Err(format!("IPC channel registered but unimplemented: {}", command))
2413 },
2414 Err(_) => {
2415 dev_log!("ipc", "error: [WindServiceHandlers] Unknown IPC command: {}", command);
2416 Err(format!("Unknown IPC command: {}", command))
2417 },
2418 }
2419 },
2420 };
2421
2422 if ResultSender.send(MatchResult).is_err() {
2423 dev_log!(
2424 "ipc",
2425 "warn: [WindServiceHandlers] IPC result receiver dropped before dispatch completed"
2426 );
2427 }
2428 },
2429 CommandPriority,
2430 );
2431
2432 let Result = match ResultReceiver.await {
2433 Ok(Dispatched) => Dispatched,
2434 Err(_) => {
2435 dev_log!(
2436 "ipc",
2437 "error: [WindServiceHandlers] IPC task cancelled before producing a result"
2438 );
2439 Err("IPC task cancelled before result was produced".to_string())
2440 },
2441 };
2442
2443 let IsErr = Result.is_err();
2445 let SpanName = if IsErr {
2446 format!("ipc:{}:error", command)
2447 } else {
2448 format!("ipc:{}", command)
2449 };
2450 crate::otel_span!(&SpanName, OTLPStart, &[("ipc.command", command.as_str())]);
2451
2452 if !IsHighFrequencyCommand {
2461 let ElapsedNanos = crate::IPC::DevLog::NowNano().saturating_sub(OTLPStart);
2462 dev_log!("ipc", "done: {} ok={} t_ns={}", command, !IsErr, ElapsedNanos);
2463 }
2464
2465 Result
2466}
2467
2468pub fn register_wind_ipc_handlers(ApplicationHandle:&tauri::AppHandle) -> Result<(), String> {
2469 dev_log!("lifecycle", "registering IPC handlers");
2470
2471 Ok(())
2475}