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::{
33 ExtensionsGet::ExtensionsGet,
34 ExtensionsGetAll::ExtensionsGetAll,
35 ExtensionsGetInstalled::ExtensionsGetInstalled,
36 ExtensionsIsActive::ExtensionsIsActive,
37};
38use FileSystem::{
39 Managed::{
40 FileCopy::*,
41 FileDelete::*,
42 FileExists::*,
43 FileMkdir::*,
44 FileMove::*,
45 FileRead::*,
46 FileReadBinary::*,
47 FileReaddir::*,
48 FileStat::*,
49 FileWrite::*,
50 FileWriteBinary::*,
51 },
52 Native::{
53 FileCloneNative::*,
54 FileDeleteNative::*,
55 FileExistsNative::*,
56 FileMkdirNative::*,
57 FileReadNative::*,
58 FileReaddirNative::*,
59 FileRealpath::*,
60 FileRenameNative::*,
61 FileStatNative::*,
62 FileWriteNative::*,
63 },
64};
65use Model::{
66 ModelClose::ModelClose,
67 ModelGet::ModelGet,
68 ModelGetAll::ModelGetAll,
69 ModelOpen::ModelOpen,
70 ModelUpdateContent::ModelUpdateContent,
71 TextfileRead::TextfileRead,
72 TextfileSave::TextfileSave,
73 TextfileWrite::TextfileWrite,
74};
75use NativeHost::{
76 FindFreePort::*,
77 GetColorScheme::*,
78 IsFullscreen::*,
79 IsMaximized::*,
80 OSProperties::*,
81 OSStatistics::*,
82 OpenExternal::*,
83 PickFolder::*,
84 ShowItemInFolder::*,
85 ShowOpenDialog::*,
86};
87use Navigation::{
88 HistoryCanGoBack::HistoryCanGoBack,
89 HistoryCanGoForward::HistoryCanGoForward,
90 HistoryClear::HistoryClear,
91 HistoryGetStack::HistoryGetStack,
92 HistoryGoBack::HistoryGoBack,
93 HistoryGoForward::HistoryGoForward,
94 HistoryPush::HistoryPush,
95 LabelGetBase::LabelGetBase,
96 LabelGetURI::LabelGetURI,
97 LabelGetWorkspace::LabelGetWorkspace,
98};
99use Output::{
100 OutputAppend::OutputAppend,
101 OutputAppendLine::OutputAppendLine,
102 OutputClear::OutputClear,
103 OutputCreate::OutputCreate,
104 OutputShow::OutputShow,
105};
106use Search::*;
107use Storage::{
108 StorageDelete::StorageDelete,
109 StorageGet::StorageGet,
110 StorageGetItems::StorageGetItems,
111 StorageKeys::StorageKeys,
112 StorageSet::StorageSet,
113 StorageUpdateItems::StorageUpdateItems,
114};
115use Terminal::{
116 LocalPTYGetDefaultShell::LocalPTYGetDefaultShell,
117 LocalPTYGetEnvironment::LocalPTYGetEnvironment,
118 LocalPTYGetProfiles::LocalPTYGetProfiles,
119 TerminalCreate::TerminalCreate,
120 TerminalDispose::TerminalDispose,
121 TerminalHide::TerminalHide,
122 TerminalSendText::TerminalSendText,
123 TerminalShow::TerminalShow,
124};
125use UI::{
126 Decoration::*,
127 Keybinding::*,
128 Lifecycle::*,
129 Notification::*,
130 Progress::*,
131 QuickInput::*,
132 Theme::*,
133 WorkingCopy::*,
134 Workspace::*,
135};
136use Utilities::{
137 ApplicationRoot::*,
138 ChannelPriority::*,
139 JsonValueHelpers::*,
140 MetadataEncoding::*,
141 PathExtraction::*,
142 RecentlyOpened::*,
143 UserdataDir::*,
144};
145use Echo::Task::Priority::Priority as EchoPriority;
146use serde_json::{Value, json};
147use tauri::{AppHandle, Manager};
148use CommonLibrary::Configuration::DTO::{
150 ConfigurationOverridesDTO as ConfigurationOverridesDTOModule,
151 ConfigurationTarget as ConfigurationTargetModule,
152};
153
154use crate::dev_log;
155type ConfigurationOverridesDTO = ConfigurationOverridesDTOModule::ConfigurationOverridesDTO;
156type ConfigurationTarget = ConfigurationTargetModule::ConfigurationTarget;
157
158use CommonLibrary::{
159 Command::CommandExecutor::CommandExecutor,
160 Configuration::ConfigurationProvider::ConfigurationProvider,
161 Environment::Requires::Requires,
162 Error::CommonError::CommonError,
163 ExtensionManagement::ExtensionManagementService::ExtensionManagementService,
164 FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
165 IPC::SkyEvent::SkyEvent,
166 Storage::StorageProvider::StorageProvider,
167};
168
169use crate::{
170 ApplicationState::{
171 DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO,
172 State::{
173 ApplicationState::ApplicationState,
174 WorkspaceState::WorkspaceDelta::UpdateWorkspaceFoldersAndBroadcast,
175 },
176 },
177 RunTime::ApplicationRunTime::ApplicationRunTime,
178};
179
180pub async fn mountain_ipc_invoke(
197 ApplicationHandle:AppHandle,
198 command:String,
199 Arguments:Vec<Value>,
200) -> Result<Value, String> {
201 let OTLPStart = crate::IPC::DevLog::NowNano::Fn();
202 let IsHighFrequencyCommand = matches!(
217 command.as_str(),
218 "logger:log"
219 | "logger:registerLogger"
220 | "logger:createLogger"
221 | "log:registerLogger"
222 | "log:createLogger"
223 | "file:stat"
224 | "file:readFile"
225 | "file:readdir"
226 | "file:writeFile"
227 | "file:delete"
228 | "file:rename"
229 | "file:realpath"
230 | "file:read"
231 | "file:write"
232 | "storage:getItems"
233 | "storage:updateItems"
234 | "configuration:lookup"
235 | "configuration:inspect"
236 | "themes:getColorTheme"
237 | "output:append"
238 | "progress:report"
239 );
240 if !IsHighFrequencyCommand {
241 dev_log!("ipc", "invoke: {} args_count={}", command, Arguments.len());
242 }
243
244 ensure_userdata_dirs();
246
247 let RunTime:Arc<ApplicationRunTime> = ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
251
252 let CommandPriority = ResolveChannelPriority(&command);
273
274 let Scheduler = RunTime.Scheduler.clone();
275
276 let (ResultSender, ResultReceiver) = tokio::sync::oneshot::channel::<Result<Value, String>>();
277
278 let DispatchAppHandle = ApplicationHandle.clone();
279
280 let DispatchRuntime = RunTime.clone();
281
282 let DispatchCommand = command.clone();
283
284 let DispatchArgs = Arguments;
285
286 Scheduler.Submit(
287 async move {
288 let ApplicationHandle = DispatchAppHandle;
289 let RunTime = DispatchRuntime;
290 let command = DispatchCommand;
291 let Arguments = DispatchArgs;
292
293 let MatchResult = match command.as_str() {
294 "configuration:get" | "configuration:getValue" => {
300 dev_log!("config", "{}", command);
301 ConfigurationGet(RunTime.clone(), Arguments).await
302 },
303 "configuration:update" | "configuration:updateValue" => {
304 dev_log!("config", "{}", command);
305 ConfigurationUpdate(RunTime.clone(), Arguments).await
306 },
307 "configuration:onDidChange" => Ok(Value::Null),
313
314 "logger:log"
316 | "logger:warn"
317 | "logger:error"
318 | "logger:info"
319 | "logger:debug"
320 | "logger:trace"
321 | "logger:critical"
322 | "logger:flush"
323 | "logger:setLevel"
324 | "logger:getLevel"
325 | "logger:createLogger"
326 | "logger:registerLogger"
327 | "logger:deregisterLogger"
328 | "logger:getRegisteredLoggers"
329 | "logger:setVisibility" => Ok(Value::Null),
330
331 "file:read" | "file:readFile" => FileReadNative(Arguments).await,
343 "file:write" | "file:writeFile" => FileWriteNative(Arguments).await,
344 "file:stat" => FileStatNative(Arguments).await,
345 "file:exists" => FileExistsNative(Arguments).await,
346 "file:delete" => FileDeleteNative(Arguments).await,
347 "file:copy" => FileCloneNative(Arguments).await,
348 "file:move" | "file:rename" => FileRenameNative(Arguments).await,
349 "file:mkdir" => FileMkdirNative(Arguments).await,
350 "file:readdir" => FileReaddirNative(Arguments).await,
351 "file:readBinary" => FileReadBinary(RunTime.clone(), Arguments).await,
352 "file:writeBinary" => FileWriteBinary(RunTime.clone(), Arguments).await,
353 "file:watch" | "file:unwatch" => {
360 dev_log!("fs-route", "{} (stub-ack)", command);
361 Ok(Value::Null)
362 },
363
364 "storage:get" => StorageGet(RunTime.clone(), Arguments).await,
371 "storage:set" => StorageSet(RunTime.clone(), Arguments).await,
372 "storage:getItems" => {
373 dev_log!("storage-verbose", "storage:getItems");
377 StorageGetItems(RunTime.clone(), Arguments).await
378 },
379 "storage:updateItems" => {
380 dev_log!("storage-verbose", "storage:updateItems");
381 StorageUpdateItems(RunTime.clone(), Arguments).await
382 },
383 "storage:optimize" => {
384 dev_log!("storage", "storage:optimize");
385 Ok(Value::Null)
386 },
387 "storage:isUsed" => {
388 dev_log!("storage", "storage:isUsed");
389 Ok(Value::Null)
390 },
391 "storage:close" => {
392 dev_log!("storage", "storage:close");
393 Ok(Value::Null)
394 },
395 "storage:onDidChangeItems" | "storage:logStorage" => {
399 dev_log!("storage-verbose", "{} (stub-ack)", command);
400 Ok(Value::Null)
401 },
402
403 "environment:get" => {
405 dev_log!("config", "environment:get");
406 EnvironmentGet(RunTime.clone(), Arguments).await
407 },
408
409 "native:showItemInFolder" => ShowItemInFolder(RunTime.clone(), Arguments).await,
411 "native:openExternal" => OpenExternal(RunTime.clone(), Arguments).await,
412
413 "workbench:getConfiguration" => WorkbenchConfiguration(RunTime.clone(), Arguments).await,
415
416 "diagnostic:log" => {
422 let Tag = Arguments.first().and_then(|V| V.as_str()).unwrap_or("webview").to_string();
423 let Message = Arguments.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
424 let Extras = if Arguments.len() > 2 {
425 let Tail:Vec<String> = Arguments
426 .iter()
427 .skip(2)
428 .map(|V| {
429 let S = serde_json::to_string(V).unwrap_or_default();
430 if S.len() > 240 {
436 let CutAt = S
437 .char_indices()
438 .map(|(Index, _)| Index)
439 .take_while(|Index| *Index <= 240)
440 .last()
441 .unwrap_or(0);
442 format!("{}…", &S[..CutAt])
443 } else {
444 S
445 }
446 })
447 .collect();
448 format!(" {}", Tail.join(" "))
449 } else {
450 String::new()
451 };
452 dev_log!("diagnostic", "[{}] {}{}", Tag, Message, Extras);
453 Ok(Value::Null)
454 },
455
456 "commands:execute" | "commands:executeCommand" => CommandsExecute(RunTime.clone(), Arguments).await,
461 "commands:getAll" | "commands:getCommands" => {
462 dev_log!("commands", "{}", command);
463 CommandsGetAll(RunTime.clone()).await
464 },
465 "commands:registerCommand"
470 | "commands:unregisterCommand"
471 | "commands:onDidRegisterCommand"
472 | "commands:onDidExecuteCommand" => Ok(Value::Null),
473
474 "extensions:getAll" => {
476 dev_log!("extensions", "extensions:getAll");
477 ExtensionsGetAll(RunTime.clone()).await
478 },
479 "extensions:get" => {
480 dev_log!("extensions", "extensions:get");
481 ExtensionsGet(RunTime.clone(), Arguments).await
482 },
483 "extensions:isActive" => {
484 dev_log!("extensions", "extensions:isActive");
485 ExtensionsIsActive(RunTime.clone(), Arguments).await
486 },
487
488 "extensions:getInstalled" | "extensions:scanSystemExtensions" => {
501 let ArgsSummary = Arguments
507 .iter()
508 .enumerate()
509 .map(|(Idx, V)| {
510 let Preview = serde_json::to_string(V).unwrap_or_default();
511 let Trimmed = if Preview.len() > 180 {
514 let CutAt = Preview
515 .char_indices()
516 .map(|(Index, _)| Index)
517 .take_while(|Index| *Index <= 180)
518 .last()
519 .unwrap_or(0);
520 format!("{}…", &Preview[..CutAt])
521 } else {
522 Preview
523 };
524 format!("[{}]={}", Idx, Trimmed)
525 })
526 .collect::<Vec<_>>()
527 .join(" ");
528 dev_log!("extensions", "{} Arguments={}", command, ArgsSummary);
529 let EffectiveArgs = if command == "extensions:scanSystemExtensions" {
538 let mut Overridden = Arguments.clone();
539 if Overridden.is_empty() {
540 Overridden.push(Value::Null);
541 }
542 Overridden[0] = json!(0);
543 Overridden
544 } else {
545 Arguments.clone()
546 };
547 ExtensionsGetInstalled(RunTime.clone(), EffectiveArgs).await
548 },
549 "extensions:scanUserExtensions" => {
550 dev_log!("extensions", "{} (forwarded to getInstalled with type=User)", command);
559 let mut UserArgs = Arguments.clone();
560 if UserArgs.is_empty() {
561 UserArgs.push(Value::Null);
562 }
563 UserArgs[0] = json!(1);
564 ExtensionsGetInstalled(RunTime.clone(), UserArgs).await
565 },
566 "extensions:getUninstalled" => {
567 dev_log!("extensions", "{} (returning [])", command);
571 Ok(Value::Array(Vec::new()))
572 },
573 "extensions:query" | "extensions:getExtensions" | "extensions:getRecommendations" => {
577 dev_log!("extensions", "{} (offline gallery - returning [])", command);
578 Ok(Value::Array(Vec::new()))
579 },
580 "extensions:getExtensionsControlManifest" => {
586 dev_log!("extensions", "{} (offline gallery - empty manifest)", command);
587 Ok(json!({
588 "malicious": [],
589 "deprecated": {},
590 "search": [],
591 "autoUpdate": {},
592 }))
593 },
594 "extensions:resetPinnedStateForAllUserExtensions" => {
601 dev_log!("extensions", "{} (no-op, pin state is UI-local)", command);
602 Ok(Value::Null)
603 },
604 "extensions:install" => {
612 Extension::ExtensionInstall::ExtensionInstall(ApplicationHandle.clone(), RunTime.clone(), Arguments)
613 .await
614 },
615 "extensions:uninstall" => {
616 Extension::ExtensionUninstall::ExtensionUninstall(
617 ApplicationHandle.clone(),
618 RunTime.clone(),
619 Arguments,
620 )
621 .await
622 },
623
624 "extensions:getManifest" => {
633 let VsixPath = match Arguments.first() {
634 Some(serde_json::Value::String(Path)) => Path.clone(),
635 Some(Obj) => {
636 Obj.get("fsPath")
637 .and_then(|V| V.as_str())
638 .map(str::to_owned)
639 .or_else(|| Obj.get("path").and_then(|V| V.as_str()).map(str::to_owned))
640 .unwrap_or_default()
641 },
642 None => String::new(),
643 };
644 dev_log!("extensions", "extensions:getManifest vsix={}", VsixPath);
645 if VsixPath.is_empty() {
646 Err("extensions:getManifest: missing VSIX path argument".to_string())
647 } else {
648 let Path = std::path::PathBuf::from(&VsixPath);
649 match crate::ExtensionManagement::VsixInstaller::ReadFullManifest(&Path) {
650 Ok(Manifest) => Ok(Manifest),
651 Err(Error) => {
652 dev_log!(
653 "extensions",
654 "warn: [WindServiceHandlers] extensions:getManifest failed for '{}': {}",
655 VsixPath,
656 Error
657 );
658 Err(format!("extensions:getManifest failed: {}", Error))
659 },
660 }
661 }
662 },
663 "extensions:reinstall" | "extensions:updateMetadata" => {
668 dev_log!("extensions", "{} (no-op: no gallery backend)", command);
669 Ok(Value::Null)
670 },
671
672 "terminal:create" => {
674 dev_log!("terminal", "terminal:create");
675 TerminalCreate(RunTime.clone(), Arguments).await
676 },
677 "terminal:sendText" => {
678 dev_log!("terminal", "terminal:sendText");
679 TerminalSendText(RunTime.clone(), Arguments).await
680 },
681 "terminal:dispose" => {
682 dev_log!("terminal", "terminal:dispose");
683 TerminalDispose(RunTime.clone(), Arguments).await
684 },
685 "terminal:show" => {
686 dev_log!("terminal", "terminal:show");
687 TerminalShow(RunTime.clone(), Arguments).await
688 },
689 "terminal:hide" => {
690 dev_log!("terminal", "terminal:hide");
691 TerminalHide(RunTime.clone(), Arguments).await
692 },
693
694 "output:create" => OutputCreate(ApplicationHandle.clone(), Arguments).await,
696 "output:append" => {
697 dev_log!("output", "output:append");
698 OutputAppend(ApplicationHandle.clone(), Arguments).await
699 },
700 "output:appendLine" => {
701 dev_log!("output", "output:appendLine");
702 OutputAppendLine(ApplicationHandle.clone(), Arguments).await
703 },
704 "output:clear" => {
705 dev_log!("output", "output:clear");
706 OutputClear(ApplicationHandle.clone(), Arguments).await
707 },
708 "output:show" => {
709 dev_log!("output", "output:show");
710 OutputShow(ApplicationHandle.clone(), Arguments).await
711 },
712
713 "textFile:read" => {
715 dev_log!("textfile", "textFile:read");
716 TextfileRead(RunTime.clone(), Arguments).await
717 },
718 "textFile:write" => {
719 dev_log!("textfile", "textFile:write");
720 TextfileWrite(RunTime.clone(), Arguments).await
721 },
722 "textFile:save" => TextfileSave(RunTime.clone(), Arguments).await,
723
724 "storage:delete" => {
726 dev_log!("storage", "storage:delete");
727 StorageDelete(RunTime.clone(), Arguments).await
728 },
729 "storage:keys" => {
730 dev_log!("storage", "storage:keys");
731 StorageKeys(RunTime.clone()).await
732 },
733
734 "notification:show" => {
736 dev_log!("notification", "notification:show");
737 NotificationShow(ApplicationHandle.clone(), Arguments).await
738 },
739 "notification:showProgress" => {
740 dev_log!("notification", "notification:showProgress");
741 NotificationShowProgress(ApplicationHandle.clone(), Arguments).await
742 },
743 "notification:updateProgress" => {
744 dev_log!("notification", "notification:updateProgress");
745 NotificationUpdateProgress(ApplicationHandle.clone(), Arguments).await
746 },
747 "notification:endProgress" => {
748 dev_log!("notification", "notification:endProgress");
749 NotificationEndProgress(ApplicationHandle.clone(), Arguments).await
750 },
751
752 "progress:begin" => {
754 dev_log!("progress", "progress:begin");
755 ProgressBegin(ApplicationHandle.clone(), Arguments).await
756 },
757 "progress:report" => {
758 dev_log!("progress", "progress:report");
759 ProgressReport(ApplicationHandle.clone(), Arguments).await
760 },
761 "progress:end" => {
762 dev_log!("progress", "progress:end");
763 ProgressEnd(ApplicationHandle.clone(), Arguments).await
764 },
765
766 "quickInput:showQuickPick" => {
768 dev_log!("quickinput", "quickInput:showQuickPick");
769 QuickInputShowQuickPick(RunTime.clone(), Arguments).await
770 },
771 "quickInput:showInputBox" => {
772 dev_log!("quickinput", "quickInput:showInputBox");
773 QuickInputShowInputBox(RunTime.clone(), Arguments).await
774 },
775
776 "workspaces:getFolders" | "workspaces:getWorkspaceFolders" | "workspaces:getWorkspace" => {
781 dev_log!("workspaces", "{}", command);
782 WorkspacesGetFolders(RunTime.clone()).await
783 },
784 "workspaces:addFolder" | "workspaces:addWorkspaceFolders" => {
785 dev_log!("workspaces", "{}", command);
786 WorkspacesAddFolder(RunTime.clone(), Arguments).await
787 },
788 "workspaces:removeFolder" | "workspaces:removeWorkspaceFolders" => {
789 dev_log!("workspaces", "{}", command);
790 WorkspacesRemoveFolder(RunTime.clone(), Arguments).await
791 },
792 "workspaces:getName" => {
793 dev_log!("workspaces", "{}", command);
794 WorkspacesGetName(RunTime.clone()).await
795 },
796 "workspaces:onDidChangeWorkspaceFolders" | "workspaces:onDidChangeWorkspaceName" => {
800 dev_log!("workspaces", "{} (stub-ack)", command);
801 Ok(Value::Null)
802 },
803
804 "themes:getActive" => {
806 dev_log!("themes", "themes:getActive");
807 ThemesGetActive(RunTime.clone()).await
808 },
809 "themes:list" => {
810 dev_log!("themes", "themes:list");
811 ThemesList(RunTime.clone()).await
812 },
813 "themes:set" => {
814 dev_log!("themes", "themes:set");
815 ThemesSet(RunTime.clone(), Arguments).await
816 },
817
818 "search:findInFiles" | "search:textSearch" | "search:searchText" => {
822 dev_log!("search", "{}", command);
823 SearchFindInFiles(RunTime.clone(), Arguments).await
824 },
825 "search:findFiles" | "search:fileSearch" | "search:searchFile" => {
826 dev_log!("search", "{}", command);
827 SearchFindFiles(RunTime.clone(), Arguments).await
828 },
829 "search:cancel" | "search:clearCache" | "search:onDidChangeResult" => {
834 dev_log!("search", "{} (stub-ack)", command);
835 Ok(Value::Null)
836 },
837
838 "decorations:get" => {
840 dev_log!("decorations", "decorations:get");
841 DecorationsGet(RunTime.clone(), Arguments).await
842 },
843 "decorations:getMany" => {
844 dev_log!("decorations", "decorations:getMany");
845 DecorationsGetMany(RunTime.clone(), Arguments).await
846 },
847 "decorations:set" => {
848 dev_log!("decorations", "decorations:set");
849 DecorationsSet(RunTime.clone(), Arguments).await
850 },
851 "decorations:clear" => {
852 dev_log!("decorations", "decorations:clear");
853 DecorationsClear(RunTime.clone(), Arguments).await
854 },
855
856 "workingCopy:isDirty" => {
858 dev_log!("workingcopy", "workingCopy:isDirty");
859 WorkingCopyIsDirty(RunTime.clone(), Arguments).await
860 },
861 "workingCopy:setDirty" => {
862 dev_log!("workingcopy", "workingCopy:setDirty");
863 WorkingCopySetDirty(RunTime.clone(), Arguments).await
864 },
865 "workingCopy:getAllDirty" => {
866 dev_log!("workingcopy", "workingCopy:getAllDirty");
867 WorkingCopyGetAllDirty(RunTime.clone()).await
868 },
869 "workingCopy:getDirtyCount" => {
870 dev_log!("workingcopy", "workingCopy:getDirtyCount");
871 WorkingCopyGetDirtyCount(RunTime.clone()).await
872 },
873
874 "keybinding:add" => {
876 dev_log!("keybinding", "keybinding:add");
877 KeybindingAdd(RunTime.clone(), Arguments).await
878 },
879 "keybinding:remove" => {
880 dev_log!("keybinding", "keybinding:remove");
881 KeybindingRemove(RunTime.clone(), Arguments).await
882 },
883 "keybinding:lookup" => {
884 dev_log!("keybinding", "keybinding:lookup");
885 KeybindingLookup(RunTime.clone(), Arguments).await
886 },
887 "keybinding:getAll" => {
888 dev_log!("keybinding", "keybinding:getAll");
889 KeybindingGetAll(RunTime.clone()).await
890 },
891
892 "lifecycle:getPhase" => {
894 dev_log!("lifecycle", "lifecycle:getPhase");
895 LifecycleGetPhase(RunTime.clone()).await
896 },
897 "lifecycle:whenPhase" => {
898 dev_log!("lifecycle", "lifecycle:whenPhase");
899 LifecycleWhenPhase(RunTime.clone(), Arguments).await
900 },
901 "lifecycle:requestShutdown" => {
902 dev_log!("lifecycle", "lifecycle:requestShutdown");
903 LifecycleRequestShutdown(ApplicationHandle.clone()).await
904 },
905 "lifecycle:advancePhase" | "lifecycle:setPhase" => {
906 dev_log!("lifecycle", "{}", command);
907 let NewPhase = Arguments.first().and_then(|V| V.as_u64()).unwrap_or(1) as u8;
912 RunTime
913 .Environment
914 .ApplicationState
915 .Feature
916 .Lifecycle
917 .AdvanceAndBroadcast(NewPhase, &ApplicationHandle);
918
919 if NewPhase >= 3 {
933 if let Some(MainWindow) = ApplicationHandle.get_webview_window("main") {
934 if let Ok(false) = MainWindow.is_visible() {
935 if let Err(Error) = MainWindow.show() {
936 dev_log!(
937 "lifecycle",
938 "warn: [Lifecycle] main window show() failed on phase {}: {}",
939 NewPhase,
940 Error
941 );
942 } else {
943 dev_log!(
944 "lifecycle",
945 "[Lifecycle] main window revealed on phase {} (hidden-until-ready)",
946 NewPhase
947 );
948 let _ = MainWindow.set_focus();
949 }
950 }
951 }
952 }
953
954 Ok(json!(RunTime.Environment.ApplicationState.Feature.Lifecycle.GetPhase()))
955 },
956
957 "label:getUri" => {
959 dev_log!("label", "label:getUri");
960 LabelGetURI(RunTime.clone(), Arguments).await
961 },
962 "label:getWorkspace" => {
963 dev_log!("label", "label:getWorkspace");
964 LabelGetWorkspace(RunTime.clone()).await
965 },
966 "label:getBase" => {
967 dev_log!("label", "label:getBase");
968 LabelGetBase(Arguments).await
969 },
970
971 "model:open" => {
973 dev_log!("model", "model:open");
974 ModelOpen(RunTime.clone(), Arguments).await
975 },
976 "model:close" => {
977 dev_log!("model", "model:close");
978 ModelClose(RunTime.clone(), Arguments).await
979 },
980 "model:get" => {
981 dev_log!("model", "model:get");
982 ModelGet(RunTime.clone(), Arguments).await
983 },
984 "model:getAll" => {
985 dev_log!("model", "model:getAll");
986 ModelGetAll(RunTime.clone()).await
987 },
988 "model:updateContent" => {
989 dev_log!("model", "model:updateContent");
990 ModelUpdateContent(RunTime.clone(), Arguments).await
991 },
992
993 "history:goBack" => {
995 dev_log!("history", "history:goBack");
996 HistoryGoBack(RunTime.clone()).await
997 },
998 "history:goForward" => {
999 dev_log!("history", "history:goForward");
1000 HistoryGoForward(RunTime.clone()).await
1001 },
1002 "history:canGoBack" => {
1003 dev_log!("history", "history:canGoBack");
1004 HistoryCanGoBack(RunTime.clone()).await
1005 },
1006 "history:canGoForward" => {
1007 dev_log!("history", "history:canGoForward");
1008 HistoryCanGoForward(RunTime.clone()).await
1009 },
1010 "history:push" => {
1011 dev_log!("history", "history:push");
1012 HistoryPush(RunTime.clone(), Arguments).await
1013 },
1014 "history:clear" => {
1015 dev_log!("history", "history:clear");
1016 HistoryClear(RunTime.clone()).await
1017 },
1018 "history:getStack" => {
1019 dev_log!("history", "history:getStack");
1020 HistoryGetStack(RunTime.clone()).await
1021 },
1022
1023 "mountain_get_status" => {
1025 let status = json!({
1026 "connected": true,
1027 "version": "1.0.0"
1028 });
1029 Ok(status)
1030 },
1031 "mountain_get_configuration" => {
1032 let config = json!({
1033 "editor": { "theme": "dark" },
1034 "extensions": { "installed": [] }
1035 });
1036 Ok(config)
1037 },
1038 "mountain_get_services_status" => {
1039 let services = json!({
1040 "editor": { "status": "running" },
1041 "extensionHost": { "status": "running" }
1042 });
1043 Ok(services)
1044 },
1045 "mountain_get_state" => {
1046 let state = json!({
1047 "ui": {},
1048 "editor": {},
1049 "workspace": {}
1050 });
1051 Ok(state)
1052 },
1053
1054 "file:realpath" => FileRealpath(Arguments).await,
1060 "file:open" => {
1061 dev_log!("vfs", "file:open stub - no fd support yet");
1062 Ok(json!(0))
1063 },
1064 "file:close" => {
1065 dev_log!("vfs", "file:close stub");
1066 Ok(Value::Null)
1067 },
1068 "file:cloneFile" => FileCloneNative(Arguments).await,
1069
1070 "nativeHost:pickFolderAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1076 "nativeHost:pickFileAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1077 "nativeHost:pickFileFolderAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1078 "nativeHost:pickWorkspaceAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1079 "nativeHost:showOpenDialog" => NativeShowOpenDialog(ApplicationHandle.clone(), Arguments).await,
1080 "nativeHost:showSaveDialog" => {
1081 use tauri_plugin_dialog::DialogExt;
1082 let Options = Arguments.first().cloned().unwrap_or(Value::Null);
1083 let Title = Options.get("title").and_then(Value::as_str).unwrap_or("Save").to_string();
1084 let DefaultPath = Options.get("defaultPath").and_then(Value::as_str).map(str::to_string);
1085 let Handle = ApplicationHandle.clone();
1086 let Joined = tokio::task::spawn_blocking(move || -> Option<String> {
1087 let mut Builder = Handle.dialog().file().set_title(&Title);
1088 if let Some(Path) = DefaultPath.as_deref() {
1089 Builder = Builder.set_directory(Path);
1090 }
1091 Builder.blocking_save_file().map(|P| P.to_string())
1092 })
1093 .await;
1094 match Joined {
1095 Ok(Some(Path)) => Ok(json!({ "canceled": false, "filePath": Path })),
1096 Ok(None) => Ok(json!({ "canceled": true })),
1097 Err(Error) => Err(format!("showSaveDialog join error: {}", Error)),
1098 }
1099 },
1100 "nativeHost:showMessageBox" => {
1101 use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
1102 let Options = Arguments.first().cloned().unwrap_or(Value::Null);
1103 let Message = Options.get("message").and_then(Value::as_str).unwrap_or("").to_string();
1104 let Detail = Options.get("detail").and_then(Value::as_str).map(str::to_string);
1105 let DialogType = Options
1106 .get("type")
1107 .and_then(Value::as_str)
1108 .map(|S| S.to_lowercase())
1109 .unwrap_or_default();
1110 let Title = Options.get("title").and_then(Value::as_str).unwrap_or("").to_string();
1111 let Kind = match DialogType.as_str() {
1112 "warning" | "warn" => MessageDialogKind::Warning,
1113 "error" => MessageDialogKind::Error,
1114 _ => MessageDialogKind::Info,
1115 };
1116 let Handle = ApplicationHandle.clone();
1117 let Joined = tokio::task::spawn_blocking(move || -> bool {
1118 let mut Builder = Handle.dialog().message(&Message).kind(Kind);
1119 if !Title.is_empty() {
1120 Builder = Builder.title(&Title);
1121 }
1122 if let Some(DetailText) = Detail.as_deref() {
1123 Builder = Builder.title(DetailText);
1124 }
1125 Builder.blocking_show()
1126 })
1127 .await;
1128 match Joined {
1129 Ok(Answered) => Ok(json!({ "response": if Answered { 0 } else { 1 } })),
1130 Err(Error) => Err(format!("showMessageBox join error: {}", Error)),
1131 }
1132 },
1133
1134 "nativeHost:getEnvironmentPaths" => {
1138 let PathResolver = ApplicationHandle.path();
1139 let AppDataDir = PathResolver.app_data_dir().unwrap_or_default();
1140 let HomeDir = PathResolver.home_dir().unwrap_or_default();
1141 let TmpDir = std::env::temp_dir();
1142
1143 let SessionLogRoot = AppDataDir.join("logs").join(crate::IPC::DevLog::SessionTimestamp::Fn());
1151 let SessionLogWindowDir = SessionLogRoot.join("window1");
1152 let _ = std::fs::create_dir_all(&SessionLogWindowDir);
1153
1154 dev_log!(
1155 "config",
1156 "getEnvironmentPaths: userDataDir={} logsPath={} homeDir={}",
1157 AppDataDir.display(),
1158 SessionLogRoot.display(),
1159 HomeDir.display()
1160 );
1161 let DevLogEnv = std::env::var("Trace").unwrap_or_default();
1162 Ok(json!({
1163 "userDataDir": AppDataDir.to_string_lossy(),
1164 "logsPath": SessionLogRoot.to_string_lossy(),
1165 "homeDir": HomeDir.to_string_lossy(),
1166 "tmpDir": TmpDir.to_string_lossy(),
1167 "devLog": if DevLogEnv.is_empty() { Value::Null } else { json!(DevLogEnv) },
1168 }))
1169 },
1170
1171 "nativeHost:getOSColorScheme" => {
1173 dev_log!("nativehost", "nativeHost:getOSColorScheme");
1174 NativeGetColorScheme().await
1175 },
1176 "nativeHost:getOSProperties" => {
1177 dev_log!("nativehost", "nativeHost:getOSProperties");
1178 NativeOSProperties().await
1179 },
1180 "nativeHost:getOSStatistics" => {
1181 dev_log!("nativehost", "nativeHost:getOSStatistics");
1182 NativeOSStatistics().await
1183 },
1184 "nativeHost:getOSVirtualMachineHint" => {
1185 dev_log!("nativehost", "nativeHost:getOSVirtualMachineHint");
1186 Ok(json!(0))
1187 },
1188
1189 "nativeHost:isWindowAlwaysOnTop" => {
1191 dev_log!("window", "nativeHost:isWindowAlwaysOnTop");
1192 Ok(json!(false))
1193 },
1194 "nativeHost:isFullScreen" => {
1195 dev_log!("window", "nativeHost:isFullScreen");
1196 NativeIsFullscreen(ApplicationHandle.clone()).await
1197 },
1198 "nativeHost:isMaximized" => {
1199 dev_log!("window", "nativeHost:isMaximized");
1200 NativeIsMaximized(ApplicationHandle.clone()).await
1201 },
1202 "nativeHost:getActiveWindowId" => {
1203 dev_log!("window", "nativeHost:getActiveWindowId");
1204 Ok(json!(1))
1205 },
1206 "nativeHost:getCursorScreenPoint" => {
1221 dev_log!("window", "nativeHost:getCursorScreenPoint");
1222 Ok(json!({ "x": 0, "y": 0 }))
1223 },
1224 "nativeHost:getWindows" => Ok(json!([{ "id": 1, "title": "Land", "filename": "" }])),
1225 "nativeHost:getWindowCount" => Ok(json!(1)),
1226
1227 "nativeHost:openAgentsWindow" | "nativeHost:openDevToolsWindow" | "nativeHost:openAuxiliaryWindow" => {
1237 dev_log!("window", "{} (acknowledged, no-op - aux window unsupported)", command);
1238 Ok(Value::Null)
1239 },
1240
1241 "nativeHost:focusWindow" => {
1245 dev_log!("window", "{}", command);
1246 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1247 let _ = Window.set_focus();
1248 }
1249 Ok(Value::Null)
1250 },
1251 "nativeHost:maximizeWindow" => {
1252 dev_log!("window", "{}", command);
1253 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1254 let _ = Window.maximize();
1255 }
1256 Ok(Value::Null)
1257 },
1258 "nativeHost:unmaximizeWindow" => {
1259 dev_log!("window", "{}", command);
1260 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1261 let _ = Window.unmaximize();
1262 }
1263 Ok(Value::Null)
1264 },
1265 "nativeHost:minimizeWindow" => {
1266 dev_log!("window", "{}", command);
1267 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1268 let _ = Window.minimize();
1269 }
1270 Ok(Value::Null)
1271 },
1272 "nativeHost:toggleFullScreen" => {
1273 dev_log!("window", "{}", command);
1274 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1275 let IsFullscreen = Window.is_fullscreen().unwrap_or(false);
1276 let _ = Window.set_fullscreen(!IsFullscreen);
1277 }
1278 Ok(Value::Null)
1279 },
1280 "nativeHost:closeWindow" => {
1281 dev_log!("window", "{}", command);
1282 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1288 let _ = Window.destroy();
1289 }
1290 Ok(Value::Null)
1291 },
1292 "nativeHost:setWindowAlwaysOnTop" => {
1293 dev_log!("window", "{}", command);
1294 let OnTop = Arguments.first().and_then(|V| V.as_bool()).unwrap_or(false);
1295 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1296 let _ = Window.set_always_on_top(OnTop);
1297 }
1298 Ok(Value::Null)
1299 },
1300 "nativeHost:toggleWindowAlwaysOnTop" => {
1301 dev_log!("window", "{}", command);
1302 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1308 let _ = Window.set_always_on_top(true);
1309 }
1310 Ok(Value::Null)
1311 },
1312 "nativeHost:setRepresentedFilename" => {
1313 dev_log!("window", "{}", command);
1314 #[cfg(target_os = "macos")]
1315 {
1316 let Path = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1317 if !Path.is_empty() {
1318 if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1319 let _ = Window.set_title(&Path);
1320 }
1321 }
1322 }
1323 let _ = (&Arguments, &ApplicationHandle);
1324 Ok(Value::Null)
1325 },
1326
1327 "nativeHost:updateWindowControls"
1332 | "nativeHost:setMinimumSize"
1333 | "nativeHost:notifyReady"
1334 | "nativeHost:saveWindowSplash"
1335 | "nativeHost:updateTouchBar"
1336 | "nativeHost:moveWindowTop"
1337 | "nativeHost:positionWindow"
1338 | "nativeHost:setDocumentEdited"
1339 | "nativeHost:setBackgroundThrottling"
1340 | "nativeHost:updateWindowAccentColor" => {
1341 dev_log!("window", "{}", command);
1342 Ok(Value::Null)
1343 },
1344
1345 "nativeHost:isAdmin" => Ok(json!(false)),
1347 "nativeHost:isRunningUnderARM64Translation" => {
1348 #[cfg(target_os = "macos")]
1349 {
1350 let Output = std::process::Command::new("sysctl")
1352 .args(["-n", "sysctl.proc_translated"])
1353 .output();
1354 let IsTranslated = Output
1355 .ok()
1356 .map(|O| String::from_utf8_lossy(&O.stdout).trim() == "1")
1357 .unwrap_or(false);
1358 Ok(json!(IsTranslated))
1359 }
1360 #[cfg(not(target_os = "macos"))]
1361 {
1362 Ok(json!(false))
1363 }
1364 },
1365 "nativeHost:hasWSLFeatureInstalled" => {
1366 #[cfg(target_os = "windows")]
1367 {
1368 Ok(json!(std::path::Path::new("C:\\Windows\\System32\\wsl.exe").exists()))
1369 }
1370 #[cfg(not(target_os = "windows"))]
1371 {
1372 Ok(json!(false))
1373 }
1374 },
1375 "nativeHost:showItemInFolder" => ShowItemInFolder(RunTime.clone(), Arguments).await,
1376 "nativeHost:openExternal" => OpenExternal(RunTime.clone(), Arguments).await,
1377 "nativeHost:moveItemToTrash" => {
1383 let Path = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1384 if Path.is_empty() {
1385 Ok(json!(false))
1386 } else {
1387 dev_log!("nativehost", "nativeHost:moveItemToTrash path={}", Path);
1388 let Moved = {
1389 #[cfg(target_os = "macos")]
1390 {
1391 tokio::process::Command::new("osascript")
1392 .args([
1393 "-e",
1394 &format!(
1395 "tell application \"Finder\" to delete POSIX file \"{}\"",
1396 Path.replace('"', "\\\"")
1397 ),
1398 ])
1399 .status()
1400 .await
1401 .map(|S| S.success())
1402 .unwrap_or(false)
1403 }
1404 #[cfg(target_os = "linux")]
1405 {
1406 let Gio = tokio::process::Command::new("gio")
1407 .args(["trash", &Path])
1408 .status()
1409 .await
1410 .map(|S| S.success())
1411 .unwrap_or(false);
1412 if Gio {
1413 true
1414 } else {
1415 tokio::process::Command::new("trash")
1416 .arg(&Path)
1417 .status()
1418 .await
1419 .map(|S| S.success())
1420 .unwrap_or(false)
1421 }
1422 }
1423 #[cfg(target_os = "windows")]
1424 {
1425 let Script = format!(
1426 "(new-object -comobject Shell.Application).NameSpace(0xA).MoveHere('{}')",
1427 Path.replace('\'', "''")
1428 );
1429 tokio::process::Command::new("powershell.exe")
1430 .args(["-NoProfile", "-Command", &Script])
1431 .status()
1432 .await
1433 .map(|S| S.success())
1434 .unwrap_or(false)
1435 }
1436 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
1437 {
1438 false
1439 }
1440 };
1441 Ok(json!(Moved))
1442 }
1443 },
1444
1445 "nativeHost:readClipboardText" => {
1450 dev_log!("clipboard", "readClipboardText");
1451 match arboard::Clipboard::new() {
1452 Ok(mut Cb) => Ok(json!(Cb.get_text().unwrap_or_default())),
1453 Err(_) => Ok(json!("")),
1454 }
1455 },
1456 "nativeHost:writeClipboardText" => {
1457 dev_log!("clipboard", "writeClipboardText");
1458 let Text = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1459 if let Ok(mut Cb) = arboard::Clipboard::new() {
1460 let _ = Cb.set_text(Text);
1461 }
1462 Ok(Value::Null)
1463 },
1464 "nativeHost:readClipboardFindText" => {
1465 dev_log!("clipboard", "readClipboardFindText");
1466 match arboard::Clipboard::new() {
1469 Ok(mut Cb) => Ok(json!(Cb.get_text().unwrap_or_default())),
1470 Err(_) => Ok(json!("")),
1471 }
1472 },
1473 "nativeHost:writeClipboardFindText" => {
1474 dev_log!("clipboard", "writeClipboardFindText");
1475 let Text = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1476 if let Ok(mut Cb) = arboard::Clipboard::new() {
1477 let _ = Cb.set_text(Text);
1478 }
1479 Ok(Value::Null)
1480 },
1481 "nativeHost:readClipboardBuffer" => {
1482 dev_log!("clipboard", "readClipboardBuffer");
1483 Ok(json!([]))
1484 },
1485 "nativeHost:writeClipboardBuffer" => {
1486 dev_log!("clipboard", "writeClipboardBuffer");
1487 Ok(Value::Null)
1488 },
1489 "nativeHost:hasClipboard" => {
1490 dev_log!("clipboard", "hasClipboard");
1491 Ok(json!(false))
1492 },
1493 "nativeHost:readImage" => {
1494 dev_log!("clipboard", "readImage");
1495 Ok(json!([]))
1496 },
1497 "nativeHost:triggerPaste" => {
1498 dev_log!("clipboard", "triggerPaste");
1499 Ok(Value::Null)
1500 },
1501
1502 "nativeHost:getProcessId" => Ok(json!(std::process::id())),
1504 "nativeHost:killProcess" => Ok(Value::Null),
1505
1506 "nativeHost:findFreePort" => NativeFindFreePort(Arguments).await,
1508 "nativeHost:isPortFree" => Ok(json!(true)),
1509 "nativeHost:resolveProxy" => Ok(Value::Null),
1510 "nativeHost:lookupAuthorization" => Ok(Value::Null),
1511 "nativeHost:lookupKerberosAuthorization" => Ok(Value::Null),
1512 "nativeHost:loadCertificates" => Ok(json!([])),
1513
1514 "nativeHost:relaunch" => Ok(Value::Null),
1516 "nativeHost:reload" => Ok(Value::Null),
1517 "nativeHost:quit" => Ok(Value::Null),
1518 "nativeHost:exit" => Ok(Value::Null),
1519
1520 "nativeHost:openDevTools" => Ok(Value::Null),
1522 "nativeHost:toggleDevTools" => Ok(Value::Null),
1523
1524 "nativeHost:getSystemIdleState" => Ok(json!("active")),
1526 "nativeHost:getSystemIdleTime" => Ok(json!(0)),
1527 "nativeHost:getCurrentThermalState" => Ok(json!("nominal")),
1528 "nativeHost:isOnBatteryPower" => Ok(json!(false)),
1529 "nativeHost:startPowerSaveBlocker" => Ok(json!(0)),
1530 "nativeHost:stopPowerSaveBlocker" => Ok(json!(false)),
1531 "nativeHost:isPowerSaveBlockerStarted" => Ok(json!(false)),
1532
1533 "nativeHost:newWindowTab" => Ok(Value::Null),
1535 "nativeHost:showPreviousWindowTab" => Ok(Value::Null),
1536 "nativeHost:showNextWindowTab" => Ok(Value::Null),
1537 "nativeHost:moveWindowTabToNewWindow" => Ok(Value::Null),
1538 "nativeHost:mergeAllWindowTabs" => Ok(Value::Null),
1539 "nativeHost:toggleWindowTabsBar" => Ok(Value::Null),
1540 "nativeHost:installShellCommand" => Ok(Value::Null),
1541 "nativeHost:uninstallShellCommand" => Ok(Value::Null),
1542
1543 "localPty:getProfiles" => {
1547 dev_log!("terminal", "localPty:getProfiles");
1548 LocalPTYGetProfiles().await
1549 },
1550 "localPty:getDefaultSystemShell" => {
1551 dev_log!("terminal", "localPty:getDefaultSystemShell");
1552 LocalPTYGetDefaultShell().await
1553 },
1554 "localPty:getTerminalLayoutInfo" => {
1555 dev_log!("terminal", "localPty:getTerminalLayoutInfo");
1556 Ok(Value::Null)
1557 },
1558 "localPty:setTerminalLayoutInfo" => {
1559 dev_log!("terminal", "localPty:setTerminalLayoutInfo");
1560 Ok(Value::Null)
1561 },
1562 "localPty:getPerformanceMarks" => {
1563 dev_log!("terminal", "localPty:getPerformanceMarks");
1564 Ok(json!([]))
1565 },
1566 "localPty:reduceConnectionGraceTime" => {
1567 dev_log!("terminal", "localPty:reduceConnectionGraceTime");
1568 Ok(Value::Null)
1569 },
1570 "localPty:listProcesses" => {
1571 dev_log!("terminal", "localPty:listProcesses");
1572 Ok(json!([]))
1573 },
1574 "localPty:getEnvironment" => {
1575 dev_log!("terminal", "localPty:getEnvironment");
1576 LocalPTYGetEnvironment().await
1577 },
1578 "localPty:getLatency" => {
1590 dev_log!("terminal", "localPty:getLatency");
1591 Ok(json!([]))
1592 },
1593
1594 "cocoon:request" => {
1605 dev_log!("ipc", "cocoon:request method={:?}", Arguments.first());
1606 let MethodOpt = Arguments.first().and_then(|V| V.as_str()).map(|S| S.to_string());
1607 match MethodOpt {
1608 None => Err("cocoon:request requires method string in slot 0".to_string()),
1609 Some(Method) => {
1610 let Payload = Arguments.get(1).cloned().unwrap_or(Value::Null);
1611 let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 5000).await;
1625 crate::Vine::Client::SendRequest::Fn("cocoon-main", Method.clone(), Payload, 30_000)
1626 .await
1627 .map_err(|Error| format!("cocoon:request {} failed: {:?}", Method, Error))
1628 },
1629 }
1630 },
1631
1632 "cocoon:notify" => {
1642 dev_log!("ipc", "cocoon:notify method={:?}", Arguments.first());
1643 let MethodOpt = Arguments.first().and_then(|V| V.as_str()).map(|S| S.to_string());
1644 match MethodOpt {
1645 None => Err("cocoon:notify requires method string in slot 0".to_string()),
1646 Some(Method) => {
1647 let Payload = Arguments.get(1).cloned().unwrap_or(Value::Null);
1648 if let Err(Error) = crate::Vine::Client::SendNotification::Fn(
1649 "cocoon-main".to_string(),
1650 Method.clone(),
1651 Payload,
1652 )
1653 .await
1654 {
1655 dev_log!("ipc", "warn: [cocoon:notify] {} failed: {:?}", Method, Error);
1656 }
1657 Ok(Value::Null)
1658 },
1659 }
1660 },
1661
1662 "localPty:spawn" => {
1679 dev_log!("terminal", "{}", command);
1684 TerminalCreate(RunTime.clone(), Arguments).await
1685 },
1686 "localPty:createProcess" => {
1687 dev_log!("terminal", "{}", command);
1688 match TerminalCreate(RunTime.clone(), Arguments).await {
1689 Ok(Response) => {
1690 let TerminalIdOption = Response.get("id").and_then(serde_json::Value::as_u64);
1694 match TerminalIdOption {
1695 Some(TerminalId) if TerminalId > 0 => Ok(serde_json::json!(TerminalId)),
1696 Some(_) | None => {
1697 dev_log!(
1708 "terminal",
1709 "error: [localPty:createProcess] CreateTerminal returned no usable id; \
1710 response={:?}",
1711 Response
1712 );
1713 Err(format!(
1714 "localPty:createProcess: CreateTerminal returned no terminal id (response={})",
1715 Response
1716 ))
1717 },
1718 }
1719 },
1720 Err(Error) => Err(Error),
1721 }
1722 },
1723 "localPty:start" => {
1724 dev_log!("terminal", "{} no-op (eager-spawn)", command);
1737 Ok(Value::Null)
1738 },
1739 "localPty:input" | "localPty:write" => {
1740 dev_log!("terminal", "{}", command);
1741 TerminalSendText(RunTime.clone(), Arguments).await
1742 },
1743 "localPty:shutdown" | "localPty:dispose" => {
1744 dev_log!("terminal", "{}", command);
1745 TerminalDispose(RunTime.clone(), Arguments).await
1746 },
1747 "localPty:resize" => {
1748 dev_log!("terminal", "localPty:resize");
1749 let (TerminalId, Columns, Rows) = {
1761 let First = Arguments.first().cloned().unwrap_or(Value::Null);
1762 if First.is_object() {
1763 let Id = First.get("id").and_then(|V| V.as_u64()).unwrap_or(0);
1764 let C = First.get("cols").and_then(|V| V.as_u64()).unwrap_or(80) as u16;
1765 let R = First.get("rows").and_then(|V| V.as_u64()).unwrap_or(24) as u16;
1766 (Id, C, R)
1767 } else {
1768 let Id = Arguments.get(0).and_then(|V| V.as_u64()).unwrap_or(0);
1769 let C = Arguments.get(1).and_then(|V| V.as_u64()).unwrap_or(80) as u16;
1770 let R = Arguments.get(2).and_then(|V| V.as_u64()).unwrap_or(24) as u16;
1771 (Id, C, R)
1772 }
1773 };
1774 if TerminalId == 0 {
1775 Ok(Value::Null)
1776 } else {
1777 let Columns = if Columns == 0 { 1 } else { Columns };
1778 let Rows = if Rows == 0 { 1 } else { Rows };
1779 use CommonLibrary::{
1780 Environment::Requires::Requires,
1781 Terminal::TerminalProvider::TerminalProvider,
1782 };
1783 let Provider:Arc<dyn TerminalProvider> = RunTime.Environment.Require();
1784 match Provider.ResizeTerminal(TerminalId, Columns, Rows).await {
1785 Ok(_) => Ok(Value::Null),
1786 Err(Error) => {
1787 dev_log!(
1797 "terminal",
1798 "warn: localPty:resize id={} cols={} rows={} failed: {}",
1799 TerminalId,
1800 Columns,
1801 Rows,
1802 Error
1803 );
1804 Ok(Value::Null)
1805 },
1806 }
1807 }
1808 },
1809 "localPty:acknowledgeDataEvent" => {
1810 Ok(Value::Null)
1812 },
1813 "localPty:processBinary"
1818 | "localPty:attachToProcess"
1819 | "localPty:detachFromProcess"
1820 | "localPty:orphanQuestionReply"
1821 | "localPty:updateTitle"
1822 | "localPty:updateIcon"
1823 | "localPty:refreshProperty"
1824 | "localPty:updateProperty"
1825 | "localPty:getRevivedPtyNewId"
1826 | "localPty:freePortKillProcess"
1827 | "localPty:reviveTerminalProcesses"
1828 | "localPty:getBackendOS"
1829 | "localPty:installAutoReply"
1830 | "localPty:uninstallAllAutoReplies"
1831 | "localPty:serializeTerminalState" => Ok(Value::Null),
1832
1833 "update:_getInitialState" => {
1837 dev_log!("update", "update:_getInitialState");
1838 Ok(json!({ "type": "idle", "updateType": 0 }))
1839 },
1840 "update:isLatestVersion" => {
1841 dev_log!("update", "update:isLatestVersion");
1842 Ok(json!(true))
1843 },
1844 "update:checkForUpdates" => {
1845 dev_log!("update", "update:checkForUpdates");
1846 Ok(Value::Null)
1847 },
1848 "update:downloadUpdate" => {
1849 dev_log!("update", "update:downloadUpdate");
1850 Ok(Value::Null)
1851 },
1852 "update:applyUpdate" => {
1853 dev_log!("update", "update:applyUpdate");
1854 Ok(Value::Null)
1855 },
1856 "update:quitAndInstall" => {
1857 dev_log!("update", "update:quitAndInstall");
1858 Ok(Value::Null)
1859 },
1860
1861 "menubar:updateMenubar" => {
1873 use std::{
1874 sync::{Arc, Mutex as StandardMutex, OnceLock},
1875 time::Duration,
1876 };
1877
1878 use tokio::task::JoinHandle;
1879 type MenubarCell = StandardMutex<(Option<JoinHandle<()>>, u64)>;
1880 static MENUBAR_DEBOUNCE:OnceLock<Arc<MenubarCell>> = OnceLock::new();
1881 let Cell = MENUBAR_DEBOUNCE.get_or_init(|| Arc::new(StandardMutex::new((None, 0)))).clone();
1882
1883 if let Ok(mut Guard) = Cell.lock() {
1884 if let Some(Pending) = Guard.0.take() {
1885 Pending.abort();
1886 }
1887 Guard.1 = Guard.1.saturating_add(1);
1888 let CellForTask = Cell.clone();
1889 Guard.0 = Some(tokio::spawn(async move {
1890 tokio::time::sleep(Duration::from_millis(50)).await;
1891 let Coalesced = if let Ok(mut Post) = CellForTask.lock() {
1892 let N = Post.1;
1893 Post.1 = 0;
1894 Post.0 = None;
1895 N
1896 } else {
1897 0
1898 };
1899 dev_log!("menubar", "menubar:updateMenubar (applied, coalesced {} pending)", Coalesced);
1900 }));
1901 } else {
1902 dev_log!("menubar", "menubar:updateMenubar (debouncer lock poisoned)");
1903 }
1904 Ok(Value::Null)
1905 },
1906
1907 "url:registerExternalUriOpener" => {
1911 dev_log!("url", "url:registerExternalUriOpener");
1912 Ok(Value::Null)
1913 },
1914
1915 "encryption:encrypt" => {
1919 dev_log!("encryption", "encryption:encrypt");
1920 Ok(json!(""))
1921 },
1922 "encryption:decrypt" => {
1923 dev_log!("encryption", "encryption:decrypt");
1924 Ok(json!(""))
1925 },
1926
1927 "extensionHostStarter:createExtensionHost" => {
1931 dev_log!("exthost", "extensionHostStarter:createExtensionHost");
1932 Ok(json!({ "id": "1" }))
1933 },
1934 "extensionHostStarter:start" => {
1935 let Pid =
1942 crate::ProcessManagement::CocoonManagement::GetCocoonPid().unwrap_or_else(std::process::id);
1943 dev_log!("exthost", "extensionHostStarter:start pid={}", Pid);
1944 Ok(json!({ "pid": Pid }))
1945 },
1946 "extensionHostStarter:kill" => {
1947 dev_log!("exthost", "extensionHostStarter:kill");
1948 Ok(Value::Null)
1949 },
1950 "extensionHostStarter:getExitInfo" => {
1951 dev_log!("exthost", "extensionHostStarter:getExitInfo");
1952 Ok(json!({ "code": null, "signal": null }))
1953 },
1954
1955 "cocoon:extensionHostMessage" => {
1959 let ByteCount = Arguments
1960 .first()
1961 .map(|P| P.get("data").and_then(|D| D.as_array()).map(|A| A.len()).unwrap_or(0))
1962 .unwrap_or(0);
1963 dev_log!("exthost", "cocoon:extensionHostMessage bytes={}", ByteCount);
1964
1965 let Payload = Arguments.first().cloned().unwrap_or(Value::Null);
1968 tokio::spawn(async move {
1969 if let Err(Error) = crate::Vine::Client::SendNotification::Fn(
1970 "cocoon-main".to_string(),
1971 "extensionHostMessage".to_string(),
1972 Payload,
1973 )
1974 .await
1975 {
1976 dev_log!("exthost", "cocoon:extensionHostMessage forward failed: {}", Error);
1977 }
1978 });
1979 Ok(Value::Null)
1980 },
1981
1982 "extensionhostdebugservice:reload" => {
1986 dev_log!("exthost", "extensionhostdebugservice:reload");
1987 use tauri::Emitter;
1992 if let Err(Error) = ApplicationHandle.emit(SkyEvent::ExtHostDebugReload.AsStr(), json!({})) {
1993 dev_log!("exthost", "warn: extensionhostdebugservice:reload emit failed: {}", Error);
1994 }
1995 Ok(Value::Null)
1996 },
1997 "extensionhostdebugservice:close" => {
1998 dev_log!("exthost", "extensionhostdebugservice:close");
1999 use tauri::Emitter;
2000 if let Err(Error) = ApplicationHandle.emit("sky://exthost/debug-close", json!({})) {
2001 dev_log!("exthost", "warn: extensionhostdebugservice:close emit failed: {}", Error);
2002 }
2003 Ok(Value::Null)
2004 },
2005 "extensionhostdebugservice:attachSession" | "extensionhostdebugservice:terminateSession" => {
2006 dev_log!("exthost", "{}", command);
2007 Ok(Value::Null)
2008 },
2009
2010 "workspaces:getRecentlyOpened" => {
2014 dev_log!("workspaces", "workspaces:getRecentlyOpened");
2015 ReadRecentlyOpened()
2016 },
2017 "workspaces:removeRecentlyOpened" => {
2018 dev_log!("workspaces", "workspaces:removeRecentlyOpened");
2019 let Uri = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2020 if !Uri.is_empty() {
2021 MutateRecentlyOpened(|List| {
2022 if let Some(Workspaces) = List.get_mut("workspaces").and_then(|V| V.as_array_mut()) {
2023 Workspaces
2024 .retain(|Entry| Entry.get("uri").and_then(|V| V.as_str()).unwrap_or("") != Uri);
2025 }
2026 if let Some(Files) = List.get_mut("files").and_then(|V| V.as_array_mut()) {
2027 Files.retain(|Entry| Entry.get("uri").and_then(|V| V.as_str()).unwrap_or("") != Uri);
2028 }
2029 });
2030 }
2031 Ok(Value::Null)
2032 },
2033 "workspaces:addRecentlyOpened" => {
2034 dev_log!("workspaces", "workspaces:addRecentlyOpened");
2035 let Entries:Vec<Value> = Arguments.first().and_then(|V| V.as_array()).cloned().unwrap_or_default();
2037 if !Entries.is_empty() {
2038 MutateRecentlyOpened(|List| {
2039 let Workspaces = List
2040 .get_mut("workspaces")
2041 .and_then(|V| V.as_array_mut())
2042 .map(|V| std::mem::take(V))
2043 .unwrap_or_default();
2044 let Files = List
2045 .get_mut("files")
2046 .and_then(|V| V.as_array_mut())
2047 .map(|V| std::mem::take(V))
2048 .unwrap_or_default();
2049 let mut MergedWorkspaces = Workspaces;
2050 let mut MergedFiles = Files;
2051 for Entry in Entries {
2052 let Folder = Entry
2053 .get("folderUri")
2054 .cloned()
2055 .or_else(|| Entry.get("workspace").and_then(|W| W.get("configPath").cloned()));
2056 let File = Entry.get("fileUri").cloned();
2057 if let Some(FolderUri) = Folder.and_then(|V| v_str(&V)) {
2058 MergedWorkspaces
2059 .retain(|E| E.get("uri").and_then(|V| V.as_str()).unwrap_or("") != FolderUri);
2060 let mut Item = serde_json::Map::new();
2061 Item.insert("uri".into(), json!(FolderUri));
2062 if let Some(Label) = Entry.get("label").and_then(|V| V.as_str()) {
2063 Item.insert("label".into(), json!(Label));
2064 }
2065 MergedWorkspaces.insert(0, Value::Object(Item));
2066 }
2067 if let Some(FileUri) = File.and_then(|V| v_str(&V)) {
2068 MergedFiles
2069 .retain(|E| E.get("uri").and_then(|V| V.as_str()).unwrap_or("") != FileUri);
2070 let mut Item = serde_json::Map::new();
2071 Item.insert("uri".into(), json!(FileUri));
2072 MergedFiles.insert(0, Value::Object(Item));
2073 }
2074 }
2075 MergedWorkspaces.truncate(50);
2078 MergedFiles.truncate(50);
2079 List.insert("workspaces".into(), Value::Array(MergedWorkspaces));
2080 List.insert("files".into(), Value::Array(MergedFiles));
2081 });
2082 }
2083 Ok(Value::Null)
2084 },
2085 "workspaces:clearRecentlyOpened" => {
2086 dev_log!("workspaces", "workspaces:clearRecentlyOpened");
2087 MutateRecentlyOpened(|List| {
2088 List.insert("workspaces".into(), json!([]));
2089 List.insert("files".into(), json!([]));
2090 });
2091 Ok(Value::Null)
2092 },
2093 "workspaces:enterWorkspace" => {
2094 dev_log!("workspaces", "workspaces:enterWorkspace");
2095 Ok(Value::Null)
2096 },
2097 "workspaces:createUntitledWorkspace" => {
2098 dev_log!("workspaces", "workspaces:createUntitledWorkspace");
2099 Ok(Value::Null)
2100 },
2101 "workspaces:deleteUntitledWorkspace" => {
2102 dev_log!("workspaces", "workspaces:deleteUntitledWorkspace");
2103 Ok(Value::Null)
2104 },
2105 "workspaces:getWorkspaceIdentifier" => {
2106 let Workspace = &RunTime.Environment.ApplicationState.Workspace;
2113 let Folders = Workspace.GetWorkspaceFolders();
2114 if let Some(First) = Folders.first() {
2115 use std::{
2116 collections::hash_map::DefaultHasher,
2117 hash::{Hash, Hasher},
2118 };
2119 let mut Hasher = DefaultHasher::new();
2120 First.URI.as_str().hash(&mut Hasher);
2121 let Id = format!("{:016x}", Hasher.finish());
2122 Ok(json!({
2123 "id": Id,
2124 "configPath": Value::Null,
2125 "uri": First.URI.to_string(),
2126 }))
2127 } else {
2128 Ok(Value::Null)
2129 }
2130 },
2131 "workspaces:getDirtyWorkspaces" => Ok(json!([])),
2132
2133 "git:exec" => {
2138 dev_log!("git", "git:exec");
2139 Git::HandleExec::HandleExec(Arguments).await
2140 },
2141 "git:clone" => {
2142 dev_log!("git", "git:clone");
2143 Git::HandleClone::HandleClone(Arguments).await
2144 },
2145 "git:pull" => {
2146 dev_log!("git", "git:pull");
2147 Git::HandlePull::HandlePull(Arguments).await
2148 },
2149 "git:checkout" => {
2150 dev_log!("git", "git:checkout");
2151 Git::HandleCheckout::HandleCheckout(Arguments).await
2152 },
2153 "git:revParse" => {
2154 dev_log!("git", "git:revParse");
2155 Git::HandleRevParse::HandleRevParse(Arguments).await
2156 },
2157 "git:fetch" => {
2158 dev_log!("git", "git:fetch");
2159 Git::HandleFetch::HandleFetch(Arguments).await
2160 },
2161 "git:revListCount" => {
2162 dev_log!("git", "git:revListCount");
2163 Git::HandleRevListCount::HandleRevListCount(Arguments).await
2164 },
2165 "git:cancel" => {
2166 dev_log!("git", "git:cancel");
2167 Git::HandleCancel::HandleCancel(Arguments).await
2168 },
2169 "git:isAvailable" => {
2170 dev_log!("git", "git:isAvailable");
2171 Git::HandleIsAvailable::HandleIsAvailable(Arguments).await
2172 },
2173
2174 "tree:getChildren" => {
2181 let ViewId = Arguments
2182 .first()
2183 .and_then(|V| V.get("viewId").or_else(|| V.get(0)))
2184 .and_then(Value::as_str)
2185 .unwrap_or("")
2186 .to_string();
2187 let ItemHandle = Arguments
2188 .first()
2189 .and_then(|V| V.get("treeItemHandle").or_else(|| V.get(1)))
2190 .and_then(Value::as_str)
2191 .unwrap_or("")
2192 .to_string();
2193 dev_log!(
2194 "tree-view",
2195 "[TreeView] invoke:getChildren view={} parent={}",
2196 ViewId,
2197 ItemHandle
2198 );
2199 if ViewId.is_empty() {
2200 Err("tree:getChildren requires viewId".to_string())
2201 } else {
2202 let Parameters = json!({
2203 "viewId": ViewId,
2204 "treeItemHandle": ItemHandle,
2205 });
2206 let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 5000).await;
2225 match crate::Vine::Client::SendRequest::Fn(
2236 "cocoon-main",
2237 "$provideTreeChildren".to_string(),
2238 Parameters,
2239 5000,
2245 )
2246 .await
2247 {
2248 Ok(Value_) => {
2259 match &Value_ {
2260 Value::Object(_) | Value::Array(_) => Ok(Value_),
2261 _ => Ok(json!({ "items": [] })),
2262 }
2263 },
2264 Err(Error) => {
2265 crate::IPC::DevLog::DebugOnce::Fn(
2280 "tree-view",
2281 &format!("get-children-error:{}", ViewId),
2282 &format!(
2283 "[TreeView] invoke:getChildren error view={} err={:?} (further occurrences \
2284 silenced)",
2285 ViewId, Error
2286 ),
2287 );
2288 Ok(json!({ "items": [] }))
2289 },
2290 }
2291 }
2292 },
2293
2294 "sky:replay-events" => {
2306 use tauri::Emitter;
2307 let mut TreeViewCount:usize = 0;
2308 let mut ScmCount:usize = 0;
2309 let mut CommandCount:usize = 0;
2310 let mut TerminalCount:usize = 0;
2311 let mut TerminalDataBytes:usize = 0;
2312 if let Ok(TreeViews) = RunTime.Environment.ApplicationState.Feature.TreeViews.ActiveTreeViews.lock()
2313 {
2314 for (ViewId, Dto) in TreeViews.iter() {
2315 let Payload = serde_json::json!({
2316 "viewId": ViewId,
2317 "options": {
2318 "canSelectMany": Dto.CanSelectMany,
2319 "showCollapseAll": Dto.HasHandleDrag,
2320 "title": Dto.Title.clone().unwrap_or_default(),
2321 },
2322 });
2323 if ApplicationHandle.emit("sky://tree-view/create", Payload).is_ok() {
2324 TreeViewCount += 1;
2325 }
2326 }
2327 }
2328 if let Ok(ScmProviders) = RunTime
2338 .Environment
2339 .ApplicationState
2340 .Feature
2341 .Markers
2342 .SourceControlManagementProviders
2343 .lock()
2344 {
2345 for (Handle, Dto) in ScmProviders.iter() {
2346 let RootUriStr = Dto
2347 .RootURI
2348 .as_ref()
2349 .and_then(|V| V.get("external").or_else(|| V.get("path")))
2350 .and_then(serde_json::Value::as_str)
2351 .unwrap_or("")
2352 .to_string();
2353 let ScmId = if Dto.Identifier.is_empty() {
2354 "git".to_string()
2355 } else {
2356 Dto.Identifier.clone()
2357 };
2358 let Payload = serde_json::json!({
2359 "scmId": ScmId,
2360 "label": Dto.Label,
2361 "rootUri": RootUriStr,
2362 "extensionId": "",
2363 "handle": *Handle,
2364 });
2365 if ApplicationHandle.emit("sky://scm/register", Payload).is_ok() {
2366 ScmCount += 1;
2367 }
2368 }
2369 }
2370 if let Ok(Commands) = RunTime.Environment.ApplicationState.Extension.Registry.CommandRegistry.lock()
2386 {
2387 let mut Batch:Vec<serde_json::Value> = Vec::new();
2388 for (CommandId, Handler) in Commands.iter() {
2389 use crate::Environment::CommandProvider::CommandHandler;
2390 let Kind = match Handler {
2391 CommandHandler::Native(_) => continue,
2392 CommandHandler::Proxied { .. } => "extension",
2393 };
2394 Batch.push(serde_json::json!({
2395 "id": CommandId,
2396 "commandId": CommandId,
2397 "kind": Kind,
2398 }));
2399 }
2400 if !Batch.is_empty() {
2401 let Count = Batch.len();
2402 if ApplicationHandle
2403 .emit("sky://command/register", serde_json::json!({ "commands": Batch }))
2404 .is_ok()
2405 {
2406 CommandCount = Count;
2407 }
2408 }
2409 }
2410 if let Ok(Terminals) = RunTime.Environment.ApplicationState.Feature.Terminals.ActiveTerminals.lock()
2418 {
2419 for (TerminalId, Arc) in Terminals.iter() {
2420 let (Name, Pid) = if let Ok(State) = Arc.lock() {
2421 (State.Name.clone(), State.OSProcessIdentifier.unwrap_or(0))
2422 } else {
2423 (String::new(), 0)
2424 };
2425 let CreatePayload = serde_json::json!({
2426 "id": *TerminalId,
2427 "name": Name,
2428 "pid": Pid,
2429 });
2430 if ApplicationHandle.emit("sky://terminal/create", CreatePayload).is_ok() {
2431 TerminalCount += 1;
2432 }
2433 }
2434 }
2435 for (TerminalId, Bytes) in crate::Environment::TerminalProvider::DrainTerminalOutputBuffer() {
2436 let DataString = String::from_utf8_lossy(&Bytes).to_string();
2437 TerminalDataBytes += Bytes.len();
2438 let _ = ApplicationHandle.emit(
2439 "sky://terminal/data",
2440 serde_json::json!({ "id": TerminalId, "data": DataString }),
2441 );
2442 }
2443 dev_log!(
2444 "sky-emit",
2445 "[SkyEmit] replay-events tree-views={} scm={} commands={} terminals={} terminal-bytes={}",
2446 TreeViewCount,
2447 ScmCount,
2448 CommandCount,
2449 TerminalCount,
2450 TerminalDataBytes
2451 );
2452 Ok(serde_json::json!({
2453 "treeViews": TreeViewCount,
2454 "scmProviders": ScmCount,
2455 "commands": CommandCount,
2456 "terminals": TerminalCount,
2457 "terminalDataBytes": TerminalDataBytes,
2458 }))
2459 },
2460
2461 _ => {
2470 use std::str::FromStr;
2471 match CommonLibrary::IPC::Channel::Channel::from_str(&command) {
2472 Ok(KnownChannel) => {
2473 dev_log!(
2474 "ipc",
2475 "error: [WindServiceHandlers] Channel {:?} is registered but has no dispatch arm",
2476 KnownChannel
2477 );
2478 Err(format!("IPC channel registered but unimplemented: {}", command))
2479 },
2480 Err(_) => {
2481 dev_log!("ipc", "error: [WindServiceHandlers] Unknown IPC command: {}", command);
2482 Err(format!("Unknown IPC command: {}", command))
2483 },
2484 }
2485 },
2486 };
2487
2488 if ResultSender.send(MatchResult).is_err() {
2489 dev_log!(
2490 "ipc",
2491 "warn: [WindServiceHandlers] IPC result receiver dropped before dispatch completed"
2492 );
2493 }
2494 },
2495 CommandPriority,
2496 );
2497
2498 let Result = match ResultReceiver.await {
2499 Ok(Dispatched) => Dispatched,
2500 Err(_) => {
2501 dev_log!(
2502 "ipc",
2503 "error: [WindServiceHandlers] IPC task cancelled before producing a result"
2504 );
2505 Err("IPC task cancelled before result was produced".to_string())
2506 },
2507 };
2508
2509 let IsErr = Result.is_err();
2511 let SpanName = if IsErr {
2512 format!("land:mountain:ipc:{}:error", command)
2513 } else {
2514 format!("land:mountain:ipc:{}", command)
2515 };
2516 crate::otel_span!(&SpanName, OTLPStart, &[("ipc.command", command.as_str())]);
2517
2518 let HandlerElapsedNanos = crate::IPC::DevLog::NowNano::Fn().saturating_sub(OTLPStart);
2523 let HandlerDurationMs = HandlerElapsedNanos / 1_000_000;
2524 crate::Binary::Build::PostHogPlugin::CaptureHandler::Fn(&command, HandlerDurationMs, !IsErr);
2525
2526 if !IsHighFrequencyCommand {
2535 let ElapsedNanos = crate::IPC::DevLog::NowNano::Fn().saturating_sub(OTLPStart);
2536 dev_log!("ipc", "done: {} ok={} t_ns={}", command, !IsErr, ElapsedNanos);
2537 }
2538
2539 Result
2540}
2541
2542pub fn register_wind_ipc_handlers(ApplicationHandle:&tauri::AppHandle) -> Result<(), String> {
2543 dev_log!("lifecycle", "registering IPC handlers");
2544
2545 Ok(())
2549}