Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Environment/
TerminalEnvCollection.rs

1//! Global registry of per-extension `EnvironmentVariableCollection`
2//! mutations.
3//!
4//! Each extension that calls `context.environmentVariableCollection.replace(
5//! variable, value)` lands a `Mutator` in this registry keyed by
6//! `(ExtensionId, VariableName)`. Every PTY spawn consults the registry
7//! and applies the accumulated mutations to the child env BEFORE the
8//! shell process is launched, so terminals created after an extension's
9//! activation observe the env it requested.
10//!
11//! Persistence: `persistent=true` mutations survive a window reload via
12//! Mountain's storage provider (key
13//! `__envCollection:<extensionId>`). Non-persistent mutations live only
14//! for the current session.
15//!
16//! Wire format matches VS Code's `EnvironmentVariableMutator`:
17//!   • Type::Replace (1) - set to `value`, ignore inherited
18//!   • Type::Append  (2) - `inherited + value`
19//!   • Type::Prepend (3) - `value + inherited`
20//!
21//! `applyAtProcessCreation` (default true) is the only application
22//! point in the Tauri+PTY world; the upstream `applyAtShellIntegration`
23//! second-chance hook is irrelevant when we already control the spawn.
24
25use std::{
26	collections::HashMap,
27	sync::{Mutex, OnceLock},
28};
29
30use serde_json::Value;
31
32#[derive(Clone, Copy, PartialEq, Eq, Debug)]
33pub enum MutatorType {
34	Replace = 1,
35	Append = 2,
36	Prepend = 3,
37}
38
39#[derive(Clone, Debug)]
40pub struct Mutator {
41	pub Variable:String,
42
43	pub Value:String,
44
45	pub Kind:MutatorType,
46}
47
48#[derive(Clone, Default)]
49pub struct ExtensionCollection {
50	pub Persistent:bool,
51
52	pub Description:Option<String>,
53
54	pub Mutators:HashMap<String, Mutator>,
55}
56
57static REGISTRY:OnceLock<Mutex<HashMap<String, ExtensionCollection>>> = OnceLock::new();
58
59fn Get() -> &'static Mutex<HashMap<String, ExtensionCollection>> { REGISTRY.get_or_init(|| Mutex::new(HashMap::new())) }
60
61pub fn Replace(ExtensionId:&str, Variable:String, Value:String) {
62	if let Ok(mut Guard) = Get().lock() {
63		let Entry = Guard.entry(ExtensionId.to_string()).or_default();
64
65		Entry
66			.Mutators
67			.insert(Variable.clone(), Mutator { Variable, Value, Kind:MutatorType::Replace });
68	}
69}
70
71pub fn Append(ExtensionId:&str, Variable:String, Value:String) {
72	if let Ok(mut Guard) = Get().lock() {
73		let Entry = Guard.entry(ExtensionId.to_string()).or_default();
74
75		Entry
76			.Mutators
77			.insert(Variable.clone(), Mutator { Variable, Value, Kind:MutatorType::Append });
78	}
79}
80
81pub fn Prepend(ExtensionId:&str, Variable:String, Value:String) {
82	if let Ok(mut Guard) = Get().lock() {
83		let Entry = Guard.entry(ExtensionId.to_string()).or_default();
84
85		Entry
86			.Mutators
87			.insert(Variable.clone(), Mutator { Variable, Value, Kind:MutatorType::Prepend });
88	}
89}
90
91pub fn Delete(ExtensionId:&str, Variable:&str) {
92	if let Ok(mut Guard) = Get().lock() {
93		if let Some(Entry) = Guard.get_mut(ExtensionId) {
94			Entry.Mutators.remove(Variable);
95		}
96	}
97}
98
99pub fn Clear(ExtensionId:&str) {
100	if let Ok(mut Guard) = Get().lock() {
101		if let Some(Entry) = Guard.get_mut(ExtensionId) {
102			Entry.Mutators.clear();
103		}
104	}
105}
106
107pub fn SetPersistent(ExtensionId:&str, Persistent:bool) {
108	if let Ok(mut Guard) = Get().lock() {
109		let Entry = Guard.entry(ExtensionId.to_string()).or_default();
110
111		Entry.Persistent = Persistent;
112	}
113}
114
115pub fn SetDescription(ExtensionId:&str, Description:Option<String>) {
116	if let Ok(mut Guard) = Get().lock() {
117		let Entry = Guard.entry(ExtensionId.to_string()).or_default();
118
119		Entry.Description = Description;
120	}
121}
122
123/// Apply every registered mutation across every extension to the supplied
124/// env map. Mutations are deterministic per (extensionId, variable);
125/// across extensions the order is iteration-stable but unordered, so
126/// avoid relying on cross-extension ordering for the same variable
127/// (matches VS Code's documented behavior).
128pub fn ApplyToEnv(Env:&mut HashMap<String, String>) {
129	let Snapshot = match Get().lock() {
130		Ok(Guard) => Guard.clone(),
131
132		Err(_) => return,
133	};
134
135	for (_ExtId, Collection) in Snapshot {
136		for Mut in Collection.Mutators.values() {
137			let Inherited = Env.get(&Mut.Variable).cloned().unwrap_or_default();
138
139			let Next = match Mut.Kind {
140				MutatorType::Replace => Mut.Value.clone(),
141
142				MutatorType::Append => format!("{}{}", Inherited, Mut.Value),
143
144				MutatorType::Prepend => format!("{}{}", Mut.Value, Inherited),
145			};
146
147			Env.insert(Mut.Variable.clone(), Next);
148		}
149	}
150}
151
152/// Parse the wire payload `{ extensionId, variable, value, persistent,
153/// description }` into a uniform tuple. Missing fields surface as empty
154/// strings / None; the dispatcher discards calls whose ExtensionId is
155/// empty.
156pub fn ParsePayload(Payload:&Value) -> (String, String, String) {
157	let ExtensionId = Payload.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
158
159	let Variable = Payload.get("variable").and_then(|V| V.as_str()).unwrap_or("").to_string();
160
161	let Value_ = Payload.get("value").and_then(|V| V.as_str()).unwrap_or("").to_string();
162
163	(ExtensionId, Variable, Value_)
164}