Skip to main content

Mountain/IPC/WindServiceHandlers/Terminal/
LocalPTYGetProfiles.rs

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