Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/IPC/WindServiceHandlers/Terminal/
ReviveTerminalProcesses.rs

1//! Revive serialised terminal processes after a window reload.
2//!
3//! VS Code calls `localPty:reviveTerminalProcesses` with the array previously
4//! returned by `localPty:serializeTerminalState`. Each entry describes a shell
5//! that was running before the reload; Mountain respawns each one and emits a
6//! `sky://terminal/create` event so the xterm panel re-binds.
7//!
8//! ## Wire shape (Arguments\[0\])
9//! ```json
10//! [
11//!   {
12//!     "id": 1,
13//!     "shellLaunchConfig": { "executable": "/bin/zsh", "args": [], "cwd": "/Users/..." },
14//!     "processDetails":    { "cwd": "/Users/...", "pid": 1234, "title": "zsh" }
15//!   }
16//! ]
17//! ```
18//!
19//! Arguments\[1\] is the locale string used for date formatting in VS Code's
20//! UI; Mountain ignores it.
21//!
22//! ## Behaviour
23//! - Each entry is forwarded to `TerminalCreate` with `{ shellPath, cwd, name
24//!   }`.
25//! - The newly allocated terminal ID (assigned by Mountain's atomic counter) is
26//!   returned alongside the requested ID so the workbench can remap its
27//!   internal `_ptys` table.
28//! - Entries whose `shellLaunchConfig.executable` is empty are skipped to avoid
29//!   spawning a headless PTY that would immediately exit.
30
31use std::sync::Arc;
32
33use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
34use serde_json::{Value, json};
35
36use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
37
38pub async fn Fn(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
39	let States:Vec<Value> = match Arguments.first() {
40		Some(Value::Array(Array)) => Array.clone(),
41
42		Some(Other) => {
43			dev_log!(
44				"terminal",
45				"warn: [ReviveTerminalProcesses] unexpected argument shape: {:?}",
46				Other
47			);
48
49			return Ok(Value::Null);
50		},
51
52		None => return Ok(Value::Null),
53	};
54
55	if States.is_empty() {
56		return Ok(Value::Null);
57	}
58
59	dev_log!("terminal", "[ReviveTerminalProcesses] reviving {} terminals", States.len());
60
61	for State in &States {
62		let Config = State.get("shellLaunchConfig").cloned().unwrap_or(Value::Null);
63
64		let Executable = Config.get("executable").and_then(Value::as_str).unwrap_or("").to_string();
65
66		if Executable.is_empty() {
67			dev_log!(
68				"terminal",
69				"warn: [ReviveTerminalProcesses] skipping entry with empty executable"
70			);
71
72			continue;
73		}
74
75		let Cwd = Config
76			.get("cwd")
77			.and_then(Value::as_str)
78			.or_else(|| State.get("processDetails").and_then(|D| D.get("cwd")).and_then(Value::as_str))
79			.unwrap_or("")
80			.to_string();
81
82		let Name = Config
83			.get("name")
84			.and_then(Value::as_str)
85			.or_else(|| State.get("processDetails").and_then(|D| D.get("title")).and_then(Value::as_str))
86			.unwrap_or("terminal")
87			.to_string();
88
89		let ShellArgs:Vec<Value> = Config.get("args").and_then(Value::as_array).cloned().unwrap_or_default();
90
91		let Options = json!({
92			"shellPath": Executable,
93			"shellArgs": ShellArgs,
94			"cwd":       Cwd,
95			"name":      Name,
96		});
97
98		match RunTime.Environment.CreateTerminal(Options).await {
99			Ok(Response) => {
100				let NewId = Response.get("id").and_then(Value::as_u64).unwrap_or(0);
101
102				dev_log!("terminal", "[ReviveTerminalProcesses] revived terminal new_id={}", NewId);
103			},
104
105			Err(Error) => {
106				dev_log!(
107					"terminal",
108					"warn: [ReviveTerminalProcesses] failed to revive terminal: {}",
109					Error
110				);
111			},
112		}
113	}
114
115	Ok(Value::Null)
116}