diff --git a/src/condition.rs b/src/condition.rs index 8116976..cecdd5c 100644 --- a/src/condition.rs +++ b/src/condition.rs @@ -59,6 +59,13 @@ pub struct AtomicCondition(AtomicPtr<()>); pub struct CachedBool(AtomicU8); impl Condition { + /// A condition that evaluates to `true` if the OS supports coloring. + /// + /// On Windows, this condition tries to enable coloring support on the first + /// call and caches the result for subsequent calls. Outside of Windows, + /// this always evaluates to `true`. + pub const DEFAULT: Condition = Condition(Condition::os_support); + /// A condition that always evaluates to `true`. pub const ALWAYS: Condition = Condition(Condition::always); @@ -125,11 +132,20 @@ impl Condition { /// The backing function for [`Condition::NEVER`]. Returns `false` always. pub const fn never() -> bool { false } + + /// The backing function for [`Condition::DEFAULT`]. + /// + /// Returns `true` if the current OS supports ANSI escape sequences for + /// coloring. On Windows, the first call to this function attempts to enable + /// support. Outside of windows, this always returns `true`. + pub fn os_support() -> bool { + crate::windows::cache_enable() + } } impl Default for Condition { fn default() -> Self { - Condition::ALWAYS + Condition::DEFAULT } } @@ -142,7 +158,7 @@ impl core::ops::Deref for Condition { } impl AtomicCondition { - pub const ALWAYS: AtomicCondition = AtomicCondition::from(Condition::ALWAYS); + pub const DEFAULT: AtomicCondition = AtomicCondition::from(Condition::DEFAULT); pub const fn from(value: Condition) -> Self { AtomicCondition(AtomicPtr::new(value.0 as *mut ())) @@ -196,7 +212,18 @@ impl CachedBool { impl fmt::Debug for Condition { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) + let mut dbg = f.debug_tuple("Condition"); + if *self == Condition::DEFAULT { + dbg.field(&"DEFAULT"); + } else if *self == Condition::ALWAYS { + dbg.field(&"ALWAYS"); + } else if *self == Condition::NEVER { + dbg.field(&"NEVER"); + } else { + dbg.field(&self.0); + } + + dbg.finish() } } diff --git a/src/global.rs b/src/global.rs index 81e9dac..f059cac 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,8 +1,8 @@ use crate::condition::{AtomicCondition, Condition}; -static ENABLED: AtomicCondition = AtomicCondition::ALWAYS; +static ENABLED: AtomicCondition = AtomicCondition::DEFAULT; -/// Disables styling globally. +/// Unconditionally disables styling globally. /// /// # Example /// @@ -20,10 +20,10 @@ pub fn disable() { ENABLED.store(Condition::NEVER); } -/// Enables styling globally. +/// Unconditionally enables styling globally. /// -/// Styling is enabled by default, so this method should only be called to _re_ -/// enable styling. +/// By default, styling is enabled based on [`Condition::Default`], which checks +/// for operating system support. /// /// # Example /// @@ -42,7 +42,7 @@ pub fn enable() { ENABLED.store(Condition::ALWAYS); } -/// Dynamically enable styling globally based on `condition`. +/// Dynamically enables styling globally based on `condition`. /// /// The supplied `condition` is checked dynamically, any time a painted value is /// displayed. As a result, `condition` should be _fast_. diff --git a/src/paint.rs b/src/paint.rs index f58ab27..92c65c6 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -90,9 +90,7 @@ impl Painted { #[inline] pub(crate) fn enabled(&self) -> bool { - crate::is_enabled() - && (self.style.condition)() - && crate::windows::cache_enable() + crate::is_enabled() && (self.style.condition)() } properties!([pub const] constructor(Self) -> Self); diff --git a/src/style.rs b/src/style.rs index 6060a54..375682d 100644 --- a/src/style.rs +++ b/src/style.rs @@ -12,15 +12,48 @@ use alloc::{string::String, borrow::Cow}; use std::borrow::Cow; /// A set of styling options. +/// +/// ## Equivalence and Ordering +/// +/// Only a style's `foreground`, `background`, and set of `attributes` is +/// considered when testing for equivalence or producing an ordering via +/// `PartialEq` or `Eq`, and `PartialOrd` or `Ord`. A style's quirks and +/// conditions are ignored. #[derive(Default, Debug, Copy, Clone)] pub struct Style { - /// The foreground color. + /// The foreground color. Defaults to `None`. + /// + /// ```rust + /// use yansi::{Style, Color}; + /// + /// assert_eq!(Style::new().foreground, None); + /// assert_eq!(Style::new().green().foreground, Some(Color::Green)); + /// ``` pub foreground: Option, - /// The background color. + /// The background color. Defaults to `None`. + /// + /// ```rust + /// use yansi::{Style, Color}; + /// + /// assert_eq!(Style::new().background, None); + /// assert_eq!(Style::new().on_red().background, Some(Color::Red)); + /// ``` pub background: Option, pub(crate) attributes: Set, pub(crate) quirks: Set, /// The condition. + /// + /// To check a style's condition directly, invoke it as a function: + /// + /// ```rust + /// use yansi::{Style, Condition}; + /// + /// let style = Style::new().whenever(Condition::ALWAYS); + /// assert!((style.condition)()); + /// + /// let style = Style::new().whenever(Condition::NEVER); + /// assert!(!(style.condition)()); + /// ``` pub condition: Condition, } @@ -45,13 +78,13 @@ impl Style { background: None, attributes: Set::EMPTY, quirks: Set::EMPTY, - condition: Condition::ALWAYS, + condition: Condition::DEFAULT, }; /// Returns a new style with no foreground or background, no attributes - /// or quirks, and an [`ALWAYS`](Condition::ALWAYS) condition. + /// or quirks, and [`Condition::DEFAULT`]. /// - /// This is the default. + /// This is the default returned by [`Default::default()`]. /// /// # Example /// @@ -230,8 +263,6 @@ impl fmt::Write for AnsiSplicer<'_> { } } -// , PartialEq, Eq, PartialOrd, Ord, Hash - impl PartialEq for Style { fn eq(&self, other: &Self) -> bool { let Style { @@ -239,7 +270,7 @@ impl PartialEq for Style { background: bg_a, attributes: attrs_a, quirks: _, - condition: cond_a, + condition: _, } = self; let Style { @@ -247,13 +278,10 @@ impl PartialEq for Style { background: bg_b, attributes: attrs_b, quirks: _, - condition: cond_b, + condition: _, } = other; - fg_a == fg_b - && bg_a == bg_b - && attrs_a == attrs_b - && cond_a == cond_b + fg_a == fg_b && bg_a == bg_b && attrs_a == attrs_b } } @@ -261,11 +289,10 @@ impl Eq for Style { } impl core::hash::Hash for Style { fn hash(&self, state: &mut H) { - let Style { foreground, background, attributes, quirks: _, condition, } = self; + let Style { foreground, background, attributes, quirks: _, condition: _, } = self; foreground.hash(state); background.hash(state); attributes.hash(state); - condition.hash(state); } } @@ -276,7 +303,7 @@ impl PartialOrd for Style { background: bg_a, attributes: attrs_a, quirks: _, - condition: cond_a, + condition: _, } = self; let Style { @@ -284,7 +311,7 @@ impl PartialOrd for Style { background: bg_b, attributes: attrs_b, quirks: _, - condition: cond_b, + condition: _, } = other; match fg_a.partial_cmp(&fg_b) { @@ -297,12 +324,7 @@ impl PartialOrd for Style { ord => return ord, } - match attrs_a.partial_cmp(&attrs_b) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - - cond_a.partial_cmp(&cond_b) + attrs_a.partial_cmp(&attrs_b) } } @@ -313,7 +335,7 @@ impl Ord for Style { background: bg_a, attributes: attrs_a, quirks: _, - condition: cond_a, + condition: _, } = self; let Style { @@ -321,7 +343,7 @@ impl Ord for Style { background: bg_b, attributes: attrs_b, quirks: _, - condition: cond_b, + condition: _, } = other; match fg_a.cmp(&fg_b) { @@ -334,11 +356,6 @@ impl Ord for Style { ord => return ord, } - match attrs_a.cmp(&attrs_b) { - core::cmp::Ordering::Equal => {} - ord => return ord, - } - - cond_a.cmp(&cond_b) + attrs_a.cmp(&attrs_b) } } diff --git a/src/windows.rs b/src/windows.rs index cd6709d..581869c 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -76,8 +76,6 @@ mod windows_console { #[allow(dead_code)] pub fn enable() -> bool { true } - // Try to enable colors on Windows, and try to do it at most once. It's okay - // if we try more than once. We only try when painting is enabled. #[inline(always)] pub fn cache_enable() -> bool { true } } diff --git a/tests/basic.rs b/tests/basic.rs index dec2e5c..538a124 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1,10 +1,12 @@ -use yansi::{Paint, Style, Color::*}; +use yansi::{Paint, Style, Condition, Color::*}; static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); macro_rules! assert_renders { ($($input:expr => $expected:expr,)*) => { let _lock = LOCK.lock().expect("FAIL FAST - LOCK POISONED"); + yansi::enable(); + $( let (input, expected) = ($input.to_string(), $expected.to_string()); if input != expected { @@ -16,6 +18,8 @@ macro_rules! assert_renders { stringify!($input), $input.style); } )* + + yansi::enable_when(Condition::DEFAULT); }; }