diff --git a/Cargo.lock b/Cargo.lock index d3a0680b71b3c..3557557f6afb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1560,6 +1560,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owo-colors" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1997,6 +2003,7 @@ dependencies = [ name = "ruff" version = "0.3.5" dependencies = [ + "anstream", "anyhow", "argfile", "bincode", @@ -2006,7 +2013,6 @@ dependencies = [ "clap", "clap_complete_command", "clearscreen", - "colored", "filetime", "ignore", "insta", @@ -2017,6 +2023,7 @@ dependencies = [ "mimalloc", "notify", "num_cpus", + "owo-colors", "path-absolutize", "rayon", "regex", @@ -2161,11 +2168,11 @@ version = "0.3.5" dependencies = [ "aho-corasick", "annotate-snippets 0.9.2", + "anstream", "anyhow", "bitflags 2.5.0", "chrono", "clap", - "colored", "fern", "glob", "globset", @@ -2179,6 +2186,7 @@ dependencies = [ "memchr", "natord", "once_cell", + "owo-colors", "path-absolutize", "pathdiff", "pep440_rs 0.5.0", @@ -2493,7 +2501,6 @@ name = "ruff_workspace" version = "0.0.0" dependencies = [ "anyhow", - "colored", "dirs 5.0.1", "glob", "globset", @@ -2501,6 +2508,7 @@ dependencies = [ "is-macro", "itertools 0.12.1", "log", + "owo-colors", "path-absolutize", "pep440_rs 0.5.0", "regex", diff --git a/Cargo.toml b/Cargo.toml index f967ca8cd3a33..3605b23ff24f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ license = "MIT" [workspace.dependencies] aho-corasick = { version = "1.1.3" } annotate-snippets = { version = "0.9.2", features = ["color"] } +anstream = { version = "0.6.13" } anyhow = { version = "1.0.80" } argfile = { version = "0.1.6" } bincode = { version = "1.3.3" } @@ -25,7 +26,6 @@ clap = { version = "4.5.3", features = ["derive"] } clap_complete_command = { version = "0.5.1" } clearscreen = { version = "2.0.0" } codspeed-criterion-compat = { version = "2.4.0", default-features = false } -colored = { version = "2.1.0" } console_error_panic_hook = { version = "0.1.7" } console_log = { version = "1.0.0" } countme = { version = "3.0.1" } @@ -65,6 +65,7 @@ natord = { version = "1.0.9" } notify = { version = "6.1.1" } num_cpus = { version = "1.16.0" } once_cell = { version = "1.19.0" } +owo-colors = { version = "4.0.0" } path-absolutize = { version = "3.1.1" } pathdiff = { version = "0.2.1" } pep440_rs = { version = "0.5.0", features = ["serde"] } diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index c7ab422c8b91f..1e624095f71ff 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -25,6 +25,7 @@ ruff_source_file = { path = "../ruff_source_file" } ruff_text_size = { path = "../ruff_text_size" } ruff_workspace = { path = "../ruff_workspace" } +anstream = { workspace = true } anyhow = { workspace = true } argfile = { workspace = true } bincode = { workspace = true } @@ -34,7 +35,6 @@ chrono = { workspace = true } clap = { workspace = true, features = ["derive", "env", "wrap_help"] } clap_complete_command = { workspace = true } clearscreen = { workspace = true } -colored = { workspace = true } filetime = { workspace = true } ignore = { workspace = true } is-macro = { workspace = true } @@ -42,6 +42,7 @@ itertools = { workspace = true } log = { workspace = true } notify = { workspace = true } num_cpus = { workspace = true } +owo-colors = { workspace = true } path-absolutize = { workspace = true, features = ["once_cell_cache"] } rayon = { workspace = true } regex = { workspace = true } @@ -62,8 +63,6 @@ wild = { workspace = true } [dev-dependencies] # Enable test rules during development ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] } -# Avoid writing colored snapshots when running tests from the terminal -colored = { workspace = true, features = ["no-color"] } insta = { workspace = true, features = ["filters", "json"] } insta-cmd = { workspace = true } tempfile = { workspace = true } diff --git a/crates/ruff/src/args.rs b/crates/ruff/src/args.rs index ed98a999afa6c..55069a8746aba 100644 --- a/crates/ruff/src/args.rs +++ b/crates/ruff/src/args.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use anyhow::bail; use clap::builder::{TypedValueParser, ValueParserFactory}; use clap::{command, Parser}; -use colored::Colorize; +use owo_colors::OwoColorize; use path_absolutize::path_dedot; use regex::Regex; use rustc_hash::FxHashMap; @@ -1053,7 +1053,6 @@ pub enum FormatRangeParseError { impl std::fmt::Display for FormatRangeParseError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let tip = " tip:".bold().green(); match self { FormatRangeParseError::StartGreaterThanEnd(start, end) => { write!( @@ -1062,7 +1061,8 @@ impl std::fmt::Display for FormatRangeParseError { start_invalid=start.to_string().bold().yellow(), end_invalid=end.to_string().bold().yellow(), start=start.to_string().green().bold(), - end=end.to_string().green().bold() + end=end.to_string().green().bold(), + tip=" tip:".bold().green() ) } FormatRangeParseError::InvalidStart(inner) => inner.write(f, true), @@ -1148,7 +1148,8 @@ pub enum LineColumnParseError { impl LineColumnParseError { fn write(&self, f: &mut std::fmt::Formatter, start_range: bool) -> std::fmt::Result { - let tip = "tip:".bold().green(); + let tip = "tip:".bold(); + let tip = tip.green(); let range = if start_range { "start" } else { "end" }; diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index d62a93426ba7f..be624a46f98f6 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -4,9 +4,9 @@ use std::path::{Path, PathBuf}; use std::time::Instant; use anyhow::Result; -use colored::Colorize; use ignore::Error; use log::{debug, error, warn}; +use owo_colors::OwoColorize; #[cfg(not(target_family = "wasm"))] use rayon::prelude::*; use rustc_hash::FxHashMap; @@ -226,6 +226,7 @@ mod test { use rustc_hash::FxHashMap; use tempfile::TempDir; + use ruff_linter::colors; use ruff_linter::message::{Emitter, EmitterContext, TextEmitter}; use ruff_linter::registry::Rule; use ruff_linter::settings::types::UnsafeFixes; @@ -280,7 +281,7 @@ mod test { UnsafeFixes::Enabled, ) .unwrap(); - let mut output = Vec::new(); + let mut output = colors::none(Vec::new()); TextEmitter::default() .with_show_fix_status(true) @@ -291,7 +292,7 @@ mod test { ) .unwrap(); - let messages = String::from_utf8(output).unwrap(); + let messages = String::from_utf8(output.into_inner()).unwrap(); insta::with_settings!({ omit_expression => true, diff --git a/crates/ruff/src/commands/clean.rs b/crates/ruff/src/commands/clean.rs index a514ac1640f45..0a931de398946 100644 --- a/crates/ruff/src/commands/clean.rs +++ b/crates/ruff/src/commands/clean.rs @@ -2,7 +2,7 @@ use std::fs::remove_dir_all; use std::io::{self, BufWriter, Write}; use anyhow::Result; -use colored::Colorize; +use owo_colors::OwoColorize; use path_absolutize::path_dedot; use walkdir::WalkDir; diff --git a/crates/ruff/src/commands/format.rs b/crates/ruff/src/commands/format.rs index 0c3e94409f977..1b12ccdaafb69 100644 --- a/crates/ruff/src/commands/format.rs +++ b/crates/ruff/src/commands/format.rs @@ -6,9 +6,9 @@ use std::path::{Path, PathBuf}; use std::time::Instant; use anyhow::Result; -use colored::Colorize; use itertools::Itertools; use log::{error, warn}; +use owo_colors::OwoColorize; use rayon::iter::Either::{Left, Right}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rustc_hash::FxHashSet; @@ -16,6 +16,7 @@ use thiserror::Error; use tracing::debug; use ruff_diagnostics::SourceMap; +use ruff_linter::colors; use ruff_linter::fs; use ruff_linter::logging::{DisplayParseError, LogLevel}; use ruff_linter::registry::Rule; @@ -189,10 +190,10 @@ pub(crate) fn format( match mode { FormatMode::Write => {} FormatMode::Check => { - results.write_changed(&mut stdout().lock())?; + results.write_changed(&mut colors::auto(stdout()).lock())?; } FormatMode::Diff => { - results.write_diff(&mut stdout().lock())?; + results.write_diff(&mut colors::auto(stdout()).lock())?; } } @@ -200,9 +201,9 @@ pub(crate) fn format( if config_arguments.log_level >= LogLevel::Default { if mode.is_diff() { // Allow piping the diff to e.g. a file by writing the summary to stderr - results.write_summary(&mut stderr().lock())?; + results.write_summary(&mut colors::auto(stderr()).lock())?; } else { - results.write_summary(&mut stdout().lock())?; + results.write_summary(&mut colors::auto(stdout()).lock())?; } } diff --git a/crates/ruff/src/commands/format_stdin.rs b/crates/ruff/src/commands/format_stdin.rs index 9c2d8ff3e0e07..ad43cb60a2154 100644 --- a/crates/ruff/src/commands/format_stdin.rs +++ b/crates/ruff/src/commands/format_stdin.rs @@ -4,6 +4,7 @@ use std::path::Path; use anyhow::Result; use log::error; +use ruff_linter::colors; use ruff_linter::source_kind::SourceKind; use ruff_python_ast::{PySourceType, SourceType}; use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver}; @@ -111,7 +112,7 @@ fn format_source_code( match &formatted { FormattedSource::Formatted(formatted) => match mode { FormatMode::Write => { - let mut writer = stdout().lock(); + let mut writer = colors::auto(stdout()).lock(); formatted .write(&mut writer) .map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?; @@ -120,7 +121,7 @@ fn format_source_code( FormatMode::Diff => { use std::io::Write; write!( - &mut stdout().lock(), + &mut colors::auto(stdout()).lock(), "{}", source_kind.diff(formatted, path).unwrap() ) @@ -130,7 +131,7 @@ fn format_source_code( FormattedSource::Unchanged => { // Write to stdout regardless of whether the source was formatted if mode.is_write() { - let mut writer = stdout().lock(); + let mut writer = colors::auto(stdout()).lock(); source_kind .write(&mut writer) .map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?; diff --git a/crates/ruff/src/diagnostics.rs b/crates/ruff/src/diagnostics.rs index ad15e88a30bcc..3523e2414bda8 100644 --- a/crates/ruff/src/diagnostics.rs +++ b/crates/ruff/src/diagnostics.rs @@ -8,11 +8,12 @@ use std::ops::{Add, AddAssign}; use std::path::Path; use anyhow::{Context, Result}; -use colored::Colorize; use log::{debug, error, warn}; +use owo_colors::OwoColorize; use rustc_hash::FxHashMap; use ruff_diagnostics::Diagnostic; +use ruff_linter::colors; use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource}; use ruff_linter::logging::DisplayParseError; use ruff_linter::message::Message; @@ -444,7 +445,7 @@ pub(crate) fn lint_stdin( // But only write a diff if it's non-empty. if !fixed.is_empty() { write!( - &mut io::stdout().lock(), + &mut colors::auto(io::stdout()).lock(), "{}", source_kind.diff(&transformed, path).unwrap() )?; diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index 1a9a333d232b4..604f722134530 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -10,10 +10,11 @@ use std::sync::mpsc::channel; use anyhow::Result; use args::{GlobalConfigArgs, ServerCommand}; use clap::CommandFactory; -use colored::Colorize; use log::warn; use notify::{recommended_watcher, RecursiveMode, Watcher}; +use owo_colors::OwoColorize; +use ruff_linter::colors; use ruff_linter::logging::{set_up_logging, LogLevel}; use ruff_linter::settings::flags::FixMode; use ruff_linter::settings::types::SerializationFormat; @@ -145,10 +146,6 @@ pub fn run( })); } - // Enabled ANSI colors on Windows 10. - #[cfg(windows)] - assert!(colored::control::set_virtual_terminal(true).is_ok()); - set_up_logging(global_options.log_level())?; if let Some(deprecated_alias_warning) = deprecated_alias_warning { @@ -225,13 +222,12 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result = match cli.output_file { Some(path) if !cli.watch => { - colored::control::set_override(false); let file = File::create(path)?; - Box::new(BufWriter::new(file)) + Box::new(BufWriter::new(colors::none(file))) } - _ => Box::new(BufWriter::new(io::stdout())), + _ => Box::new(BufWriter::new(colors::auto(io::stdout()))), }; - let stderr_writer = Box::new(BufWriter::new(io::stderr())); + let stderr_writer = Box::new(BufWriter::new(colors::auto(io::stderr()))); let is_stdin = is_stdin(&cli.files, cli.stdin_filename.as_deref()); let files = resolve_default_files(cli.files, is_stdin); diff --git a/crates/ruff/src/main.rs b/crates/ruff/src/main.rs index 2f5fe9cfab95b..ba97dfff7ac9f 100644 --- a/crates/ruff/src/main.rs +++ b/crates/ruff/src/main.rs @@ -1,7 +1,8 @@ use std::process::ExitCode; +use anstream::eprintln; use clap::{Parser, Subcommand}; -use colored::Colorize; +use owo_colors::OwoColorize; use ruff::args::{Args, Command}; use ruff::{run, ExitStatus}; diff --git a/crates/ruff/src/printer.rs b/crates/ruff/src/printer.rs index c44150602bdce..042acb9de9a72 100644 --- a/crates/ruff/src/printer.rs +++ b/crates/ruff/src/printer.rs @@ -5,8 +5,8 @@ use std::io::Write; use anyhow::Result; use bitflags::bitflags; -use colored::Colorize; use itertools::{iterate, Itertools}; +use owo_colors::OwoColorize; use serde::Serialize; use ruff_linter::fs::relativize_path; diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index cee9e18a6b559..928de6c990736 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -30,11 +30,11 @@ ruff_text_size = { path = "../ruff_text_size" } aho-corasick = { workspace = true } annotate-snippets = { workspace = true, features = ["color"] } +anstream = { workspace = true } anyhow = { workspace = true } bitflags = { workspace = true } chrono = { workspace = true } clap = { workspace = true, features = ["derive", "string"], optional = true } -colored = { workspace = true } fern = { workspace = true } glob = { workspace = true } globset = { workspace = true } @@ -47,6 +47,7 @@ log = { workspace = true } memchr = { workspace = true } natord = { workspace = true } once_cell = { workspace = true } +owo-colors = { workspace = true } path-absolutize = { workspace = true, features = [ "once_cell_cache", "use_unix_paths_on_wasm", @@ -75,8 +76,6 @@ url = { workspace = true } [dev-dependencies] insta = { workspace = true } test-case = { workspace = true } -# Disable colored output in tests -colored = { workspace = true, features = ["no-color"] } [features] default = [] diff --git a/crates/ruff_linter/src/colors.rs b/crates/ruff_linter/src/colors.rs new file mode 100644 index 0000000000000..9462164647f18 --- /dev/null +++ b/crates/ruff_linter/src/colors.rs @@ -0,0 +1,19 @@ +use anstream::stream::RawStream; +use anstream::{AutoStream, ColorChoice}; + +pub fn none(stream: S) -> AutoStream { + AutoStream::new(stream, ColorChoice::Never) +} + +pub fn auto(stream: S) -> AutoStream { + let choice = choice(&stream); + AutoStream::new(stream, choice) +} + +pub fn choice(stream: &S) -> ColorChoice { + AutoStream::choice(stream) +} + +pub fn enabled(stream: &S) -> bool { + choice(stream) != ColorChoice::Never +} diff --git a/crates/ruff_linter/src/lib.rs b/crates/ruff_linter/src/lib.rs index 857c7fadae5fc..37936628812d2 100644 --- a/crates/ruff_linter/src/lib.rs +++ b/crates/ruff_linter/src/lib.rs @@ -16,6 +16,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); mod checkers; pub mod codes; +pub mod colors; mod comments; mod cst; pub mod directives; diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 4f5de73ae9686..edafb767282e9 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -2,10 +2,11 @@ use std::borrow::Cow; use std::ops::Deref; use std::path::Path; +use anstream::eprintln; use anyhow::{anyhow, Result}; -use colored::Colorize; use itertools::Itertools; use log::error; +use owo_colors::OwoColorize; use rustc_hash::FxHashMap; use ruff_diagnostics::Diagnostic; diff --git a/crates/ruff_linter/src/logging.rs b/crates/ruff_linter/src/logging.rs index 2a64cde01c636..cc21eb5f23dc9 100644 --- a/crates/ruff_linter/src/logging.rs +++ b/crates/ruff_linter/src/logging.rs @@ -3,15 +3,16 @@ use std::path::{Path, PathBuf}; use std::sync::Mutex; use anyhow::Result; -use colored::Colorize; use fern; use log::Level; use once_cell::sync::Lazy; +use owo_colors::OwoColorize; use ruff_python_parser::{ParseError, ParseErrorType}; use rustc_hash::FxHashSet; use ruff_source_file::{LineIndex, OneIndexed, SourceCode, SourceLocation}; +use crate::colors; use crate::fs; use crate::source_kind::SourceKind; use ruff_notebook::Notebook; @@ -22,7 +23,7 @@ pub static IDENTIFIERS: Lazy>> = Lazy::new(Mutex::defaul #[macro_export] macro_rules! warn_user_once_by_id { ($id:expr, $($arg:tt)*) => { - use colored::Colorize; + use owo_colors::OwoColorize; use log::warn; if let Ok(mut states) = $crate::logging::IDENTIFIERS.lock() { @@ -42,7 +43,7 @@ pub static MESSAGES: Lazy>> = Lazy::new(Mutex::default); #[macro_export] macro_rules! warn_user_once_by_message { ($($arg:tt)*) => { - use colored::Colorize; + use owo_colors::OwoColorize; use log::warn; if let Ok(mut states) = $crate::logging::MESSAGES.lock() { @@ -59,7 +60,7 @@ macro_rules! warn_user_once_by_message { #[macro_export] macro_rules! warn_user_once { ($($arg:tt)*) => { - use colored::Colorize; + use owo_colors::OwoColorize; use log::warn; static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); @@ -73,7 +74,7 @@ macro_rules! warn_user_once { #[macro_export] macro_rules! warn_user { ($($arg:tt)*) => {{ - use colored::Colorize; + use owo_colors::OwoColorize; use log::warn; let message = format!("{}", format_args!($($arg)*)); @@ -152,7 +153,10 @@ pub fn set_up_logging(level: LogLevel) -> Result<()> { }) .level(level.level_filter()) .level_for("globset", log::LevelFilter::Warn) - .chain(std::io::stderr()) + .chain(fern::Output::writer( + Box::new(colors::auto(std::io::stderr())), + "\n", + )) .apply()?; Ok(()) } diff --git a/crates/ruff_linter/src/message/diff.rs b/crates/ruff_linter/src/message/diff.rs index 2ba3f24ee24df..7eaa32f1574ec 100644 --- a/crates/ruff_linter/src/message/diff.rs +++ b/crates/ruff_linter/src/message/diff.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Formatter}; use std::num::NonZeroUsize; -use colored::{Color, ColoredString, Colorize, Styles}; +use owo_colors::{OwoColorize, Style}; use ruff_text_size::{Ranged, TextRange, TextSize}; use similar::{ChangeTag, TextDiff}; @@ -81,7 +81,7 @@ impl Display for Diff<'_> { ChangeTag::Equal => " ", }; - let line_style = LineStyle::from(change.tag()); + let line_style = diff_line_style(change.tag()); let old_index = change.old_index().map(OneIndexed::from_zero_indexed); let new_index = change.new_index().map(OneIndexed::from_zero_indexed); @@ -97,14 +97,14 @@ impl Display for Diff<'_> { index: new_index, width: digit_with }, - line_style.apply_to(sign).bold() + sign.style(line_style).bold() )?; for (emphasized, value) in change.iter_strings_lossy() { if emphasized { - write!(f, "{}", line_style.apply_to(&value).underline().on_black())?; + write!(f, "{}", value.style(line_style).underline().on_black())?; } else { - write!(f, "{}", line_style.apply_to(&value))?; + write!(f, "{}", value.style(line_style))?; } } if change.missing_newline() { @@ -118,52 +118,11 @@ impl Display for Diff<'_> { } } -struct LineStyle { - fgcolor: Option, - style: Option, -} - -impl LineStyle { - fn apply_to(&self, input: &str) -> ColoredString { - let mut colored = ColoredString::from(input); - if let Some(color) = self.fgcolor { - colored = colored.color(color); - } - - if let Some(style) = self.style { - match style { - Styles::Clear => colored.clear(), - Styles::Bold => colored.bold(), - Styles::Dimmed => colored.dimmed(), - Styles::Underline => colored.underline(), - Styles::Reversed => colored.reversed(), - Styles::Italic => colored.italic(), - Styles::Blink => colored.blink(), - Styles::Hidden => colored.hidden(), - Styles::Strikethrough => colored.strikethrough(), - } - } else { - colored - } - } -} - -impl From for LineStyle { - fn from(value: ChangeTag) -> Self { - match value { - ChangeTag::Equal => LineStyle { - fgcolor: None, - style: Some(Styles::Dimmed), - }, - ChangeTag::Delete => LineStyle { - fgcolor: Some(Color::Red), - style: None, - }, - ChangeTag::Insert => LineStyle { - fgcolor: Some(Color::Green), - style: None, - }, - } +fn diff_line_style(change_tag: ChangeTag) -> Style { + match change_tag { + ChangeTag::Equal => Style::new().dimmed(), + ChangeTag::Delete => Style::new().red(), + ChangeTag::Insert => Style::new().green(), } } diff --git a/crates/ruff_linter/src/message/grouped.rs b/crates/ruff_linter/src/message/grouped.rs index 0445c1746193b..6f76eab8c1548 100644 --- a/crates/ruff_linter/src/message/grouped.rs +++ b/crates/ruff_linter/src/message/grouped.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter}; use std::io::Write; use std::num::NonZeroUsize; -use colored::Colorize; +use owo_colors::OwoColorize; use ruff_notebook::NotebookIndex; use ruff_source_file::OneIndexed; diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 2f44de44eda71..d0600eb6fbe50 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -156,6 +156,7 @@ mod tests { use ruff_source_file::{OneIndexed, SourceFileBuilder}; use ruff_text_size::{Ranged, TextRange, TextSize}; + use crate::colors; use crate::message::{Emitter, EmitterContext, Message}; pub(super) fn create_messages() -> Vec { @@ -337,10 +338,10 @@ def foo(): ) -> String { let notebook_indexes = FxHashMap::default(); let context = EmitterContext::new(¬ebook_indexes); - let mut output: Vec = Vec::new(); + let mut output = colors::none(Vec::new()); emitter.emit(&mut output, messages, &context).unwrap(); - String::from_utf8(output).expect("Output to be valid UTF-8") + String::from_utf8(output.into_inner()).expect("Output to be valid UTF-8") } pub(super) fn capture_emitter_notebook_output( @@ -349,9 +350,9 @@ def foo(): notebook_indexes: &FxHashMap, ) -> String { let context = EmitterContext::new(notebook_indexes); - let mut output: Vec = Vec::new(); + let mut output = colors::none(Vec::new()); emitter.emit(&mut output, messages, &context).unwrap(); - String::from_utf8(output).expect("Output to be valid UTF-8") + String::from_utf8(output.into_inner()).expect("Output to be valid UTF-8") } } diff --git a/crates/ruff_linter/src/message/text.rs b/crates/ruff_linter/src/message/text.rs index 833deab46c818..6f150ad445627 100644 --- a/crates/ruff_linter/src/message/text.rs +++ b/crates/ruff_linter/src/message/text.rs @@ -5,12 +5,13 @@ use std::io::Write; use annotate_snippets::display_list::{DisplayList, FormatOptions}; use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; use bitflags::bitflags; -use colored::Colorize; +use owo_colors::OwoColorize; use ruff_notebook::NotebookIndex; use ruff_source_file::{OneIndexed, SourceLocation}; use ruff_text_size::{Ranged, TextRange, TextSize}; +use crate::colors; use crate::fs::relativize_path; use crate::line_width::{IndentWidth, LineWidthBuilder}; use crate::message::diff::Diff; @@ -284,10 +285,7 @@ impl Display for MessageCodeFrame<'_> { }], footer, opt: FormatOptions { - #[cfg(test)] - color: false, - #[cfg(not(test))] - color: colored::control::SHOULD_COLORIZE.should_colorize(), + color: colors::enabled(&std::io::stdout()), ..FormatOptions::default() }, }; diff --git a/crates/ruff_linter/src/pyproject_toml.rs b/crates/ruff_linter/src/pyproject_toml.rs index 88b0765c23d66..39c8e5e5b85da 100644 --- a/crates/ruff_linter/src/pyproject_toml.rs +++ b/crates/ruff_linter/src/pyproject_toml.rs @@ -1,5 +1,5 @@ -use colored::Colorize; use log::warn; +use owo_colors::OwoColorize; use pyproject_toml::PyProjectToml; use ruff_text_size::{TextRange, TextSize}; diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters.py.snap index 707794f9422c4..42562893506e7 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters.py.snap @@ -5,9 +5,9 @@ invalid_characters.py:15:6: PLE2510 [*] Invalid unescaped character backspace, u | 13 | # (Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall, 14 | #foo = 'hi' -15 | b = '' +15 | b = '' | PLE2510 -16 | b = f'' +16 | b = f'' | = help: Replace with escape sequence @@ -15,17 +15,17 @@ invalid_characters.py:15:6: PLE2510 [*] Invalid unescaped character backspace, u 12 12 | # (Pylint, "C0414") => Rule::UselessImportAlias, 13 13 | # (Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall, 14 14 | #foo = 'hi' -15 |-b = '' +15 |-b = '' 15 |+b = '\b' -16 16 | b = f'' +16 16 | b = f'' 17 17 | 18 18 | b_ok = '\\b' invalid_characters.py:16:7: PLE2510 [*] Invalid unescaped character backspace, use "\b" instead | 14 | #foo = 'hi' -15 | b = '' -16 | b = f'' +15 | b = '' +16 | b = f'' | PLE2510 17 | 18 | b_ok = '\\b' @@ -35,8 +35,8 @@ invalid_characters.py:16:7: PLE2510 [*] Invalid unescaped character backspace, u ℹ Safe fix 13 13 | # (Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall, 14 14 | #foo = 'hi' -15 15 | b = '' -16 |-b = f'' +15 15 | b = '' +16 |-b = f'' 16 |+b = f'\b' 17 17 | 18 18 | b_ok = '\\b' @@ -46,7 +46,7 @@ invalid_characters.py:55:21: PLE2510 [*] Invalid unescaped character backspace, | 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 54 | -55 | nested_fstrings = f'{f'{f''}'}' +55 | nested_fstrings = f'{f'{f''}' | PLE2510 56 | 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106 @@ -57,10 +57,8 @@ invalid_characters.py:55:21: PLE2510 [*] Invalid unescaped character backspace, 52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 54 54 | -55 |-nested_fstrings = f'{f'{f''}'}' - 55 |+nested_fstrings = f'\b{f'{f''}'}' +55 |-nested_fstrings = f'{f'{f''}' + 55 |+nested_fstrings = f'\b{f'{f''}' 56 56 | 57 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106 -58 58 | x = f"""}}ab""" - - +58 58 | x = f"""}}ab""" diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap index 78d0e18d726ad..6069a0abb1a1b 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap @@ -5,9 +5,9 @@ invalid_characters.py:24:12: PLE2512 [*] Invalid unescaped character SUB, use "\ | 22 | cr_ok = f'\\r' 23 | -24 | sub = 'sub ' +24 | sub = 'sub ' | PLE2512 -25 | sub = f'sub ' +25 | sub = f'sub ' | = help: Replace with escape sequence @@ -15,16 +15,16 @@ invalid_characters.py:24:12: PLE2512 [*] Invalid unescaped character SUB, use "\ 21 21 | cr_ok = '\\r' 22 22 | cr_ok = f'\\r' 23 23 | -24 |-sub = 'sub ' +24 |-sub = 'sub ' 24 |+sub = 'sub \x1A' -25 25 | sub = f'sub ' +25 25 | sub = f'sub ' 26 26 | 27 27 | sub_ok = '\x1a' invalid_characters.py:25:13: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead | -24 | sub = 'sub ' -25 | sub = f'sub ' +24 | sub = 'sub ' +25 | sub = f'sub ' | PLE2512 26 | 27 | sub_ok = '\x1a' @@ -34,8 +34,8 @@ invalid_characters.py:25:13: PLE2512 [*] Invalid unescaped character SUB, use "\ ℹ Safe fix 22 22 | cr_ok = f'\\r' 23 23 | -24 24 | sub = 'sub ' -25 |-sub = f'sub ' +24 24 | sub = 'sub ' +25 |-sub = f'sub ' 25 |+sub = f'sub \x1A' 26 26 | 27 27 | sub_ok = '\x1a' @@ -45,7 +45,7 @@ invalid_characters.py:55:25: PLE2512 [*] Invalid unescaped character SUB, use "\ | 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 54 | -55 | nested_fstrings = f'{f'{f''}'}' +55 | nested_fstrings = f'{f'{f''}' | PLE2512 56 | 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106 @@ -56,29 +56,27 @@ invalid_characters.py:55:25: PLE2512 [*] Invalid unescaped character SUB, use "\ 52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 54 54 | -55 |-nested_fstrings = f'{f'{f''}'}' - 55 |+nested_fstrings = f'{f'\x1A{f''}'}' +55 |-nested_fstrings = f'{f'{f''}' + 55 |+nested_fstrings = f'{f'\x1A{f''}' 56 56 | 57 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106 -58 58 | x = f"""}}ab""" +58 58 | x = f"""}}ab""" invalid_characters.py:58:12: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead | 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106 -58 | x = f"""}}ab""" +58 | x = f"""}}ab""" | PLE2512 59 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998256 -60 | x = f"""}}ab""" +60 | x = f"""}}a""" | = help: Replace with escape sequence ℹ Safe fix -55 55 | nested_fstrings = f'{f'{f''}'}' +55 55 | nested_fstrings = f'{f'{f''}' 56 56 | 57 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106 -58 |-x = f"""}}ab""" +58 |-x = f"""}}ab""" 58 |+x = f"""}}a\x1Ab""" 59 59 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998256 -60 60 | x = f"""}}ab""" - - +60 60 | x = f"""}}a""" diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2513_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2513_invalid_characters.py.snap index 2c684a1a774be..2086cb7aeb59e 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2513_invalid_characters.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2513_invalid_characters.py.snap @@ -5,27 +5,27 @@ invalid_characters.py:30:16: PLE2513 [*] Invalid unescaped character ESC, use "\ | 28 | sub_ok = f'\x1a' 29 | -30 | esc = 'esc esc ' - | PLE2513 -31 | esc = f'esc esc ' - | +30 | esc = 'esc esc + PLE2513 +31 | esc = f'esc esc + = help: Replace with escape sequence ℹ Safe fix 27 27 | sub_ok = '\x1a' 28 28 | sub_ok = f'\x1a' 29 29 | -30 |-esc = 'esc esc ' +30 |-esc = 'esc esc 30 |+esc = 'esc esc \x1B' -31 31 | esc = f'esc esc ' +31 31 | esc = f'esc esc 32 32 | 33 33 | esc_ok = '\x1b' invalid_characters.py:31:17: PLE2513 [*] Invalid unescaped character ESC, use "\x1B" instead | -30 | esc = 'esc esc ' -31 | esc = f'esc esc ' - | PLE2513 +30 | esc = 'esc esc +1 | esc = f'esc esc + PLE2513 32 | 33 | esc_ok = '\x1b' | @@ -34,8 +34,8 @@ invalid_characters.py:31:17: PLE2513 [*] Invalid unescaped character ESC, use "\ ℹ Safe fix 28 28 | sub_ok = f'\x1a' 29 29 | -30 30 | esc = 'esc esc ' -31 |-esc = f'esc esc ' +30 30 | esc = 'esc esc +31 |-esc = f'esc esc 31 |+esc = f'esc esc \x1B' 32 32 | 33 33 | esc_ok = '\x1b' @@ -45,7 +45,7 @@ invalid_characters.py:55:29: PLE2513 [*] Invalid unescaped character ESC, use "\ | 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 54 | -55 | nested_fstrings = f'{f'{f''}'}' +55 | nested_fstrings = f'{f'{f''}' | PLE2513 56 | 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106 @@ -56,26 +56,24 @@ invalid_characters.py:55:29: PLE2513 [*] Invalid unescaped character ESC, use "\ 52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 54 54 | -55 |-nested_fstrings = f'{f'{f''}'}' - 55 |+nested_fstrings = f'{f'{f'\x1B'}'}' +55 |-nested_fstrings = f'{f'{f''}' + 55 |+nested_fstrings = f'{f'{f'\x1B'}'}' 56 56 | 57 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106 -58 58 | x = f"""}}ab""" +58 58 | x = f"""}}ab""" invalid_characters.py:60:12: PLE2513 [*] Invalid unescaped character ESC, use "\x1B" instead | -58 | x = f"""}}ab""" +58 | x = f"""}}ab""" 59 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998256 -60 | x = f"""}}ab""" +60 | x = f"""}}a""" | PLE2513 | = help: Replace with escape sequence ℹ Safe fix 57 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106 -58 58 | x = f"""}}ab""" +58 58 | x = f"""}}ab""" 59 59 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998256 -60 |-x = f"""}}ab""" +60 |-x = f"""}}a""" 60 |+x = f"""}}a\x1Bb""" - - diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2514_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2514_invalid_characters.py.snap index 3ab1a52ec4958..6f9414706e740 100644 Binary files a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2514_invalid_characters.py.snap and b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2514_invalid_characters.py.snap differ diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2515_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2515_invalid_characters.py.snap index 24e4b0a94620c..d2eb2ffafe342 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2515_invalid_characters.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2515_invalid_characters.py.snap @@ -100,7 +100,7 @@ invalid_characters.py:52:60: PLE2515 [*] Invalid unescaped character zero-width- 52 |+zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ \u200b​" 53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 54 54 | -55 55 | nested_fstrings = f'{f'{f''}'}' +55 55 | nested_fstrings = f'{f'{f''}' invalid_characters.py:52:61: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead | @@ -120,7 +120,7 @@ invalid_characters.py:52:61: PLE2515 [*] Invalid unescaped character zero-width- 52 |+zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​\u200b" 53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 54 54 | -55 55 | nested_fstrings = f'{f'{f''}'}' +55 55 | nested_fstrings = f'{f'{f''}' invalid_characters.py:53:61: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead | @@ -129,7 +129,7 @@ invalid_characters.py:53:61: PLE2515 [*] Invalid unescaped character zero-width- 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" | PLE2515 54 | -55 | nested_fstrings = f'{f'{f''}'}' +55 | nested_fstrings = f'{f'{f''}' | = help: Replace with escape sequence @@ -140,7 +140,7 @@ invalid_characters.py:53:61: PLE2515 [*] Invalid unescaped character zero-width- 53 |-zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 53 |+zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ \u200b​" 54 54 | -55 55 | nested_fstrings = f'{f'{f''}'}' +55 55 | nested_fstrings = f'{f'{f''}' 56 56 | invalid_characters.py:53:62: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead @@ -150,7 +150,7 @@ invalid_characters.py:53:62: PLE2515 [*] Invalid unescaped character zero-width- 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" | PLE2515 54 | -55 | nested_fstrings = f'{f'{f''}'}' +55 | nested_fstrings = f'{f'{f''}' | = help: Replace with escape sequence @@ -161,7 +161,5 @@ invalid_characters.py:53:62: PLE2515 [*] Invalid unescaped character zero-width- 53 |-zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" 53 |+zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​\u200b" 54 54 | -55 55 | nested_fstrings = f'{f'{f''}'}' -56 56 | - - +55 55 | nested_fstrings = f'{f'{f''}' +56 56 | diff --git a/crates/ruff_linter/src/source_kind.rs b/crates/ruff_linter/src/source_kind.rs index da27457348a1b..023b094321726 100644 --- a/crates/ruff_linter/src/source_kind.rs +++ b/crates/ruff_linter/src/source_kind.rs @@ -11,7 +11,7 @@ use ruff_diagnostics::SourceMap; use ruff_notebook::{Cell, Notebook, NotebookError}; use ruff_python_ast::PySourceType; -use colored::Colorize; +use owo_colors::OwoColorize; use crate::fs; diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index ff9cf99da21ae..9bfa19d246d41 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -19,6 +19,7 @@ use ruff_python_trivia::textwrap::dedent; use ruff_source_file::{Locator, SourceFileBuilder}; use ruff_text_size::Ranged; +use crate::colors; use crate::directives; use crate::fix::{fix_file, FixResult}; use crate::linter::{check_path, LinterResult, TokenSource}; @@ -293,7 +294,7 @@ pub(crate) fn print_jupyter_messages( path: &Path, notebook: &Notebook, ) -> String { - let mut output = Vec::new(); + let mut output = colors::none(Vec::new()); TextEmitter::default() .with_show_fix_status(true) @@ -310,11 +311,11 @@ pub(crate) fn print_jupyter_messages( ) .unwrap(); - String::from_utf8(output).unwrap() + String::from_utf8(output.into_inner()).unwrap() } pub(crate) fn print_messages(messages: &[Message]) -> String { - let mut output = Vec::new(); + let mut output = colors::none(Vec::new()); TextEmitter::default() .with_show_fix_status(true) @@ -328,7 +329,7 @@ pub(crate) fn print_messages(messages: &[Message]) -> String { ) .unwrap(); - String::from_utf8(output).unwrap() + String::from_utf8(output.into_inner()).unwrap() } #[macro_export] diff --git a/crates/ruff_workspace/Cargo.toml b/crates/ruff_workspace/Cargo.toml index ce4e1c9b13e6b..e944b6e90fd33 100644 --- a/crates/ruff_workspace/Cargo.toml +++ b/crates/ruff_workspace/Cargo.toml @@ -22,7 +22,6 @@ ruff_cache = { path = "../ruff_cache" } ruff_macros = { path = "../ruff_macros" } anyhow = { workspace = true } -colored = { workspace = true } dirs = { workspace = true } ignore = { workspace = true } is-macro = { workspace = true } @@ -30,6 +29,7 @@ itertools = { workspace = true } log = { workspace = true } glob = { workspace = true } globset = { workspace = true } +owo-colors = { workspace = true } path-absolutize = { workspace = true } pep440_rs = { workspace = true, features = ["serde"] } regex = { workspace = true } diff --git a/docs/faq.md b/docs/faq.md index 6311dddfdccda..6b04b0ec528cc 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -622,9 +622,7 @@ making changes to code, even for seemingly trivial fixes. If a "safe" fix breaks ## How can I disable Ruff's color output? -Ruff's color output is powered by the [`colored`](https://crates.io/crates/colored) crate, which -attempts to automatically detect whether the output stream supports color. However, you can force -colors off by setting the `NO_COLOR` environment variable to any value (e.g., `NO_COLOR=1`). - -[`colored`](https://crates.io/crates/colored) also supports the `CLICOLOR` and `CLICOLOR_FORCE` -environment variables (see the [spec](https://bixense.com/clicolors/)). +Ruff's color output is powered by the [`owo-colors`](https://crates.io/crates/owo-colors) and +[`anstream`](https://crates.io/crates/anstream) crates, which attempt to automatically detect +whether the output stream supports color. However, you can force colors off by setting the +`NO_COLOR` environment variable to any value (e.g., `NO_COLOR=1`).