Skip to content

Commit

Permalink
AnsiDisableGuard to disable ANSI code
Browse files Browse the repository at this point in the history
see #921
  • Loading branch information
rtbo committed Aug 28, 2024
1 parent b056370 commit 4d6a759
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
146 changes: 146 additions & 0 deletions src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -180,6 +183,91 @@ 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
///
/// ```no_run
/// # use std::io::{self, IsTerminal};
/// use crossterm::style::{self, Stylize};
///
/// fn print_diagnostic() {
/// let _guard = style::disable_ansi(!io::stderr().is_terminal());
///
/// // styling stderr output is now only active if stderr is not redirected
/// 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.
///
/// A more detailed example:
///
/// ```no_run
/// # use std::io::{self, IsTerminal};
/// # use std::fmt::{self, Display};
/// use crossterm::style::{self, Stylize};
/// # struct Error;
/// # impl Display for Error { fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { Ok(()) } }
///
/// fn write_diagnostic<W: io::Write>(out: &mut W, err: &Error) -> io::Result<()> {
/// writeln!(out, "{}: {}", "error".red().bold(), err.to_string().bold())
/// }
///
/// fn print_diagnostic(err: &Error, force_color: bool) -> io::Result<()> {
/// let stderr = io::stderr().lock();
/// let _guard = style::disable_ansi(!force_color && !stderr.is_terminal());
///
/// let mut out = io::BufWriter::new(stderr);
/// write_diagnostic(&mut out, err)
/// }
/// ```
///
/// 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
Expand Down Expand Up @@ -207,11 +295,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)
}
}
Expand All @@ -231,11 +327,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)
}
}
Expand All @@ -255,6 +359,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))
}

Expand Down Expand Up @@ -293,6 +401,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
Expand All @@ -315,6 +427,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)?;
}
Expand All @@ -337,11 +453,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(())
}
Expand All @@ -359,6 +483,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)?;
Expand All @@ -384,6 +512,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)?;
}
Expand Down Expand Up @@ -423,6 +555,12 @@ pub struct PrintStyledContent<D: Display>(pub StyledContent<D>);

impl<D: Display> Command for PrintStyledContent<D> {
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;
Expand Down Expand Up @@ -483,11 +621,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()
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/style/types/colored.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(());
}
Expand Down

0 comments on commit 4d6a759

Please sign in to comment.