Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/IPC/WindServiceHandlers/Terminal/
ReviveTerminalProcesses.rs

1#![allow(non_snake_case)]
2
3//! Revive serialised terminal processes after a window reload.
4//!
5//! VS Code calls `localPty:reviveTerminalProcesses` with the array previously
6//! returned by `localPty:serializeTerminalState`. Each entry describes a shell
7//! that was running before the reload; Mountain respawns each one and emits a
8//! `sky://terminal/create` event so the xterm panel re-binds.
9//!
10//! ## Wire shape (Arguments[0])
11//! ```json
12//! [
13//!   {
14//!     "id": 1,
15//!     "shellLaunchConfig": { "executable": "/bin/zsh", "args": [], "cwd": "/Users/..." },
16//!     "processDetails":    { "cwd": "/Users/...", "pid": 1234, "title": "zsh" }
17//!   }
18//! ]
19//! ```
20//!
21//! Arguments[1] is the locale string used for date formatting in VS Code's UI;
22//! Mountain ignores it.
23//!
24//! ## Behaviour
25//! - Each entry is forwarded to `TerminalCreate` with `{ shellPath, cwd, name
26//!   }`.
27//! - The newly allocated terminal ID (assigned by Mountain's atomic counter) is
28//!   returned alongside the requested ID so the workbench can remap its
29//!   internal `_ptys` table.
30//! - Entries whose `shellLaunchConfig.executable` is empty are skipped to avoid
31//!   spawning a headless PTY that would immediately exit.
32
33use std::sync::Arc;
34
35use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
36use serde_json::{Value, json};
37
38use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
39
40pub async fn ReviveTerminalProcesses(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
41	let States:Vec<Value> = match Arguments.first() {
42		Some(Value::Array(Array)) => Array.clone(),
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		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			Err(Error) => {
105				dev_log!(
106					"terminal",
107					"warn: [ReviveTerminalProcesses] failed to revive terminal: {}",
108					Error
109				);
110			},
111		}
112	}
113
114	Ok(Value::Null)
115}