Skip to main content

Mountain/IPC/WindServiceHandlers/Sky/
ReplayEvents.rs

1#![allow(unused_variables, dead_code, unused_imports)]
2
3//! Wire method: `sky:replay-events`.
4//! Called by SkyBridge after every `sky://*` Tauri listener is installed.
5//! Mountain → Sky `app.emit()` events are NOT buffered: any emit fired before
6//! the listener was registered is silently dropped. In the bundled-electron
7//! profile, extension activation starts ~580 log lines before the Sky bundle
8//! finishes booting (~1995 lines). Without replay, all tree-view + SCM
9//! register events are lost and the Activity Bar comes up empty.
10//!
11//! Replays: tree-views, SCM providers, extension commands, active terminals
12//! (including buffered stdout from before SkyBridge's listeners were up).
13
14use std::sync::Arc;
15
16use serde_json::Value;
17use tauri::{AppHandle, Emitter};
18
19use crate::RunTime::ApplicationRunTime::ApplicationRunTime;
20
21pub async fn Fn(ApplicationHandle:AppHandle, RunTime:Arc<ApplicationRunTime>) -> Result<Value, String> {
22	let mut TreeViewCount:usize = 0;
23
24	let mut ScmCount:usize = 0;
25
26	let mut CommandCount:usize = 0;
27
28	let mut TerminalCount:usize = 0;
29
30	let mut TerminalDataBytes:usize = 0;
31
32	// ── Tree views ────────────────────────────────────────────────────────
33	if let Ok(TreeViews) = RunTime.Environment.ApplicationState.Feature.TreeViews.ActiveTreeViews.lock() {
34		for (ViewId, Dto) in TreeViews.iter() {
35			let Payload = serde_json::json!({
36				"viewId": ViewId,
37				"options": {
38					"canSelectMany": Dto.CanSelectMany,
39					"showCollapseAll": Dto.HasHandleDrag,
40					"title": Dto.Title.clone().unwrap_or_default(),
41				},
42			});
43
44			if ApplicationHandle.emit("sky://tree-view/create", Payload).is_ok() {
45				TreeViewCount += 1;
46			}
47		}
48	}
49
50	// ── SCM providers ─────────────────────────────────────────────────────
51	// Pre-DTO-Identifier-field DTOs default `Identifier` to "" (serde
52	// default); fall back to "git" - the only SCM provider in production
53	// today is `vscode.git` and a stale state file with empty id is the
54	// realistic upgrade-path mismatch.
55	if let Ok(ScmProviders) = RunTime
56		.Environment
57		.ApplicationState
58		.Feature
59		.Markers
60		.SourceControlManagementProviders
61		.lock()
62	{
63		for (Handle, Dto) in ScmProviders.iter() {
64			let RootUriStr = Dto
65				.RootURI
66				.as_ref()
67				.and_then(|V| V.get("external").or_else(|| V.get("path")))
68				.and_then(serde_json::Value::as_str)
69				.unwrap_or("")
70				.to_string();
71
72			let ScmId = if Dto.Identifier.is_empty() {
73				"git".to_string()
74			} else {
75				Dto.Identifier.clone()
76			};
77
78			let Payload = serde_json::json!({
79				"scmId": ScmId,
80				"label": Dto.Label,
81				"rootUri": RootUriStr,
82				"extensionId": "",
83				"handle": *Handle,
84			});
85
86			if ApplicationHandle.emit("sky://scm/register", Payload).is_ok() {
87				ScmCount += 1;
88			}
89		}
90	}
91
92	// ── Extension commands ────────────────────────────────────────────────
93	// Emit ONE batched event with the whole array. Per-command emits
94	// (one per registered command, ~1000+ during extension boot) saturate
95	// Tauri's shared WKWebView IPC channel and starve keystroke delivery.
96	// SkyBridge accepts `{ commands: [...] }` or `{ id, commandId, kind }`.
97	if let Ok(Commands) = RunTime.Environment.ApplicationState.Extension.Registry.CommandRegistry.lock() {
98		let mut Batch:Vec<serde_json::Value> = Vec::new();
99
100		for (CommandId, Handler) in Commands.iter() {
101			use crate::Environment::CommandProvider::CommandHandler;
102
103			let Kind = match Handler {
104				CommandHandler::Native(_) => continue,
105
106				CommandHandler::Proxied { .. } => "extension",
107			};
108
109			Batch.push(serde_json::json!({
110				"id": CommandId,
111				"commandId": CommandId,
112				"kind": Kind,
113			}));
114		}
115
116		if !Batch.is_empty() {
117			let Count = Batch.len();
118
119			if ApplicationHandle
120				.emit("sky://command/register", serde_json::json!({ "commands": Batch }))
121				.is_ok()
122			{
123				CommandCount = Count;
124			}
125		}
126	}
127
128	// ── Terminals + buffered stdout ───────────────────────────────────────
129	// Each active terminal needs its `create` event AND any buffered stdout
130	// the PTY reader produced before SkyBridge was up. Without this, the
131	// shell's first prompt is silently dropped and the user sees an empty
132	// terminal pane until they type.
133	if let Ok(Terminals) = RunTime.Environment.ApplicationState.Feature.Terminals.ActiveTerminals.lock() {
134		for (TerminalId, Arc) in Terminals.iter() {
135			let (Name, Pid) = if let Ok(State) = Arc.lock() {
136				(State.Name.clone(), State.OSProcessIdentifier.unwrap_or(0))
137			} else {
138				(String::new(), 0)
139			};
140
141			let CreatePayload = serde_json::json!({
142				"id": *TerminalId,
143				"name": Name,
144				"pid": Pid,
145			});
146
147			if ApplicationHandle.emit("sky://terminal/create", CreatePayload).is_ok() {
148				TerminalCount += 1;
149			}
150		}
151	}
152
153	for (TerminalId, Bytes) in crate::Environment::TerminalProvider::Fn() {
154		let DataString = String::from_utf8_lossy(&Bytes).to_string();
155
156		TerminalDataBytes += Bytes.len();
157
158		let _ = ApplicationHandle.emit(
159			"sky://terminal/data",
160			serde_json::json!({ "id": TerminalId, "data": DataString }),
161		);
162	}
163
164	crate::dev_log!(
165		"sky-emit",
166		"[SkyEmit] replay-events tree-views={} scm={} commands={} terminals={} terminal-bytes={}",
167		TreeViewCount,
168		ScmCount,
169		CommandCount,
170		TerminalCount,
171		TerminalDataBytes
172	);
173
174	Ok(serde_json::json!({
175		"treeViews": TreeViewCount,
176		"scmProviders": ScmCount,
177		"commands": CommandCount,
178		"terminals": TerminalCount,
179		"terminalDataBytes": TerminalDataBytes,
180	}))
181}