Add auto option for config.use_ansi_coloring (#14647)

<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

In this PR I continued the idea of #11494, it added an `auto` option to
the ansi coloring config option, I did this too but in a more simple
approach.

So I added a new enum `UseAnsiColoring` with the three values `True`,
`False` and `Auto`. When that value is set to `auto`, the default value,
it will use `std::io::stdout().is_terminal()` to decided whether to use
ansi coloring. This allows to dynamically decide whether to print ansi
color codes or not, [cargo does it the same
way](652623b779/src/bin/cargo/main.rs (L72)).
`True` and `False` act as overrides to the `is_terminal` check. So with
that PR it is possible to force ansi colors on the `table` command or
automatically remove them from the miette errors if no terminal is used.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

Terminal users shouldn't be affected by this change as the default value
was `true` and `is_terminal` returns for terminals `true` (duh).
Non-terminal users, that use `nu` in some embedded way or the engine
implemented in some other way (like my jupyter kernel) will now have by
default no ansi coloring and need to enable it manually if their
environment allows it.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

The test for fancy errors expected ansi codes, since tests aren't run
"in terminal", the ansi codes got stripped away.
I added a line that forced ansi colors above it. I'm not sure if that
should be the case or if we should test against no ansi colors.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

This should resolve #11464 and partially #11847. This also closes
#11494.
This commit is contained in:
Piepmatz 2024-12-26 18:00:01 +01:00 committed by GitHub
parent 38694a9850
commit 5f3c8d45d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 411 additions and 58 deletions

View File

@ -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;

View File

@ -49,7 +49,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
perf!(
"add plugin file to engine_state",
start_time,
engine_state.get_config().use_ansi_coloring
engine_state
.get_config()
.use_ansi_coloring
.get(engine_state)
);
start_time = std::time::Instant::now();
@ -129,7 +132,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
perf!(
&format!("read plugin file {}", plugin_path.display()),
start_time,
engine_state.get_config().use_ansi_coloring
engine_state
.get_config()
.use_ansi_coloring
.get(engine_state)
);
start_time = std::time::Instant::now();
@ -145,7 +151,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
perf!(
&format!("load plugin file {}", plugin_path.display()),
start_time,
engine_state.get_config().use_ansi_coloring
engine_state
.get_config()
.use_ansi_coloring
.get(engine_state)
);
}
}
@ -345,7 +354,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState) -> 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
}

View File

@ -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()));

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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(

View File

@ -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 =

View File

@ -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 {

View File

@ -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<usize>,
use_ansi_coloring: bool,
}
impl TableConfig {
@ -240,6 +236,7 @@ impl TableConfig {
theme: TableMode,
abbreviation: Option<usize>,
index: Option<usize>,
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<Option<String>, ShellError>,
config: &Config,
cfg: &TableConfig,
signals: &Signals,
term_width: usize,
) -> Option<Result<Vec<u8>, 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

View File

@ -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

View File

@ -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<bool> 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<Self, ShellError> {
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));
}
}

View File

@ -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(),

View File

@ -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 {

View File

@ -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;

View File

@ -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<bool> {
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);
}
}

View File

@ -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"
);
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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();