Skip to main content

Mountain/IPC/WindServiceHandlers/Terminal/
ReviveTerminalProcesses.rs

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