which_terminal/
lib.rs

1mod exec;
2mod types;
3mod utils;
4
5/// Unified terminal detection for all platforms
6///
7/// This module consolidates detection logic for all platforms to handle
8/// cross-platform scenarios (e.g., SSH connections, remote terminals)
9///
10/// Detection priority:
11/// 1. TERM_PROGRAM (most reliable)
12/// 2. Highly specific environment variables (WT_SESSION, TERMUX_VERSION, etc.)
13/// 3. Terminal-specific environment variables
14/// 4. TERM environment variable patterns
15/// 5. Generic fallback
16use crate::exec::extract;
17use crate::types::{Terminal, TerminalInfo};
18use std::path::Path;
19
20/// Detects the terminal using a comprehensive strategy
21pub fn which_terminal() -> Option<TerminalInfo> {
22    // Priority 1: Check TERM_PROGRAM first (most reliable cross-platform method)
23    if let Some(info) = detect_by_term_program() {
24        return Some(info);
25    }
26
27    // Priority 2: Check highly specific environment variables
28    if let Some(info) = detect_by_specific_env_vars() {
29        return Some(info);
30    }
31
32    // Priority 3: Check terminal-specific environment variables
33    if let Some(info) = detect_by_terminal_env_vars() {
34        return Some(info);
35    }
36
37    // Priority 4: Check TERM environment variable patterns
38    if let Some(info) = detect_by_term_patterns() {
39        return Some(info);
40    }
41
42    // Priority 5: Generic fallback
43    detect_generic()
44}
45
46/// Detects terminal using TERM_PROGRAM environment variable (highest priority)
47fn detect_by_term_program() -> Option<TerminalInfo> {
48    let term_program = utils::get_env("TERM_PROGRAM")?;
49    let version = utils::get_env("TERM_PROGRAM_VERSION");
50
51    let terminal = match term_program.as_str() {
52        // macOS native
53        "Apple_Terminal" => Terminal::AppleTerminal,
54        "iTerm.app" => Terminal::ITerm2,
55
56        // Cross-platform popular terminals (sorted by popularity)
57        "vscode" => Terminal::VSCode,
58        "kiro" => Terminal::Kiro,
59        "WezTerm" => Terminal::WezTerm,
60        "Hyper" => Terminal::Hyper,
61        "Tabby" => Terminal::Tabby,
62
63        // Unknown TERM_PROGRAM
64        _ => return None,
65    };
66
67    Some(TerminalInfo::with_version(terminal, version))
68}
69
70/// Detects terminal using highly specific environment variables (second priority)
71/// These are unique identifiers that are very reliable
72fn detect_by_specific_env_vars() -> Option<TerminalInfo> {
73    // Windows Terminal (very specific)
74    if utils::has_env("WT_SESSION") || utils::has_env("WT_PROFILE_ID") {
75        let version = extract(&Terminal::WindowsTerminal);
76        return Some(TerminalInfo::with_version(
77            Terminal::WindowsTerminal,
78            version,
79        ));
80    }
81
82    // Termux (Android) - very specific
83    if let Some(version) = utils::get_env("TERMUX_VERSION") {
84        return Some(TerminalInfo::with_version(Terminal::Termux, Some(version)));
85    }
86
87    // iTerm2 session ID (macOS)
88    if utils::has_env("ITERM_SESSION_ID") {
89        let version = extract(&Terminal::ITerm2);
90        return Some(TerminalInfo::with_version(Terminal::ITerm2, version));
91    }
92
93    // ConEmu (Windows)
94    if utils::has_env("ConEmuPID") || utils::has_env("ConEmuBuild") {
95        let version = utils::get_env("ConEmuBuild");
96        return Some(TerminalInfo::with_version(Terminal::ConEmu, version));
97    }
98
99    // Kitty (cross-platform)
100    if utils::has_env("KITTY_WINDOW_ID") {
101        let version = extract(&Terminal::Kitty);
102        return Some(TerminalInfo::with_version(Terminal::Kitty, version));
103    }
104
105    None
106}
107
108/// Detects terminal using terminal-specific environment variables (third priority)
109fn detect_by_terminal_env_vars() -> Option<TerminalInfo> {
110    // GNOME Terminal (Linux)
111    if utils::has_env("GNOME_TERMINAL_SERVICE") || utils::has_env("GNOME_TERMINAL_SCREEN") {
112        let version = extract(&Terminal::GnomeTerminal);
113        return Some(TerminalInfo::with_version(Terminal::GnomeTerminal, version));
114    }
115
116    // Konsole (Linux/KDE)
117    if utils::has_env("KONSOLE_VERSION") || utils::has_env("KONSOLE_DBUS_SESSION") {
118        let version = utils::get_env("KONSOLE_VERSION").or_else(|| extract(&Terminal::Konsole));
119        return Some(TerminalInfo::with_version(Terminal::Konsole, version));
120    }
121
122    // Terminator (Linux)
123    if utils::has_env("TERMINATOR_UUID") {
124        return Some(TerminalInfo::new(Terminal::Terminator));
125    }
126
127    // Tilix (Linux)
128    if utils::has_env("TILIX_ID") {
129        let version = extract(&Terminal::Tilix);
130        return Some(TerminalInfo::with_version(Terminal::Tilix, version));
131    }
132
133    // Cmder (Windows)
134    if utils::has_env("CMDER_ROOT") {
135        return Some(TerminalInfo::new(Terminal::Cmder));
136    }
137
138    // Termux via PREFIX (Android)
139    if let Some(prefix) = utils::get_env("PREFIX")
140        && prefix.contains("com.termux")
141    {
142        let version = extract(&Terminal::Termux);
143        return Some(TerminalInfo::with_version(Terminal::Termux, version));
144    }
145
146    // Termux installation directory (Android)
147    if Path::new("/data/data/com.termux").exists() {
148        let version = extract(&Terminal::Termux);
149        return Some(TerminalInfo::with_version(Terminal::Termux, version));
150    }
151
152    None
153}
154
155/// Detects terminal using TERM environment variable patterns (fourth priority)
156fn detect_by_term_patterns() -> Option<TerminalInfo> {
157    let term = utils::get_env("TERM")?;
158
159    // Alacritty
160    if term.contains("alacritty") {
161        let version = extract(&Terminal::Alacritty);
162        return Some(TerminalInfo::with_version(Terminal::Alacritty, version));
163    }
164
165    // Kitty
166    if term.contains("kitty") {
167        let version = extract(&Terminal::Kitty);
168        return Some(TerminalInfo::with_version(Terminal::Kitty, version));
169    }
170
171    // XTerm
172    if term.contains("xterm") {
173        let version = extract(&Terminal::XTerm);
174        return Some(TerminalInfo::with_version(Terminal::XTerm, version));
175    }
176
177    // Rxvt
178    if term.contains("rxvt") {
179        return Some(TerminalInfo::new(Terminal::Rxvt));
180    }
181
182    // PowerShell (Windows) - check after TERM patterns
183    if utils::has_env("PSModulePath") {
184        return Some(TerminalInfo::new(Terminal::PowerShell));
185    }
186
187    // Command Prompt (Windows) - lowest priority Windows terminal
188    if utils::has_env("COMSPEC") && !utils::has_env("PSModulePath") {
189        return Some(TerminalInfo::new(Terminal::CommandPrompt));
190    }
191
192    None
193}
194
195/// Generic fallback detection using TERM environment variable
196fn detect_generic() -> Option<TerminalInfo> {
197    if let Some(term) = utils::get_env("TERM") {
198        let terminal = Terminal::Generic(term.clone());
199        Some(TerminalInfo::new(terminal))
200    } else {
201        None
202    }
203}