Skip to main content

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}