diff --git a/Cargo.toml b/Cargo.toml index 27d6448e..d1848a19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ events = [ "dep:signal-hook-mio", ] # Enables reading input/events from the system. serde = ["dep:serde", "bitflags/serde"] # Enables 'serde' for various types. +disable-guard = [] # Guard to locally disable ansi style code emission. # # Shared dependencies diff --git a/src/style.rs b/src/style.rs index b67a421b..506acaa1 100644 --- a/src/style.rs +++ b/src/style.rs @@ -116,6 +116,9 @@ use std::{ fmt::{self, Display}, }; +#[cfg(feature = "disable-guard")] +use std::sync::atomic::AtomicBool; + use crate::command::execute_fmt; use crate::{csi, impl_display, Command}; @@ -180,6 +183,84 @@ pub fn available_color_count() -> u16 { }) } +#[cfg(feature = "disable-guard")] +static ANSI_DISABLED: AtomicBool = AtomicBool::new(false); + +#[cfg(feature = "disable-guard")] +/// A guard that disables styled output +/// +/// See [`disable_ansi`](crate::style::disable_ansi) +pub struct AnsiDisabledGuard(bool); + +#[cfg(feature = "disable-guard")] +/// Build a guard that temporarily disables output style. +/// +/// If `disable` is `true`, the guard will disable output style until it is dropped. +/// If `disable` is `false`, the guard will not do anything. +/// +/// Typical use of this feature is to disable style on redirected stdout or stderr, in order to +/// prevent the output from being polluted by ANSI escape codes. +/// +/// This function is behind the `disable-guard` feature. +/// +/// # Examples +/// +/// ```rust +/// use crossterm::style; +/// +/// fn print_diagnostic(force_color: bool) { +/// let _guard = style::disable_ansi(!force_color && !io::stderr().is_terminal()); +/// +/// // styling stderr output is now only active if stderr is not redirected +/// // and force_color is false +/// eprintln!("{}: {}", "error".red().bold(), "something went wrong".bold()); +/// } +/// +/// ``` +/// +/// # Notes +/// +/// The guard will disable styled output in all threads. It is likely not what you want. +/// External locking must be used if you style output in multiple threads. +/// [`io::stderr().lock()`](https://doc.rust-lang.org/std/io/struct.Stderr.html#method.lock) or +/// [`io::stdout().lock()`](https://doc.rust-lang.org/std/io/struct.Stdout.html#method.lock) +/// are typically used. +/// +/// ```rust +/// use std::io; +/// use crossterm::style; +/// +/// fn print_diagnostic(force_color: bool) -> io::Result<()> { +/// let stderr = io::stderr().lock(); +/// let _guard = style::disable_ansi(!force_color && !stderr.is_terminal()); +/// +/// // styling stderr output is now only active in this thread if stderr is not redirected +/// // and force_color is false +/// writeln!(&mut stderr, "{}: {}", "error".red().bold(), "something went wrong".bold())?; +/// } +/// ``` +/// +/// See also: [`is_ansi_disabled()`](crate::style::is_ansi_disabled) +#[must_use] +pub fn disable_ansi(disable: bool) -> AnsiDisabledGuard { + AnsiDisabledGuard(ANSI_DISABLED.swap(disable, std::sync::atomic::Ordering::Relaxed)) +} + +#[cfg(feature = "disable-guard")] +impl Drop for AnsiDisabledGuard { + fn drop(&mut self) { + ANSI_DISABLED.store(self.0, std::sync::atomic::Ordering::Relaxed); + } +} + +#[cfg(feature = "disable-guard")] +/// Checks whether ansi styling is currently disabled +/// +/// See [`disable_ansi`](crate::style::disable_ansi) +pub fn is_ansi_disabled() -> bool { + ANSI_DISABLED.load(std::sync::atomic::Ordering::Relaxed) +} + /// Forces colored output on or off globally, overriding NO_COLOR. /// /// # Notes @@ -207,11 +288,19 @@ pub struct SetForegroundColor(pub Color); impl Command for SetForegroundColor { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } write!(f, csi!("{}m"), Colored::ForegroundColor(self.0)) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } sys::windows::set_foreground_color(self.0) } } @@ -231,11 +320,19 @@ pub struct SetBackgroundColor(pub Color); impl Command for SetBackgroundColor { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } write!(f, csi!("{}m"), Colored::BackgroundColor(self.0)) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } sys::windows::set_background_color(self.0) } } @@ -255,6 +352,10 @@ pub struct SetUnderlineColor(pub Color); impl Command for SetUnderlineColor { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } write!(f, csi!("{}m"), Colored::UnderlineColor(self.0)) } @@ -293,6 +394,10 @@ pub struct SetColors(pub Colors); impl Command for SetColors { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } // Writing both foreground and background colors in one command resulted in about 20% more // FPS (20 to 24 fps) on a fullscreen (171x51) app that writes every cell with a different // foreground and background color, compared to separately using the SetForegroundColor and @@ -315,6 +420,10 @@ impl Command for SetColors { #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } if let Some(color) = self.0.foreground { sys::windows::set_foreground_color(color)?; } @@ -337,11 +446,19 @@ pub struct SetAttribute(pub Attribute); impl Command for SetAttribute { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } write!(f, csi!("{}m"), self.0.sgr()) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } // attributes are not supported by WinAPI. Ok(()) } @@ -359,6 +476,10 @@ pub struct SetAttributes(pub Attributes); impl Command for SetAttributes { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } for attr in Attribute::iterator() { if self.0.has(attr) { SetAttribute(attr).write_ansi(f)?; @@ -384,6 +505,10 @@ pub struct SetStyle(pub ContentStyle); impl Command for SetStyle { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } if let Some(bg) = self.0.background_color { execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?; } @@ -423,6 +548,12 @@ pub struct PrintStyledContent(pub StyledContent); impl Command for PrintStyledContent { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + write!(f, "{}", self.0.content())?; + return Ok(()); + } + let style = self.0.style(); let mut reset_background = false; @@ -483,11 +614,19 @@ pub struct ResetColor; impl Command for ResetColor { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } f.write_str(csi!("0m")) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { + #[cfg(feature = "disable-guard")] + if is_ansi_disabled() { + return Ok(()); + } sys::windows::reset() } } diff --git a/src/style/types/colored.rs b/src/style/types/colored.rs index c9feecdb..20e2ccd7 100644 --- a/src/style/types/colored.rs +++ b/src/style/types/colored.rs @@ -97,6 +97,11 @@ impl fmt::Display for Colored { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let color; + #[cfg(feature = "disable-guard")] + if crate::style::is_ansi_disabled() { + return Ok(()); + } + if Self::ansi_color_disabled_memoized() { return Ok(()); }