Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions crates/ruff/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ pub struct GlobalConfigArgs {
// we emit an error later on, after the initial parsing by clap.
#[arg(long, help_heading = "Global options", global = true)]
pub isolated: bool,

/// Control when colored output is used.
#[arg(
long,
value_name = "WHEN",
help_heading = "Global options",
global = true
)]
pub(crate) color: Option<TerminalColor>,
}

impl GlobalConfigArgs {
Expand All @@ -80,6 +89,20 @@ impl GlobalConfigArgs {
}
}

/// Control when colored output is used.
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)]
pub(crate) enum TerminalColor {
/// Display colors if the output goes to an interactive terminal.
#[default]
Auto,

/// Always display colors.
Always,

/// Never display colors.
Never,
}

// Configures Clap v3-style help menu colors
const STYLES: Styles = Styles::styled()
.header(AnsiColor::Green.on_default().effects(Effects::BOLD))
Expand Down
49 changes: 48 additions & 1 deletion crates/ruff/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(clippy::print_stdout)]

use std::ffi::OsString;
use std::fs::File;
use std::io::{self, BufWriter, Write, stdout};
use std::path::{Path, PathBuf};
Expand All @@ -21,7 +22,7 @@ use ruff_linter::{fs, warn_user, warn_user_once};
use ruff_workspace::Settings;

use crate::args::{
AnalyzeCommand, AnalyzeGraphCommand, Args, CheckCommand, Command, FormatCommand,
AnalyzeCommand, AnalyzeGraphCommand, Args, CheckCommand, Command, FormatCommand, TerminalColor,
};
use crate::printer::{Flags as PrinterFlags, Printer};

Expand Down Expand Up @@ -131,6 +132,13 @@ pub fn run(
global_options,
}: Args,
) -> Result<ExitStatus> {
// Set color before so all outputs are properly colored
if let Some(color_override) =
colored_override(global_options.color, std::env::var_os("FORCE_COLOR"))
{
colored::control::set_override(color_override);
}

{
ruff_db::set_program_version(crate::version::version().to_string()).unwrap();
let default_panic_hook = std::panic::take_hook();
Expand Down Expand Up @@ -514,6 +522,22 @@ https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
Ok(ExitStatus::Success)
}

fn colored_override(
color: Option<TerminalColor>,
env_force_color: Option<OsString>,
) -> Option<bool> {
match color {
// Cli arguments should take precedence over env vars.
Some(TerminalColor::Always) => Some(true),
Some(TerminalColor::Never) => Some(false),
// Default to no override, but respect FORCE_COLOR.
Some(TerminalColor::Auto) | None => {
// support FORCE_COLOR env var
env_force_color.map(|force_color: OsString| !force_color.is_empty())
}
}
}

#[cfg(test)]
mod test_file_change_detector {
use std::path::PathBuf;
Expand Down Expand Up @@ -634,3 +658,26 @@ mod test_file_change_detector {
);
}
}

#[cfg(test)]
mod test_set_colored_override {
Copy link
Member

@MichaReiser MichaReiser Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, we can't keep those unit tests as they'll mess up the colored output of other tests (they modify global state).

If you want to test this code, I suggest changing set_colored_override to colored_override(color_option: Option<TerminalColor>, force_color_env: Option<OsString>) -> Option<bool>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed this. Thanks for pointing this out!

use crate::{args::TerminalColor, colored_override};

#[test]
fn force_color_env_is_respected() {
assert_eq!(colored_override(None, Some("1".into())), Some(true));
}

#[test]
fn cli_args_takes_precedences_over_force_color_env() {
assert_eq!(
colored_override(Some(TerminalColor::Never), Some("1".into())),
Some(false)
);

assert_eq!(
colored_override(Some(TerminalColor::Always), None),
Some(true)
);
}
}
7 changes: 0 additions & 7 deletions crates/ruff/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ pub fn main() -> ExitCode {
#[cfg(windows)]
assert!(colored::control::set_virtual_terminal(true).is_ok());

// support FORCE_COLOR env var
if let Some(force_color) = std::env::var_os("FORCE_COLOR") {
if !force_color.is_empty() {
colored::control::set_override(true);
}
}

let args = wild::args_os();
let args = match argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX)
.context("Failed to read CLI arguments from files")
Expand Down
13 changes: 11 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ Commands:
help Print this message or the help of the given subcommand(s)

Options:
-h, --help Print help
-h, --help Print help (see more with '--help')
-V, --version Print version

Log levels:
Expand All @@ -559,6 +559,9 @@ Global options:
configuration files that were also specified using `--config`
--isolated
Ignore all configuration files
--color <WHEN>
Control when colored output is used [possible values: auto, always,
never]

For help with a specific command, see: `ruff help <command>`.
```
Expand Down Expand Up @@ -628,7 +631,7 @@ Options:
--show-settings
See the settings Ruff will use to lint a given Python file
-h, --help
Print help
Print help (see more with '--help')

Rule selection:
--select <RULE_CODE>
Expand Down Expand Up @@ -696,6 +699,9 @@ Global options:
configuration files that were also specified using `--config`
--isolated
Ignore all configuration files
--color <WHEN>
Control when colored output is used [possible values: auto, always,
never]
```

<!-- End auto-generated check help. -->
Expand Down Expand Up @@ -786,6 +792,9 @@ Global options:
configuration files that were also specified using `--config`
--isolated
Ignore all configuration files
--color <WHEN>
Control when colored output is used [possible values: auto, always,
never]
```

<!-- End auto-generated format help. -->
Expand Down