nu_protocol/config/
ansi_coloring.rs1use super::{ConfigErrors, ConfigPath, IntoValue, ShellError, UpdateFromValue, Value};
2use crate::{self as nu_protocol, FromValue, engine::EngineState};
3use serde::{Deserialize, Serialize};
4use std::io::IsTerminal;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, IntoValue, Serialize, Deserialize)]
7pub enum UseAnsiColoring {
8 #[default]
9 Auto,
10 True,
11 False,
12}
13
14impl UseAnsiColoring {
15 pub fn get(self, engine_state: &EngineState) -> bool {
42 let is_terminal = match self {
43 Self::Auto => std::io::stdout().is_terminal(),
44 Self::True => return true,
45 Self::False => return false,
46 };
47
48 let env_value = |env_name| {
49 engine_state
50 .get_env_var(env_name)
51 .and_then(|v| v.coerce_bool().ok())
52 .unwrap_or(false)
53 };
54
55 if env_value("force_color") {
56 return true;
57 }
58
59 if env_value("no_color") {
60 return false;
61 }
62
63 if let Some(cli_color) = engine_state.get_env_var("clicolor")
64 && let Ok(cli_color) = cli_color.coerce_bool()
65 {
66 return cli_color;
67 }
68
69 if let Some(term) = engine_state.get_env_var("term")
71 && term.as_str().ok() == Some("dumb")
72 {
73 return false;
74 }
75
76 is_terminal
77 }
78}
79
80impl From<bool> for UseAnsiColoring {
81 fn from(value: bool) -> Self {
82 match value {
83 true => Self::True,
84 false => Self::False,
85 }
86 }
87}
88
89impl FromValue for UseAnsiColoring {
90 fn from_value(v: Value) -> Result<Self, ShellError> {
91 if let Ok(v) = v.as_bool() {
92 return Ok(v.into());
93 }
94
95 #[derive(FromValue)]
96 enum UseAnsiColoringString {
97 Auto = 0,
98 True = 1,
99 False = 2,
100 }
101
102 Ok(match UseAnsiColoringString::from_value(v)? {
103 UseAnsiColoringString::Auto => Self::Auto,
104 UseAnsiColoringString::True => Self::True,
105 UseAnsiColoringString::False => Self::False,
106 })
107 }
108}
109
110impl UpdateFromValue for UseAnsiColoring {
111 fn update<'a>(
112 &mut self,
113 value: &'a Value,
114 path: &mut ConfigPath<'a>,
115 errors: &mut ConfigErrors,
116 ) {
117 let Ok(value) = UseAnsiColoring::from_value(value.clone()) else {
118 errors.type_mismatch(path, UseAnsiColoring::expected_type(), value);
119 return;
120 };
121
122 *self = value;
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use nu_protocol::Config;
130
131 fn set_env(engine_state: &mut EngineState, name: &str, value: bool) {
132 engine_state.add_env_var(name.to_string(), Value::test_bool(value));
133 }
134
135 #[test]
136 fn test_use_ansi_coloring_true() {
137 let mut engine_state = EngineState::new();
138 engine_state.config = Config {
139 use_ansi_coloring: UseAnsiColoring::True,
140 ..Default::default()
141 }
142 .into();
143
144 assert!(
146 engine_state
147 .get_config()
148 .use_ansi_coloring
149 .get(&engine_state)
150 );
151
152 set_env(&mut engine_state, "clicolor", false);
153 assert!(
154 engine_state
155 .get_config()
156 .use_ansi_coloring
157 .get(&engine_state)
158 );
159 set_env(&mut engine_state, "clicolor", true);
160 assert!(
161 engine_state
162 .get_config()
163 .use_ansi_coloring
164 .get(&engine_state)
165 );
166 set_env(&mut engine_state, "no_color", true);
167 assert!(
168 engine_state
169 .get_config()
170 .use_ansi_coloring
171 .get(&engine_state)
172 );
173 set_env(&mut engine_state, "force_color", true);
174 assert!(
175 engine_state
176 .get_config()
177 .use_ansi_coloring
178 .get(&engine_state)
179 );
180 }
181
182 #[test]
183 fn test_use_ansi_coloring_false() {
184 let mut engine_state = EngineState::new();
185 engine_state.config = Config {
186 use_ansi_coloring: UseAnsiColoring::False,
187 ..Default::default()
188 }
189 .into();
190
191 assert!(
193 !engine_state
194 .get_config()
195 .use_ansi_coloring
196 .get(&engine_state)
197 );
198
199 set_env(&mut engine_state, "clicolor", false);
200 assert!(
201 !engine_state
202 .get_config()
203 .use_ansi_coloring
204 .get(&engine_state)
205 );
206 set_env(&mut engine_state, "clicolor", true);
207 assert!(
208 !engine_state
209 .get_config()
210 .use_ansi_coloring
211 .get(&engine_state)
212 );
213 set_env(&mut engine_state, "no_color", true);
214 assert!(
215 !engine_state
216 .get_config()
217 .use_ansi_coloring
218 .get(&engine_state)
219 );
220 set_env(&mut engine_state, "force_color", true);
221 assert!(
222 !engine_state
223 .get_config()
224 .use_ansi_coloring
225 .get(&engine_state)
226 );
227 }
228
229 #[test]
230 fn test_use_ansi_coloring_auto() {
231 let mut engine_state = EngineState::new();
232 engine_state.config = Config {
233 use_ansi_coloring: UseAnsiColoring::Auto,
234 ..Default::default()
235 }
236 .into();
237
238 let is_terminal = std::io::stdout().is_terminal();
240 assert_eq!(
241 engine_state
242 .get_config()
243 .use_ansi_coloring
244 .get(&engine_state),
245 is_terminal
246 );
247
248 set_env(&mut engine_state, "clicolor", true);
250 assert!(
251 engine_state
252 .get_config()
253 .use_ansi_coloring
254 .get(&engine_state)
255 );
256
257 set_env(&mut engine_state, "clicolor", false);
258 assert!(
259 !engine_state
260 .get_config()
261 .use_ansi_coloring
262 .get(&engine_state)
263 );
264
265 set_env(&mut engine_state, "no_color", true);
267 assert!(
268 !engine_state
269 .get_config()
270 .use_ansi_coloring
271 .get(&engine_state)
272 );
273
274 set_env(&mut engine_state, "force_color", true);
276 assert!(
277 engine_state
278 .get_config()
279 .use_ansi_coloring
280 .get(&engine_state)
281 );
282 }
283
284 #[test]
285 fn test_use_ansi_coloring_auto_term_dumb() {
286 let mut engine_state = EngineState::new();
287 engine_state.config = Config {
288 use_ansi_coloring: UseAnsiColoring::Auto,
289 ..Default::default()
290 }
291 .into();
292
293 engine_state.add_env_var("term".to_string(), Value::test_string("dumb"));
295 assert!(
296 !engine_state
297 .get_config()
298 .use_ansi_coloring
299 .get(&engine_state)
300 );
301 }
302}