Skip to main content

CommonLibrary/IPC/
Channel.rs

1//! # Channel Registry - single source of truth for Wind ↔ Mountain IPC
2//!
3//! Every Tauri `invoke` request from Wind is a string-typed RPC. Historically
4//! that string was hand-kept on both sides (Rust `match command.as_str()` in
5//! `Mountain/Source/IPC/WindServiceHandlers.rs` and string literals in Wind's
6//! `Effect/*/Live.ts` files). The result: drift - a channel could be declared
7//! in Wind, referenced in Mountain's match, and still have no implementation
8//! (the `extensions:install` no-op stub shipped for months).
9//!
10//! `Channel` is the enumerated registry. Rust callers dispatch on the variant;
11//! the wire string is produced by `AsStr()` and parsed by `FromStr`. The
12//! matching TypeScript const object lives at
13//! `Element/Wind/Source/IPC/Channel.ts` - kept in sync by convention (a grep
14//! diff is cheap; a full codegen would be overkill for 147 strings).
15//!
16//! ## Why a declarative macro?
17//!
18//! The variant → wire-string mapping is pure data. `DefineChannels!` expands
19//! it into the enum body, `AsStr`, `All`, and `FromStr` in one pass so adding
20//! a channel is a single-line change that compilers can't forget.
21//!
22//! ## Channel priority classes (Atom O3)
23//!
24//! `Priority` returns the Echo scheduler lane a given channel should dispatch
25//! on. Used by the O1 wrap in `mountain_ipc_invoke` so user-facing latency
26//! never queues behind background work. Three classes:
27//!
28//!   - `High`: direct user action (commands, file read, terminal input,
29//!     notifications, VSIX install).
30//!   - `Low`: background / deferrable (search, logging, update checks,
31//!     offline-gallery stubs).
32//!   - `Normal`: everything else.
33
34/// Lane selector for Echo scheduler dispatch.
35///
36/// Deliberately isolated from `Echo::Task::Priority` so Common stays
37/// dependency-free on Echo. Mountain's `mountain_ipc_invoke` wrapper maps
38/// `ChannelPriority` → `Echo::Task::Priority` at the single submit site.
39#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
40pub enum ChannelPriority {
41	High,
42
43	Normal,
44
45	Low,
46}
47
48macro_rules! DefineChannels {
49
50	($($Variant:ident => $Wire:literal,)* $(,)?) => {
51
52		/// Enumerated IPC channel identifiers for Wind ↔ Mountain calls.
53		#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
54		pub enum Channel {
55
56			$($Variant,)*
57		}
58
59		impl Channel {
60
61			/// Wire string produced on the Tauri transport.
62			pub fn AsStr(&self) -> &'static str {
63
64				match self {
65
66					$(Self::$Variant => $Wire,)*
67				}
68			}
69
70			/// Full set of channels, in declaration order.
71			pub fn All() -> &'static [Self] {
72
73				&[$(Self::$Variant,)*]
74			}
75		}
76
77		impl ::std::fmt::Display for Channel {
78
79			fn fmt(&self, Formatter:&mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
80
81				Formatter.write_str(self.AsStr())
82			}
83		}
84
85		impl ::std::str::FromStr for Channel {
86
87			type Err = ::std::string::String;
88
89			fn from_str(Wire:&str) -> ::std::result::Result<Self, Self::Err> {
90
91				match Wire {
92
93					$($Wire => Ok(Self::$Variant),)*
94					_ => Err(format!("unknown IPC channel: {}", Wire)),
95				}
96			}
97		}
98	};
99}
100
101DefineChannels! {
102
103	// --- Cocoon bridge ---
104	CocoonExtensionHostMessage                    => "cocoon:extensionHostMessage",
105
106	// --- Commands ---
107	CommandsExecute                               => "commands:execute",
108
109	CommandsGetAll                                => "commands:getAll",
110
111	// --- Configuration ---
112	ConfigurationGet                              => "configuration:get",
113
114	ConfigurationInspect                          => "configuration:inspect",
115
116	ConfigurationLookup                           => "configuration:lookup",
117
118	ConfigurationUpdate                           => "configuration:update",
119
120	// --- Decorations ---
121	DecorationsClear                              => "decorations:clear",
122
123	DecorationsGet                                => "decorations:get",
124
125	DecorationsGetMany                            => "decorations:getMany",
126
127	DecorationsSet                                => "decorations:set",
128
129	// --- Diagnostics ---
130	DiagnosticLog                                 => "diagnostic:log",
131
132	// --- Encryption ---
133	EncryptionDecrypt                             => "encryption:decrypt",
134
135	EncryptionEncrypt                             => "encryption:encrypt",
136
137	// --- Environment ---
138	EnvironmentGet                                => "environment:get",
139
140	// --- Extension host debug service ---
141	ExtensionHostDebugServiceAttachSession        => "extensionhostdebugservice:attachSession",
142
143	ExtensionHostDebugServiceClose                => "extensionhostdebugservice:close",
144
145	ExtensionHostDebugServiceReload               => "extensionhostdebugservice:reload",
146
147	ExtensionHostDebugServiceTerminateSession     => "extensionhostdebugservice:terminateSession",
148
149	// --- Extensions ---
150	ExtensionsActivate                            => "extensions:activate",
151
152	ExtensionsGet                                 => "extensions:get",
153
154	ExtensionsGetAll                              => "extensions:getAll",
155
156	ExtensionsGetExtensions                       => "extensions:getExtensions",
157
158	ExtensionsGetExtensionsControlManifest        => "extensions:getExtensionsControlManifest",
159
160	ExtensionsGetInstalled                        => "extensions:getInstalled",
161
162	ExtensionsGetRecommendations                  => "extensions:getRecommendations",
163
164	ExtensionsGetUninstalled                      => "extensions:getUninstalled",
165
166	ExtensionsGetManifest                         => "extensions:getManifest",
167
168	ExtensionsInstall                             => "extensions:install",
169
170	ExtensionsIsActive                            => "extensions:isActive",
171
172	ExtensionsQuery                               => "extensions:query",
173
174	ExtensionsReinstall                           => "extensions:reinstall",
175
176	ExtensionsResetPinnedState                    => "extensions:resetPinnedStateForAllUserExtensions",
177
178	ExtensionsScanSystemExtensions                => "extensions:scanSystemExtensions",
179
180	ExtensionsScanUserExtensions                  => "extensions:scanUserExtensions",
181
182	ExtensionsUninstall                           => "extensions:uninstall",
183
184	ExtensionsUpdateMetadata                      => "extensions:updateMetadata",
185
186	// --- File system ---
187	FileCloneFile                                 => "file:cloneFile",
188
189	FileClose                                     => "file:close",
190
191	FileCopy                                      => "file:copy",
192
193	FileDelete                                    => "file:delete",
194
195	FileExists                                    => "file:exists",
196
197	FileMkdir                                     => "file:mkdir",
198
199	FileMove                                      => "file:move",
200
201	FileOpen                                      => "file:open",
202
203	FileRead                                      => "file:read",
204
205	FileReadBinary                                => "file:readBinary",
206
207	FileReaddir                                   => "file:readdir",
208
209	FileReadFile                                  => "file:readFile",
210
211	FileRealpath                                  => "file:realpath",
212
213	FileRename                                    => "file:rename",
214
215	FileStat                                      => "file:stat",
216
217	FileUnwatch                                   => "file:unwatch",
218
219	FileWatch                                     => "file:watch",
220
221	FileWrite                                     => "file:write",
222
223	FileWriteBinary                               => "file:writeBinary",
224
225	FileWriteFile                                 => "file:writeFile",
226
227	// --- Git (renderer `localGit` channel; stock VS Code names it
228	//          `ILocalGitService`, shared-process wire "localGit"). Land
229	//          routes each method to a Mountain subprocess handler that
230	//          spawns native `git`.
231	GitCancel                                     => "git:cancel",
232
233	GitCheckout                                   => "git:checkout",
234
235	GitClone                                      => "git:clone",
236
237	GitExec                                       => "git:exec",
238
239	GitFetch                                      => "git:fetch",
240
241	GitIsAvailable                                => "git:isAvailable",
242
243	GitPull                                       => "git:pull",
244
245	GitRevListCount                               => "git:revListCount",
246
247	GitRevParse                                   => "git:revParse",
248
249	// --- History ---
250	HistoryCanGoBack                              => "history:canGoBack",
251
252	HistoryCanGoForward                           => "history:canGoForward",
253
254	HistoryClear                                  => "history:clear",
255
256	HistoryGetStack                               => "history:getStack",
257
258	HistoryGoBack                                 => "history:goBack",
259
260	HistoryGoForward                              => "history:goForward",
261
262	HistoryPush                                   => "history:push",
263
264	// --- Keybindings ---
265	KeybindingAdd                                 => "keybinding:add",
266
267	KeybindingGetAll                              => "keybinding:getAll",
268
269	KeybindingLookup                              => "keybinding:lookup",
270
271	KeybindingRemove                              => "keybinding:remove",
272
273	// --- Labels ---
274	LabelGetBase                                  => "label:getBase",
275
276	LabelGetURI                                   => "label:getUri",
277
278	LabelGetWorkspace                             => "label:getWorkspace",
279
280	// --- Lifecycle ---
281	LifecycleAdvancePhase                         => "lifecycle:advancePhase",
282
283	LifecycleGetPhase                             => "lifecycle:getPhase",
284
285	LifecycleRequestShutdown                      => "lifecycle:requestShutdown",
286
287	LifecycleSetPhase                             => "lifecycle:setPhase",
288
289	LifecycleWhenPhase                            => "lifecycle:whenPhase",
290
291	// --- Log (legacy / short) ---
292	LogCreateLogger                               => "log:createLogger",
293
294	LogRegisterLogger                             => "log:registerLogger",
295
296	// --- Logger (current) ---
297	LoggerCreateLogger                            => "logger:createLogger",
298
299	LoggerCritical                                => "logger:critical",
300
301	LoggerDebug                                   => "logger:debug",
302
303	LoggerDeregisterLogger                        => "logger:deregisterLogger",
304
305	LoggerError                                   => "logger:error",
306
307	LoggerFlush                                   => "logger:flush",
308
309	LoggerGetLevel                                => "logger:getLevel",
310
311	LoggerGetRegisteredLoggers                    => "logger:getRegisteredLoggers",
312
313	LoggerInfo                                    => "logger:info",
314
315	LoggerLog                                     => "logger:log",
316
317	LoggerRegisterLogger                          => "logger:registerLogger",
318
319	LoggerSetLevel                                => "logger:setLevel",
320
321	LoggerSetVisibility                           => "logger:setVisibility",
322
323	LoggerTrace                                   => "logger:trace",
324
325	LoggerWarn                                    => "logger:warn",
326
327	// --- Menubar ---
328	MenubarUpdateMenubar                          => "menubar:updateMenubar",
329
330	// --- Model ---
331	ModelClose                                    => "model:close",
332
333	ModelGet                                      => "model:get",
334
335	ModelGetAll                                   => "model:getAll",
336
337	ModelOpen                                     => "model:open",
338
339	ModelUpdateContent                            => "model:updateContent",
340
341	// --- Native host ---
342	NativeOpenExternal                            => "native:openExternal",
343
344	NativeShowItemInFolder                        => "native:showItemInFolder",
345
346	// --- Notifications ---
347	NotificationEndProgress                       => "notification:endProgress",
348
349	NotificationShow                              => "notification:show",
350
351	NotificationShowProgress                      => "notification:showProgress",
352
353	NotificationUpdateProgress                    => "notification:updateProgress",
354
355	// --- Output channel ---
356	OutputAppend                                  => "output:append",
357
358	OutputAppendLine                              => "output:appendLine",
359
360	OutputClear                                   => "output:clear",
361
362	OutputCreate                                  => "output:create",
363
364	OutputShow                                    => "output:show",
365
366	// --- Progress ---
367	ProgressBegin                                 => "progress:begin",
368
369	ProgressEnd                                   => "progress:end",
370
371	ProgressReport                                => "progress:report",
372
373	// --- Search ---
374	SearchFindFiles                               => "search:findFiles",
375
376	SearchFindInFiles                             => "search:findInFiles",
377
378	// --- Storage ---
379	StorageClose                                  => "storage:close",
380
381	StorageDelete                                 => "storage:delete",
382
383	StorageGet                                    => "storage:get",
384
385	StorageGetItems                               => "storage:getItems",
386
387	StorageIsUsed                                 => "storage:isUsed",
388
389	StorageKeys                                   => "storage:keys",
390
391	StorageOptimize                               => "storage:optimize",
392
393	StorageSet                                    => "storage:set",
394
395	StorageUpdateItems                            => "storage:updateItems",
396
397	// Storage event-channel stubs (ack-only; delivery via Tauri events).
398	StorageOnDidChangeItems                       => "storage:onDidChangeItems",
399
400	StorageLogStorage                             => "storage:logStorage",
401
402	// --- QuickInput (vscode.window.showQuickPick / showInputBox) ---
403	QuickInputShowInputBox                        => "quickInput:showInputBox",
404
405	QuickInputShowQuickPick                       => "quickInput:showQuickPick",
406
407	// --- TextFile (editor working-copy surface) ---
408	TextFileRead                                  => "textFile:read",
409
410	TextFileWrite                                 => "textFile:write",
411
412	TextFileSave                                  => "textFile:save",
413
414	// --- WorkingCopy (dirty-state tracking) ---
415	WorkingCopyGetAllDirty                        => "workingCopy:getAllDirty",
416
417	WorkingCopyGetDirtyCount                      => "workingCopy:getDirtyCount",
418
419	WorkingCopyIsDirty                            => "workingCopy:isDirty",
420
421	WorkingCopySetDirty                           => "workingCopy:setDirty",
422
423	// --- Terminal ---
424	TerminalCreate                                => "terminal:create",
425
426	TerminalDispose                               => "terminal:dispose",
427
428	TerminalHide                                  => "terminal:hide",
429
430	TerminalSendText                              => "terminal:sendText",
431
432	TerminalShow                                  => "terminal:show",
433
434	// --- Themes ---
435	ThemesGetActive                               => "themes:getActive",
436
437	ThemesGetColorTheme                           => "themes:getColorTheme",
438
439	ThemesList                                    => "themes:list",
440
441	ThemesSet                                    => "themes:set",
442
443	// --- Update ---
444	UpdateApplyUpdate                             => "update:applyUpdate",
445
446	UpdateCheckForUpdates                         => "update:checkForUpdates",
447
448	UpdateDownloadUpdate                          => "update:downloadUpdate",
449
450	UpdateIsLatestVersion                         => "update:isLatestVersion",
451
452	UpdateQuitAndInstall                          => "update:quitAndInstall",
453
454	// --- URL handlers ---
455	URLRegisterExternalURIOpener                  => "url:registerExternalUriOpener",
456
457	// --- Workbench ---
458	WorkbenchGetConfiguration                     => "workbench:getConfiguration",
459
460	// --- Workspaces ---
461	WorkspacesAddFolder                           => "workspaces:addFolder",
462
463	WorkspacesAddRecentlyOpened                   => "workspaces:addRecentlyOpened",
464
465	WorkspacesClearRecentlyOpened                 => "workspaces:clearRecentlyOpened",
466
467	WorkspacesCreateUntitledWorkspace             => "workspaces:createUntitledWorkspace",
468
469	WorkspacesDeleteUntitledWorkspace             => "workspaces:deleteUntitledWorkspace",
470
471	WorkspacesEnterWorkspace                      => "workspaces:enterWorkspace",
472
473	WorkspacesGetDirtyWorkspaces                  => "workspaces:getDirtyWorkspaces",
474
475	WorkspacesGetFolders                          => "workspaces:getFolders",
476
477	WorkspacesGetName                             => "workspaces:getName",
478
479	WorkspacesGetRecentlyOpened                   => "workspaces:getRecentlyOpened",
480
481	WorkspacesGetWorkspaceIdentifier              => "workspaces:getWorkspaceIdentifier",
482
483	WorkspacesRemoveFolder                        => "workspaces:removeFolder",
484
485	WorkspacesRemoveRecentlyOpened                => "workspaces:removeRecentlyOpened",
486
487	// Workspace event-channel stubs (ack-only; delivery via Tauri events).
488	WorkspacesOnDidChangeWorkspaceFolders         => "workspaces:onDidChangeWorkspaceFolders",
489
490	WorkspacesOnDidChangeWorkspaceName            => "workspaces:onDidChangeWorkspaceName",
491
492	// Additional VS Code workspace service methods.
493	WorkspacesGetWorkspace                        => "workspaces:getWorkspace",
494
495	WorkspacesGetWorkspaceFolders                 => "workspaces:getWorkspaceFolders",
496
497	WorkspacesAddWorkspaceFolders                 => "workspaces:addWorkspaceFolders",
498
499	WorkspacesRemoveWorkspaceFolders              => "workspaces:removeWorkspaceFolders",
500
501	// --- Language features (dispatched to Cocoon via NodeDeferred) ---
502	// These reach Mountain from Wind's TauriMainProcessService via the
503	// `languages` channel prefix. Mountain's mod.rs defers them to Cocoon
504	// when TierIPC=NodeDeferred. They are registered here so the Channel
505	// registry logs "registered, no dispatch arm" instead of "Unknown".
506	LanguagesGetAll                               => "languages:getAll",
507
508	LanguagesGetEncodedLanguageId                 => "languages:getEncodedLanguageId",
509
510	// --- SCM (Source Control Management) ---
511	ScmCreateSourceControl                        => "scm:createSourceControl",
512
513	ScmGetSourceControls                          => "scm:getSourceControls",
514
515	ScmSetActiveProvider                          => "scm:setActiveProvider",
516
517	// --- Debug ---
518	DebugStartDebugging                           => "debug:startDebugging",
519
520	DebugStopDebugging                            => "debug:stopDebugging",
521
522	DebugGetSessions                              => "debug:getSessions",
523
524	DebugGetBreakpoints                           => "debug:getBreakpoints",
525
526	DebugAddBreakpoints                           => "debug:addBreakpoints",
527
528	DebugRemoveBreakpoints                        => "debug:removeBreakpoints",
529
530	// --- Tasks ---
531	TasksExecuteTask                              => "tasks:executeTask",
532
533	TasksGetTasks                                 => "tasks:getTasks",
534
535	TasksGetTaskExecution                         => "tasks:getTaskExecution",
536
537	// --- Authentication ---
538	AuthGetSessions                               => "auth:getSessions",
539
540	AuthCreateSession                             => "auth:createSession",
541
542	AuthRemoveSession                             => "auth:removeSession",
543
544	// --- Legacy wire-shape channels (non prefix:method) ---
545	// Two historical groups predate the `prefix:method` convention:
546	//   1. `UserInterface.Show*Dialog` - dotted names mirrored from
547	//      Cocoon→Mountain gRPC; the Wind-side Files/Live.ts routes them
548	//      through Tauri IPC today. Rename target: `dialog:showOpen` /
549	//      `dialog:showSave`.
550	//   2. `mountain_get_status` - snake_case Tauri command.
551	// Grouped at the tail so the eventual rename is a single block move.
552	MountainGetStatus                             => "mountain_get_status",
553
554	UserInterfaceShowOpenDialog                   => "UserInterface.ShowOpenDialog",
555
556	UserInterfaceShowSaveDialog                   => "UserInterface.ShowSaveDialog",
557}
558
559impl Channel {
560	/// Echo scheduler lane for this channel. See module-level docs for the
561	/// classification rationale.
562	pub fn Priority(&self) -> ChannelPriority {
563		use Channel::*;
564
565		match self {
566			// --- Direct user action → High ---
567			CommandsExecute
568			| CocoonExtensionHostMessage
569			| ExtensionsInstall
570			| ExtensionsUninstall
571			| ExtensionsReinstall
572			| FileRead
573			| FileReadBinary
574			| FileReadFile
575			| FileStat
576			| FileExists
577			| FileOpen
578			| FileWrite
579			| FileWriteBinary
580			| FileWriteFile
581			| FileDelete
582			| FileCopy
583			| FileMove
584			| FileRename
585			| FileMkdir
586			| KeybindingLookup
587			| MenubarUpdateMenubar
588			| ModelUpdateContent
589			| NativeOpenExternal
590			| NativeShowItemInFolder
591			| NotificationShow
592			| NotificationShowProgress
593			| NotificationUpdateProgress
594			| NotificationEndProgress
595			| TerminalCreate
596			| TerminalSendText
597			| TerminalShow
598			| TerminalHide
599			| TerminalDispose
600			| WorkspacesEnterWorkspace
601			| WorkspacesAddFolder
602			| WorkspacesRemoveFolder
603			| WorkspacesCreateUntitledWorkspace
604			| WorkspacesDeleteUntitledWorkspace => ChannelPriority::High,
605
606			// --- Background / deferrable → Low ---
607			GitClone
608			| GitFetch
609			| GitPull
610			| GitRevListCount
611			| SearchFindFiles
612			| SearchFindInFiles
613			| LogCreateLogger
614			| LogRegisterLogger
615			| LoggerCreateLogger
616			| LoggerCritical
617			| LoggerDebug
618			| LoggerDeregisterLogger
619			| LoggerError
620			| LoggerFlush
621			| LoggerGetLevel
622			| LoggerGetRegisteredLoggers
623			| LoggerInfo
624			| LoggerLog
625			| LoggerRegisterLogger
626			| LoggerSetLevel
627			| LoggerSetVisibility
628			| LoggerTrace
629			| LoggerWarn
630			| StorageOptimize
631			| UpdateCheckForUpdates
632			| UpdateDownloadUpdate
633			| UpdateApplyUpdate
634			| UpdateIsLatestVersion
635			| UpdateQuitAndInstall
636			| ExtensionsQuery
637			| ExtensionsGetRecommendations
638			| ExtensionsGetExtensions
639			| ExtensionsGetExtensionsControlManifest
640			| ExtensionsGetUninstalled
641			| ExtensionsUpdateMetadata
642			| DiagnosticLog
643			| StorageOnDidChangeItems
644			| StorageLogStorage
645			| WorkspacesOnDidChangeWorkspaceFolders
646			| WorkspacesOnDidChangeWorkspaceName => ChannelPriority::Low,
647
648			// --- Everything else → Normal ---
649			_ => ChannelPriority::Normal,
650		}
651	}
652}
653
654#[cfg(test)]
655mod Tests {
656
657	use std::str::FromStr;
658
659	use super::{Channel, ChannelPriority};
660
661	#[test]
662	fn RoundTrip() {
663		for Variant in Channel::All() {
664			let Wire = Variant.AsStr();
665
666			let Parsed = Channel::from_str(Wire).expect("round-trip");
667
668			assert_eq!(*Variant, Parsed, "{} failed round-trip", Wire);
669		}
670	}
671
672	#[test]
673	fn PriorityIsTotal() {
674		// Every variant must match one of three classes; the `_` fallback
675		// returning Normal guarantees totality, so this test just runs the
676		// mapping on every variant to catch any future match panic.
677		for Variant in Channel::All() {
678			let _Class = Variant.Priority();
679		}
680	}
681
682	#[test]
683	fn UserActionIsHigh() {
684		assert_eq!(Channel::CommandsExecute.Priority(), ChannelPriority::High);
685
686		assert_eq!(Channel::ExtensionsInstall.Priority(), ChannelPriority::High);
687
688		assert_eq!(Channel::TerminalSendText.Priority(), ChannelPriority::High);
689	}
690
691	#[test]
692	fn BackgroundIsLow() {
693		assert_eq!(Channel::SearchFindInFiles.Priority(), ChannelPriority::Low);
694
695		assert_eq!(Channel::LoggerInfo.Priority(), ChannelPriority::Low);
696	}
697
698	#[test]
699	fn RejectsUnknown() {
700		assert!(Channel::from_str("nope:nope").is_err());
701
702		assert!(Channel::from_str("").is_err());
703	}
704
705	#[test]
706	fn UniqueWireStrings() {
707		let mut Seen = std::collections::HashSet::new();
708
709		for Variant in Channel::All() {
710			assert!(Seen.insert(Variant.AsStr()), "duplicate wire: {}", Variant.AsStr());
711		}
712	}
713}