cc_switch/platform.rs
1//! Cross-platform helpers for binary resolution and terminal capability detection.
2//!
3//! `resolve_npm_cli` exists because npm-installed CLIs on Windows ship as `.cmd` or
4//! `.ps1` shims rather than `.exe`, and `std::process::Command::new("name")` does
5//! not always pick them up depending on how PATHEXT is configured.
6//!
7//! `unicode_support_enabled` centralizes the heuristic used by the interactive UI
8//! to decide between Unicode box-drawing and ASCII fallback.
9
10use std::path::PathBuf;
11
12/// Resolve a Node/npm-style CLI name to an executable path.
13///
14/// Resolution order:
15/// 1. Env var `<NAME_UPPERCASE>_BINARY` (e.g. `CLAUDE_BINARY`, `CODEX_BINARY`) —
16/// if set, returned verbatim. Lets users pin a specific binary.
17/// 2. On Windows: probe `<name>.exe`, then `<name>.cmd`, then `<name>.ps1` via
18/// the `which` crate (which respects PATH and is independent of cmd.exe's
19/// PATHEXT handling).
20/// 3. Fallback: return `PathBuf::from(name)` so `Command::new()` behaves
21/// identically to the pre-resolver code on platforms where probing finds
22/// nothing.
23pub fn resolve_npm_cli(name: &str) -> PathBuf {
24 let override_var = format!("{}_BINARY", name.to_uppercase());
25 if let Ok(path) = std::env::var(&override_var) {
26 return PathBuf::from(path);
27 }
28
29 #[cfg(windows)]
30 {
31 for ext in ["exe", "cmd", "ps1"] {
32 let candidate = format!("{name}.{ext}");
33 if let Ok(path) = which::which(&candidate) {
34 return path;
35 }
36 }
37 }
38
39 PathBuf::from(name)
40}
41
42/// Decide whether the terminal supports Unicode box-drawing characters.
43///
44/// Precedence (highest first):
45/// 1. `CC_SWITCH_ASCII=1` → force ASCII (escape hatch for any terminal).
46/// 2. `CC_SWITCH_UNICODE=1` → force Unicode (escape hatch for Windows users
47/// on terminals our heuristic can't detect).
48/// 3. On Windows: only enable Unicode if `WT_SESSION` is set (Windows
49/// Terminal). Default to ASCII to avoid mojibake on legacy conhost / CMD
50/// where the default codepage is not UTF-8.
51/// 4. On non-Windows: enabled by default. Modern Linux and macOS terminals
52/// universally support UTF-8; the `CC_SWITCH_ASCII=1` escape hatch above
53/// covers exceptions.
54pub fn unicode_support_enabled() -> bool {
55 if std::env::var("CC_SWITCH_ASCII").is_ok_and(|v| v == "1") {
56 return false;
57 }
58 if std::env::var("CC_SWITCH_UNICODE").is_ok_and(|v| v == "1") {
59 return true;
60 }
61
62 #[cfg(windows)]
63 {
64 std::env::var("WT_SESSION").is_ok()
65 }
66
67 #[cfg(not(windows))]
68 {
69 // Default to enabled — modern *nix terminals (Linux, macOS) universally
70 // support UTF-8. The CC_SWITCH_ASCII=1 escape hatch above covers exceptions.
71 true
72 }
73}