Skip to main content

Mountain/IPC/WindServiceHandlers/Terminal/
LocalPTYGetProfiles.rs

1#![allow(non_snake_case)]
2
3//! Discover available terminal profiles. Probes every well-
4//! known shell location plus `/etc/shells` (Unix) or known
5//! Windows install paths. The first existing match flags
6//! `isDefault=true`; on Unix the user's `$SHELL` wins.
7//!
8//! The wire shape matches VS Code's
9//! `ITerminalProfileProvider.profileName / path / Arguments /
10//! env / icon / isDefault` so Wind's terminal picker renders
11//! without reshaping.
12
13use serde_json::{Value, json};
14
15pub async fn LocalPTYGetProfiles() -> Result<Value, String> {
16	let mut Profiles = Vec::new();
17
18	#[cfg(unix)]
19	{
20		let DefaultShell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
21
22		let UnixShells = [
23			"/bin/zsh",
24			"/bin/bash",
25			"/bin/sh",
26			"/usr/bin/zsh",
27			"/usr/bin/bash",
28			"/usr/bin/fish",
29			"/usr/local/bin/fish",
30			"/usr/local/bin/zsh",
31			"/usr/local/bin/bash",
32			"/bin/dash",
33			"/usr/bin/ksh",
34			"/usr/bin/tcsh",
35			"/bin/csh",
36			"/usr/bin/pwsh",
37			"/usr/local/bin/pwsh",
38		];
39
40		for Shell in &UnixShells {
41			if std::path::Path::new(Shell).exists() {
42				let Name = std::path::Path::new(Shell)
43					.file_name()
44					.and_then(|N| N.to_str())
45					.unwrap_or("shell");
46
47				Profiles.push(json!({
48					"profileName": Name,
49					"path": Shell,
50					"isDefault": *Shell == DefaultShell.as_str(),
51					"Arguments": [],
52					"env": {},
53					"icon": "terminal"
54				}));
55			}
56		}
57
58		if let Ok(ShellsFile) = std::fs::read_to_string("/etc/shells") {
59			for Line in ShellsFile.lines() {
60				let Trimmed = Line.trim();
61				if Trimmed.starts_with('/') && !Trimmed.starts_with('#') {
62					let AlreadyAdded = Profiles.iter().any(|P| P.get("path").and_then(|V| V.as_str()) == Some(Trimmed));
63					if !AlreadyAdded && std::path::Path::new(Trimmed).exists() {
64						let Name = std::path::Path::new(Trimmed)
65							.file_name()
66							.and_then(|N| N.to_str())
67							.unwrap_or("shell");
68
69						Profiles.push(json!({
70							"profileName": Name,
71							"path": Trimmed,
72							"isDefault": Trimmed == DefaultShell.as_str(),
73							"Arguments": [],
74							"env": {},
75							"icon": "terminal"
76						}));
77					}
78				}
79			}
80		}
81	}
82
83	#[cfg(target_os = "windows")]
84	{
85		let SystemRoot = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
86		let ProgramFiles = std::env::var("ProgramFiles").unwrap_or_else(|_| "C:\\Program Files".to_string());
87		let LocalAppData =
88			std::env::var("LOCALAPPDATA").unwrap_or_else(|_| "C:\\Users\\User\\AppData\\Local".to_string());
89
90		let WindowsShells:Vec<(&str, String, Vec<&str>)> = vec![
91			(
92				"PowerShell",
93				format!("{}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", SystemRoot),
94				vec!["-NoLogo"],
95			),
96			(
97				"PowerShell 7",
98				format!("{}\\PowerShell\\7\\pwsh.exe", ProgramFiles),
99				vec!["-NoLogo"],
100			),
101			("Command Prompt", format!("{}\\System32\\cmd.exe", SystemRoot), vec![]),
102			(
103				"Git Bash",
104				format!("{}\\Git\\bin\\bash.exe", ProgramFiles),
105				vec!["--login", "-i"],
106			),
107			(
108				"Git Bash (User)",
109				format!("{}\\Programs\\Git\\bin\\bash.exe", LocalAppData),
110				vec!["--login", "-i"],
111			),
112			("WSL", format!("{}\\System32\\wsl.exe", SystemRoot), vec![]),
113			("MSYS2", "C:\\msys64\\usr\\bin\\bash.exe".to_string(), vec!["--login", "-i"]),
114			("Cygwin", "C:\\cygwin64\\bin\\bash.exe".to_string(), vec!["--login", "-i"]),
115		];
116
117		let mut IsFirstFound = true;
118		for (Name, Path, Args) in &WindowsShells {
119			if std::path::Path::new(Path).exists() {
120				Profiles.push(json!({
121					"profileName": Name,
122					"path": Path,
123					"isDefault": IsFirstFound,
124					"Arguments": Args,
125					"env": {},
126					"icon": "terminal"
127				}));
128				IsFirstFound = false;
129			}
130		}
131	}
132
133	Ok(json!(Profiles))
134}