ralph_workflow/json_parser/
terminal.rs1use crate::logger::ColorEnvironment;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum TerminalMode {
11 Full,
13 Basic,
15 None,
17}
18
19impl TerminalMode {
20 pub fn detect_with_env(env: &dyn ColorEnvironment) -> Self {
22 if env.get_var("NO_COLOR").is_some() {
23 return Self::None;
24 }
25 if let Some(val) = env.get_var("CLICOLOR_FORCE") {
26 if val != "0" {
27 return if env.is_terminal() {
28 Self::Full
29 } else {
30 Self::Basic
31 };
32 }
33 }
34 if env.get_var("CLICOLOR").as_deref() == Some("0") {
35 return Self::None;
36 }
37 if !env.is_terminal() {
38 return Self::None;
39 }
40 classify_term_var(env.get_var("TERM").as_deref())
41 }
42
43 #[must_use]
45 pub fn detect() -> Self {
46 Self::detect_with_env(&RealTerminalEnvironment)
47 }
48}
49
50impl Default for TerminalMode {
51 fn default() -> Self {
52 Self::detect()
53 }
54}
55
56fn classify_term_var(term: Option<&str>) -> TerminalMode {
57 let Some(term_str) = term else {
58 return TerminalMode::Basic;
59 };
60 let term_lower = term_str.to_lowercase();
61 if term_lower == "dumb" {
62 return TerminalMode::Basic;
63 }
64 const CAPABLE_TERMINALS: &[&str] = &[
65 "xterm",
66 "vt100",
67 "vt102",
68 "vt220",
69 "vt320",
70 "screen",
71 "tmux",
72 "ansi",
73 "rxvt",
74 "konsole",
75 "gnome-terminal",
76 "iterm",
77 "alacritty",
78 "kitty",
79 "wezterm",
80 "foot",
81 ];
82 if CAPABLE_TERMINALS
83 .iter()
84 .any(|capable| term_lower.starts_with(capable))
85 {
86 TerminalMode::Full
87 } else {
88 TerminalMode::Basic
89 }
90}
91
92include!("terminal/io.rs");
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use std::collections::HashMap;
99
100 struct MockTerminalEnv {
101 vars: HashMap<String, String>,
102 is_tty: bool,
103 }
104
105 impl MockTerminalEnv {
106 fn new() -> Self {
107 Self {
108 vars: HashMap::new(),
109 is_tty: true,
110 }
111 }
112
113 fn with_var(mut self, name: &str, value: &str) -> Self {
114 self.vars.insert(name.to_string(), value.to_string());
115 self
116 }
117
118 fn not_tty(mut self) -> Self {
119 self.is_tty = false;
120 self
121 }
122 }
123
124 impl ColorEnvironment for MockTerminalEnv {
125 fn get_var(&self, name: &str) -> Option<String> {
126 self.vars.get(name).cloned()
127 }
128
129 fn is_terminal(&self) -> bool {
130 self.is_tty
131 }
132 }
133
134 #[test]
135 fn test_terminal_mode_no_color() {
136 let env = MockTerminalEnv::new().with_var("NO_COLOR", "1");
137 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::None);
138 }
139
140 #[test]
141 fn test_terminal_mode_clicolor_force_tty() {
142 let env = MockTerminalEnv::new().with_var("CLICOLOR_FORCE", "1");
143 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Full);
144 }
145
146 #[test]
147 fn test_terminal_mode_clicolor_force_not_tty() {
148 let env = MockTerminalEnv::new()
149 .with_var("CLICOLOR_FORCE", "1")
150 .not_tty();
151 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Basic);
152 }
153
154 #[test]
155 fn test_terminal_mode_clicolor_zero() {
156 let env = MockTerminalEnv::new().with_var("CLICOLOR", "0");
157 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::None);
158 }
159
160 #[test]
161 fn test_terminal_mode_term_dumb() {
162 let env = MockTerminalEnv::new().with_var("TERM", "dumb");
163 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Basic);
164 }
165
166 #[test]
167 fn test_terminal_mode_term_xterm() {
168 let env = MockTerminalEnv::new().with_var("TERM", "xterm-256color");
169 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Full);
170 }
171
172 #[test]
173 fn test_terminal_mode_not_tty() {
174 let env = MockTerminalEnv::new().not_tty();
175 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::None);
176 }
177
178 #[test]
179 fn test_terminal_mode_unknown_term() {
180 let env = MockTerminalEnv::new().with_var("TERM", "unknown-terminal");
181 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Basic);
182 }
183
184 #[test]
185 fn test_terminal_mode_partial_eq() {
186 assert_eq!(TerminalMode::Full, TerminalMode::Full);
187 assert_eq!(TerminalMode::Basic, TerminalMode::Basic);
188 assert_eq!(TerminalMode::None, TerminalMode::None);
189 assert_ne!(TerminalMode::Full, TerminalMode::Basic);
190 }
191}