diff --git a/crates/nu-cli/src/completions/completion_common.rs b/crates/nu-cli/src/completions/completion_common.rs index 4b5ef857e1..4969cbd51d 100644 --- a/crates/nu-cli/src/completions/completion_common.rs +++ b/crates/nu-cli/src/completions/completion_common.rs @@ -196,14 +196,14 @@ pub fn complete_item( .map(|cwd| Path::new(cwd.as_ref()).to_path_buf()) .collect(); let ls_colors = (engine_state.config.completions.use_ls_colors - && engine_state.config.use_ansi_coloring) - .then(|| { - let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") { - Some(v) => env_to_string("LS_COLORS", v, engine_state, stack).ok(), - None => None, - }; - get_ls_colors(ls_colors_env_str) - }); + && engine_state.config.use_ansi_coloring.get(engine_state)) + .then(|| { + let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") { + Some(v) => env_to_string("LS_COLORS", v, engine_state, stack).ok(), + None => None, + }; + get_ls_colors(ls_colors_env_str) + }); let mut cwds = cwd_pathbufs.clone(); let mut prefix_len = 0; diff --git a/crates/nu-cli/src/config_files.rs b/crates/nu-cli/src/config_files.rs index 7627252acc..28332fe998 100644 --- a/crates/nu-cli/src/config_files.rs +++ b/crates/nu-cli/src/config_files.rs @@ -49,7 +49,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option bool { perf!( "migrate old plugin file", start_time, - engine_state.get_config().use_ansi_coloring + engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state) ); true } diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 06ecd1d49a..19f4aba22f 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -61,7 +61,7 @@ pub fn evaluate_repl( // from the Arc. This lets us avoid copying stack variables needlessly let mut unique_stack = stack.clone(); let config = engine_state.get_config(); - let use_color = config.use_ansi_coloring; + let use_color = config.use_ansi_coloring.get(engine_state); confirm_stdin_is_terminal()?; @@ -390,7 +390,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { ))) .with_quick_completions(config.completions.quick) .with_partial_completions(config.completions.partial) - .with_ansi_colors(config.use_ansi_coloring) + .with_ansi_colors(config.use_ansi_coloring.get(engine_state)) .with_cwd(Some( engine_state .cwd(None) @@ -410,7 +410,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { let style_computer = StyleComputer::from_config(engine_state, &stack_arc); start_time = std::time::Instant::now(); - line_editor = if config.use_ansi_coloring { + line_editor = if config.use_ansi_coloring.get(engine_state) { line_editor.with_hinter(Box::new({ // As of Nov 2022, "hints" color_config closures only get `null` passed in. let style = style_computer.compute("hints", &Value::nothing(Span::unknown())); diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index 50db15a68e..6b0bade8c6 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -265,7 +265,10 @@ pub fn eval_source( perf!( &format!("eval_source {}", &fname), start_time, - engine_state.get_config().use_ansi_coloring + engine_state + .get_config() + .use_ansi_coloring + .get(engine_state) ); exit_code diff --git a/crates/nu-command/src/help/help_aliases.rs b/crates/nu-command/src/help/help_aliases.rs index 0d6d4b4f15..0e5241e6a9 100644 --- a/crates/nu-command/src/help/help_aliases.rs +++ b/crates/nu-command/src/help/help_aliases.rs @@ -144,7 +144,7 @@ pub fn help_aliases( long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}")); let config = stack.get_config(engine_state); - if !config.use_ansi_coloring { + if !config.use_ansi_coloring.get(engine_state) { long_desc = nu_utils::strip_ansi_string_likely(long_desc); } diff --git a/crates/nu-command/src/help/help_modules.rs b/crates/nu-command/src/help/help_modules.rs index 579bd3f768..84fff36dfb 100644 --- a/crates/nu-command/src/help/help_modules.rs +++ b/crates/nu-command/src/help/help_modules.rs @@ -231,7 +231,7 @@ pub fn help_modules( } let config = stack.get_config(engine_state); - if !config.use_ansi_coloring { + if !config.use_ansi_coloring.get(engine_state) { long_desc = nu_utils::strip_ansi_string_likely(long_desc); } diff --git a/crates/nu-command/src/platform/ansi/ansi_.rs b/crates/nu-command/src/platform/ansi/ansi_.rs index fe66cb9e15..4e40f7df3f 100644 --- a/crates/nu-command/src/platform/ansi/ansi_.rs +++ b/crates/nu-command/src/platform/ansi/ansi_.rs @@ -654,7 +654,10 @@ Operating system commands: let list: bool = call.has_flag(engine_state, stack, "list")?; let escape: bool = call.has_flag(engine_state, stack, "escape")?; let osc: bool = call.has_flag(engine_state, stack, "osc")?; - let use_ansi_coloring = stack.get_config(engine_state).use_ansi_coloring; + let use_ansi_coloring = stack + .get_config(engine_state) + .use_ansi_coloring + .get(engine_state); if list { return Ok(generate_ansi_code_list( @@ -691,7 +694,10 @@ Operating system commands: let list: bool = call.has_flag_const(working_set, "list")?; let escape: bool = call.has_flag_const(working_set, "escape")?; let osc: bool = call.has_flag_const(working_set, "osc")?; - let use_ansi_coloring = working_set.get_config().use_ansi_coloring; + let use_ansi_coloring = working_set + .get_config() + .use_ansi_coloring + .get(working_set.permanent()); if list { return Ok(generate_ansi_code_list( diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index e3d74c055b..304b0fff8d 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,7 +1,9 @@ use nu_cmd_base::hook::eval_hook; use nu_engine::{command_prelude::*, env_to_strings}; use nu_path::{dots::expand_ndots, expand_tilde, AbsolutePath}; -use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals}; +use nu_protocol::{ + did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals, UseAnsiColoring, +}; use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; use pathdiff::diff_paths; @@ -417,7 +419,7 @@ fn write_pipeline_data( stack.start_collect_value(); // Turn off color as we pass data through - Arc::make_mut(&mut engine_state.config).use_ansi_coloring = false; + Arc::make_mut(&mut engine_state.config).use_ansi_coloring = UseAnsiColoring::False; // Invoke the `table` command. let output = diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 23b8259ddd..336026fa38 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -72,7 +72,7 @@ prints out the list properly."# None => None, }; - let use_color: bool = color_param && config.use_ansi_coloring; + let use_color: bool = color_param && config.use_ansi_coloring.get(engine_state); let cwd = engine_state.cwd(Some(stack))?; match input { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 4f74e4b0a8..3c699f52df 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -15,12 +15,7 @@ use nu_table::{ StringResult, TableOpts, TableOutput, }; use nu_utils::{get_ls_colors, terminal_size}; -use std::{ - collections::VecDeque, - io::{IsTerminal, Read}, - path::PathBuf, - str::FromStr, -}; +use std::{collections::VecDeque, io::Read, path::PathBuf, str::FromStr}; use url::Url; use web_time::Instant; @@ -231,6 +226,7 @@ struct TableConfig { term_width: usize, theme: TableMode, abbreviation: Option, + use_ansi_coloring: bool, } impl TableConfig { @@ -240,6 +236,7 @@ impl TableConfig { theme: TableMode, abbreviation: Option, index: Option, + use_ansi_coloring: bool, ) -> Self { Self { index, @@ -247,6 +244,7 @@ impl TableConfig { term_width, abbreviation, theme, + use_ansi_coloring, } } } @@ -280,12 +278,15 @@ fn parse_table_config( let term_width = get_width_param(width_param); + let use_ansi_coloring = state.get_config().use_ansi_coloring.get(state); + Ok(TableConfig::new( table_view, term_width, theme, abbrivation, index, + use_ansi_coloring, )) } @@ -563,7 +564,7 @@ fn handle_record( let result = build_table_kv(record, cfg.table_view, opts, span)?; let result = match result { - Some(output) => maybe_strip_color(output, &config), + Some(output) => maybe_strip_color(output, cfg.use_ansi_coloring), None => report_unsuccessful_output(input.engine_state.signals(), cfg.term_width), }; @@ -947,14 +948,9 @@ impl Iterator for PagingTableCreator { self.row_offset += batch_size; - let config = { - let state = &self.engine_state; - let stack = &self.stack; - stack.get_config(state) - }; convert_table_to_output( table, - &config, + &self.cfg, self.engine_state.signals(), self.cfg.term_width, ) @@ -1116,9 +1112,8 @@ enum TableView { }, } -fn maybe_strip_color(output: String, config: &Config) -> String { - // the terminal is for when people do ls from vim, there should be no coloring there - if !config.use_ansi_coloring || !std::io::stdout().is_terminal() { +fn maybe_strip_color(output: String, use_ansi_coloring: bool) -> String { + if !use_ansi_coloring { // Draw the table without ansi colors nu_utils::strip_ansi_string_likely(output) } else { @@ -1154,13 +1149,13 @@ fn create_empty_placeholder( fn convert_table_to_output( table: Result, ShellError>, - config: &Config, + cfg: &TableConfig, signals: &Signals, term_width: usize, ) -> Option, ShellError>> { match table { Ok(Some(table)) => { - let table = maybe_strip_color(table, config); + let table = maybe_strip_color(table, cfg.use_ansi_coloring); let mut bytes = table.as_bytes().to_vec(); bytes.push(b'\n'); // nu-table tables don't come with a newline on the end diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 81add7af4d..4598e1b325 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -258,7 +258,7 @@ fn get_documentation( long_desc.push_str(" "); long_desc.push_str(example.description); - if !nu_config.use_ansi_coloring { + if !nu_config.use_ansi_coloring.get(engine_state) { let _ = write!(long_desc, "\n > {}\n", example.example); } else { let code_string = nu_highlight_string(example.example, engine_state, stack); @@ -329,7 +329,7 @@ fn get_documentation( long_desc.push('\n'); - if !nu_config.use_ansi_coloring { + if !nu_config.use_ansi_coloring.get(engine_state) { nu_utils::strip_ansi_string_likely(long_desc) } else { long_desc diff --git a/crates/nu-protocol/src/config/ansi_coloring.rs b/crates/nu-protocol/src/config/ansi_coloring.rs new file mode 100644 index 0000000000..408eee52cc --- /dev/null +++ b/crates/nu-protocol/src/config/ansi_coloring.rs @@ -0,0 +1,248 @@ +use super::{ConfigErrors, ConfigPath, IntoValue, ShellError, UpdateFromValue, Value}; +use crate::{self as nu_protocol, engine::EngineState, FromValue}; +use serde::{Deserialize, Serialize}; +use std::io::IsTerminal; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, IntoValue, Serialize, Deserialize)] +pub enum UseAnsiColoring { + #[default] + Auto, + True, + False, +} + +impl UseAnsiColoring { + /// Determines whether ANSI colors should be used. + /// + /// This method evaluates the `UseAnsiColoring` setting and considers environment variables + /// (`FORCE_COLOR`, `NO_COLOR`, and `CLICOLOR`) when the value is set to `Auto`. + /// The configuration value (`UseAnsiColoring`) takes precedence over environment variables, as + /// it is more direct and internally may be modified to override ANSI coloring behavior. + /// + /// Most users should have the default value `Auto` which allows the environment variables to + /// control ANSI coloring. + /// However, when explicitly set to `True` or `False`, the environment variables are ignored. + /// + /// Behavior based on `UseAnsiColoring`: + /// - `True`: Forces ANSI colors to be enabled, ignoring terminal support and environment variables. + /// - `False`: Disables ANSI colors completely. + /// - `Auto`: Determines whether ANSI colors should be used based on environment variables and terminal support. + /// + /// When set to `Auto`, the following environment variables are checked in order: + /// 1. `FORCE_COLOR`: If set, ANSI colors are always enabled, overriding all other settings. + /// 2. `NO_COLOR`: If set, ANSI colors are disabled, overriding `CLICOLOR` and terminal checks. + /// 3. `CLICOLOR`: If set, its value determines whether ANSI colors are enabled (`1` for enabled, `0` for disabled). + /// + /// If none of these variables are set, ANSI coloring is enabled only if the standard output is + /// a terminal. + /// + /// By prioritizing the `UseAnsiColoring` value, we ensure predictable behavior and prevent + /// conflicts with internal overrides that depend on this configuration. + pub fn get(self, engine_state: &EngineState) -> bool { + let is_terminal = match self { + Self::Auto => std::io::stdout().is_terminal(), + Self::True => return true, + Self::False => return false, + }; + + let env_value = |env_name| { + engine_state + .get_env_var_insensitive(env_name) + .and_then(Value::as_env_bool) + .unwrap_or(false) + }; + + if env_value("force_color") { + return true; + } + + if env_value("no_color") { + return false; + } + + if let Some(cli_color) = engine_state.get_env_var_insensitive("clicolor") { + if let Some(cli_color) = cli_color.as_env_bool() { + return cli_color; + } + } + + is_terminal + } +} + +impl From for UseAnsiColoring { + fn from(value: bool) -> Self { + match value { + true => Self::True, + false => Self::False, + } + } +} + +impl FromValue for UseAnsiColoring { + fn from_value(v: Value) -> Result { + if let Ok(v) = v.as_bool() { + return Ok(v.into()); + } + + #[derive(FromValue)] + enum UseAnsiColoringString { + Auto = 0, + True = 1, + False = 2, + } + + Ok(match UseAnsiColoringString::from_value(v)? { + UseAnsiColoringString::Auto => Self::Auto, + UseAnsiColoringString::True => Self::True, + UseAnsiColoringString::False => Self::False, + }) + } +} + +impl UpdateFromValue for UseAnsiColoring { + fn update<'a>( + &mut self, + value: &'a Value, + path: &mut ConfigPath<'a>, + errors: &mut ConfigErrors, + ) { + let Ok(value) = UseAnsiColoring::from_value(value.clone()) else { + errors.type_mismatch(path, UseAnsiColoring::expected_type(), value); + return; + }; + + *self = value; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nu_protocol::Config; + + fn set_env(engine_state: &mut EngineState, name: &str, value: bool) { + engine_state.add_env_var(name.to_string(), Value::test_bool(value)); + } + + #[test] + fn test_use_ansi_coloring_true() { + let mut engine_state = EngineState::new(); + engine_state.config = Config { + use_ansi_coloring: UseAnsiColoring::True, + ..Default::default() + } + .into(); + + // explicit `True` ignores environment variables + assert!(engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + + set_env(&mut engine_state, "clicolor", false); + assert!(engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + set_env(&mut engine_state, "clicolor", true); + assert!(engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + set_env(&mut engine_state, "no_color", true); + assert!(engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + set_env(&mut engine_state, "force_color", true); + assert!(engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + } + + #[test] + fn test_use_ansi_coloring_false() { + let mut engine_state = EngineState::new(); + engine_state.config = Config { + use_ansi_coloring: UseAnsiColoring::False, + ..Default::default() + } + .into(); + + // explicit `False` ignores environment variables + assert!(!engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + + set_env(&mut engine_state, "clicolor", false); + assert!(!engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + set_env(&mut engine_state, "clicolor", true); + assert!(!engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + set_env(&mut engine_state, "no_color", true); + assert!(!engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + set_env(&mut engine_state, "force_color", true); + assert!(!engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + } + + #[test] + fn test_use_ansi_coloring_auto() { + let mut engine_state = EngineState::new(); + engine_state.config = Config { + use_ansi_coloring: UseAnsiColoring::Auto, + ..Default::default() + } + .into(); + + // no environment variables, behavior depends on terminal state + let is_terminal = std::io::stdout().is_terminal(); + assert_eq!( + engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state), + is_terminal + ); + + // `clicolor` determines ANSI behavior if no higher-priority variables are set + set_env(&mut engine_state, "clicolor", true); + assert!(engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + + set_env(&mut engine_state, "clicolor", false); + assert!(!engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + + // `no_color` overrides `clicolor` and terminal state + set_env(&mut engine_state, "no_color", true); + assert!(!engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + + // `force_color` overrides everything + set_env(&mut engine_state, "force_color", true); + assert!(engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state)); + } +} diff --git a/crates/nu-protocol/src/config/mod.rs b/crates/nu-protocol/src/config/mod.rs index d2ee40a2a0..6ce7335e9c 100644 --- a/crates/nu-protocol/src/config/mod.rs +++ b/crates/nu-protocol/src/config/mod.rs @@ -6,6 +6,7 @@ use helper::*; use prelude::*; use std::collections::HashMap; +pub use ansi_coloring::UseAnsiColoring; pub use completions::{ CompletionAlgorithm, CompletionConfig, CompletionSort, ExternalCompleterConfig, }; @@ -23,6 +24,7 @@ pub use rm::RmConfig; pub use shell_integration::ShellIntegrationConfig; pub use table::{FooterMode, TableConfig, TableIndexMode, TableMode, TrimStrategy}; +mod ansi_coloring; mod completions; mod datetime_format; mod display_errors; @@ -49,7 +51,7 @@ pub struct Config { pub footer_mode: FooterMode, pub float_precision: i64, pub recursion_limit: i64, - pub use_ansi_coloring: bool, + pub use_ansi_coloring: UseAnsiColoring, pub completions: CompletionConfig, pub edit_mode: EditBindings, pub history: HistoryConfig, @@ -106,7 +108,7 @@ impl Default for Config { footer_mode: FooterMode::RowCount(25), float_precision: 2, buffer_editor: Value::nothing(Span::unknown()), - use_ansi_coloring: true, + use_ansi_coloring: UseAnsiColoring::default(), bracketed_paste: true, edit_mode: EditBindings::default(), diff --git a/crates/nu-protocol/src/config/output.rs b/crates/nu-protocol/src/config/output.rs index 8db3467d6e..082856bdce 100644 --- a/crates/nu-protocol/src/config/output.rs +++ b/crates/nu-protocol/src/config/output.rs @@ -1,5 +1,6 @@ use super::{config_update_string_enum, prelude::*}; -use crate as nu_protocol; + +use crate::{self as nu_protocol}; #[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)] pub enum ErrorStyle { diff --git a/crates/nu-protocol/src/errors/cli_error.rs b/crates/nu-protocol/src/errors/cli_error.rs index 5680ab172c..ecf35e247d 100644 --- a/crates/nu-protocol/src/errors/cli_error.rs +++ b/crates/nu-protocol/src/errors/cli_error.rs @@ -70,7 +70,7 @@ impl std::fmt::Debug for CliError<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let config = self.1.get_config(); - let ansi_support = config.use_ansi_coloring; + let ansi_support = config.use_ansi_coloring.get(self.1.permanent()); let error_style = &config.error_style; diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index c12dd3d71d..ac663f4c49 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -655,6 +655,36 @@ impl Value { } } + /// Interprets this `Value` as a boolean based on typical conventions for environment values. + /// + /// The following rules are used: + /// - Values representing `false`: + /// - Empty strings + /// - The number `0` (as an integer, float or string) + /// - `Nothing` + /// - Explicit boolean `false` + /// - Values representing `true`: + /// - Non-zero numbers (integer or float) + /// - Non-empty strings + /// - Explicit boolean `true` + /// + /// For all other, more complex variants of [`Value`], the function cannot determine a + /// boolean representation and returns `None`. + pub fn as_env_bool(&self) -> Option { + match self { + Value::Bool { val: false, .. } + | Value::Int { val: 0, .. } + | Value::Float { val: 0.0, .. } + | Value::Nothing { .. } => Some(false), + Value::String { val, .. } => match val.as_str() { + "" | "0" => Some(false), + _ => Some(true), + }, + Value::Bool { .. } | Value::Int { .. } | Value::Float { .. } => Some(true), + _ => None, + } + } + /// Returns a reference to the inner [`CustomValue`] trait object or an error if this `Value` is not a custom value pub fn as_custom_value(&self) -> Result<&dyn CustomValue, ShellError> { if let Value::Custom { val, .. } = self { @@ -3874,4 +3904,43 @@ mod tests { assert_eq!("-0316-02-11T06:13:20+00:00", formatted); } } + + #[test] + fn test_env_as_bool() { + // explicit false values + assert_eq!(Value::test_bool(false).as_env_bool(), Some(false)); + assert_eq!(Value::test_int(0).as_env_bool(), Some(false)); + assert_eq!(Value::test_float(0.0).as_env_bool(), Some(false)); + assert_eq!(Value::test_string("").as_env_bool(), Some(false)); + assert_eq!(Value::test_string("0").as_env_bool(), Some(false)); + assert_eq!(Value::test_nothing().as_env_bool(), Some(false)); + + // explicit true values + assert_eq!(Value::test_bool(true).as_env_bool(), Some(true)); + assert_eq!(Value::test_int(1).as_env_bool(), Some(true)); + assert_eq!(Value::test_float(1.0).as_env_bool(), Some(true)); + assert_eq!(Value::test_string("1").as_env_bool(), Some(true)); + + // implicit true values + assert_eq!(Value::test_int(42).as_env_bool(), Some(true)); + assert_eq!(Value::test_float(0.5).as_env_bool(), Some(true)); + assert_eq!(Value::test_string("not zero").as_env_bool(), Some(true)); + + // complex values returning None + assert_eq!(Value::test_record(Record::default()).as_env_bool(), None); + assert_eq!( + Value::test_list(vec![Value::test_int(1)]).as_env_bool(), + None + ); + assert_eq!( + Value::test_date( + chrono::DateTime::parse_from_rfc3339("2024-01-01T12:00:00+00:00").unwrap(), + ) + .as_env_bool(), + None + ); + assert_eq!(Value::test_glob("*.rs").as_env_bool(), None); + assert_eq!(Value::test_binary(vec![1, 2, 3]).as_env_bool(), None); + assert_eq!(Value::test_duration(3600).as_env_bool(), None); + } } diff --git a/crates/nu-protocol/tests/test_config.rs b/crates/nu-protocol/tests/test_config.rs index eb57903685..81a0a7dede 100644 --- a/crates/nu-protocol/tests/test_config.rs +++ b/crates/nu-protocol/tests/test_config.rs @@ -53,6 +53,7 @@ fn filesize_format_auto_metric_false() { #[test] fn fancy_default_errors() { let code = nu_repl_code(&[ + "$env.config.use_ansi_coloring = true", r#"def force_error [x] { error make { msg: "oh no!" @@ -69,7 +70,7 @@ fn fancy_default_errors() { assert_eq!( actual.err, - "Error: \n \u{1b}[31m×\u{1b}[0m oh no!\n ╭─[\u{1b}[36;1;4mline1:1:13\u{1b}[0m]\n \u{1b}[2m1\u{1b}[0m │ force_error \"My error\"\n · \u{1b}[35;1m ─────┬────\u{1b}[0m\n · \u{1b}[35;1m╰── \u{1b}[35;1mhere's the error\u{1b}[0m\u{1b}[0m\n ╰────\n\n" + "Error: \n \u{1b}[31m×\u{1b}[0m oh no!\n ╭─[\u{1b}[36;1;4mline2:1:13\u{1b}[0m]\n \u{1b}[2m1\u{1b}[0m │ force_error \"My error\"\n · \u{1b}[35;1m ─────┬────\u{1b}[0m\n · \u{1b}[35;1m╰── \u{1b}[35;1mhere's the error\u{1b}[0m\u{1b}[0m\n ╰────\n\n" ); } diff --git a/crates/nu-utils/src/default_files/doc_config.nu b/crates/nu-utils/src/default_files/doc_config.nu index db83e11110..08de7724f2 100644 --- a/crates/nu-utils/src/default_files/doc_config.nu +++ b/crates/nu-utils/src/default_files/doc_config.nu @@ -244,12 +244,20 @@ $env.config.shell_integration.reset_application_mode = true # Nushell. $env.config.bracketed_paste = true -# use_ansi_coloring (bool): -# true/false to enable/disable the use of ANSI colors in Nushell internal commands. -# When disabled, output from Nushell built-in commands will display only in the default -# foreground color. -# Note: Does not apply to the `ansi` command. -$env.config.use_ansi_coloring = true +# use_ansi_coloring ("auto" or bool): +# The default value `"auto"` dynamically determines if ANSI coloring is used. +# It evaluates the following environment variables in decreasingly priority: +# `FORCE_COLOR`, `NO_COLOR`, and `CLICOLOR`. +# - If `FORCE_COLOR` is set, coloring is always enabled. +# - If `NO_COLOR` is set, coloring is disabled. +# - If `CLICOLOR` is set, its value (0 or 1) decides whether coloring is used. +# If none of these are set, it checks whether the standard output is a terminal +# and enables coloring if it is. +# A value of `true` or `false` overrides this behavior, explicitly enabling or +# disabling ANSI coloring in Nushell's internal commands. +# When disabled, built-in commands will only use the default foreground color. +# Note: This setting does not affect the `ansi` command. +$env.config.use_ansi_coloring = "auto" # ---------------------- # Error Display Settings diff --git a/src/config_files.rs b/src/config_files.rs index 5c43764580..6a612ea4a7 100644 --- a/src/config_files.rs +++ b/src/config_files.rs @@ -39,7 +39,7 @@ pub(crate) fn read_config_file( let start_time = std::time::Instant::now(); let config = engine_state.get_config(); - let use_color = config.use_ansi_coloring; + let use_color = config.use_ansi_coloring.get(engine_state); // Translate environment variables from Strings to Values if let Err(e) = convert_env_values(engine_state, stack) { report_shell_error(engine_state, &e); @@ -53,7 +53,7 @@ pub(crate) fn read_config_file( } else { let start_time = std::time::Instant::now(); let config = engine_state.get_config(); - let use_color = config.use_ansi_coloring; + let use_color = config.use_ansi_coloring.get(engine_state); if let Err(e) = convert_env_values(engine_state, stack) { report_shell_error(engine_state, &e); } diff --git a/src/main.rs b/src/main.rs index 4ebc215531..bfae9c2f31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -213,7 +213,10 @@ fn main() -> Result<()> { engine_state.history_enabled = parsed_nu_cli_args.no_history.is_none(); - let use_color = engine_state.get_config().use_ansi_coloring; + let use_color = engine_state + .get_config() + .use_ansi_coloring + .get(&engine_state); // Set up logger if let Some(level) = parsed_nu_cli_args diff --git a/src/run.rs b/src/run.rs index 89bad3866f..640cc9bc35 100644 --- a/src/run.rs +++ b/src/run.rs @@ -197,7 +197,10 @@ pub(crate) fn run_repl( } // Reload use_color from config in case it's different from the default value - let use_color = engine_state.get_config().use_ansi_coloring; + let use_color = engine_state + .get_config() + .use_ansi_coloring + .get(engine_state); perf!("setup_config", start_time, use_color); let start_time = std::time::Instant::now();