blob: 3048c9bfb977705ec2dd4096c6474ff9a8c8c67e [file] [log] [blame] [edit]
use core::fmt::{self, Write};
use crate::color::{Color, Variant};
use crate::attr_quirk::{Attribute, Quirk};
use crate::condition::Condition;
use crate::set::Set;
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::{string::String, borrow::Cow};
#[cfg(feature = "std")]
use std::borrow::Cow;
/// A set of styling options.
///
/// ## Equivalence and Ordering
///
/// Only a style's `foreground`, `background`, and set of `attributes` are
/// 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. 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<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<Color>,
pub(crate) attributes: Set<Attribute>,
pub(crate) quirks: Set<Quirk>,
/// The condition.
///
/// To check a style's condition directly, use [`Style::enabled()`]:
///
/// ```rust
/// use yansi::{Style, Condition};
///
/// let style = Style::new().whenever(Condition::ALWAYS);
/// assert!(style.enabled());
///
/// let style = Style::new().whenever(Condition::NEVER);
/// assert!(!style.enabled());
/// ```
pub condition: Option<Condition>,
}
struct AnsiSplicer<'a> {
f: &'a mut dyn fmt::Write,
splice: bool,
}
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum Application {
fg(Color),
bg(Color),
attr(Attribute),
quirk(Quirk),
whenever(Condition),
}
impl Style {
const DEFAULT: Style = Style {
foreground: None,
background: None,
attributes: Set::EMPTY,
quirks: Set::EMPTY,
condition: None,
};
/// Returns a new style with no foreground or background, no attributes
/// or quirks, and [`Condition::DEFAULT`].
///
/// This is the default returned by [`Default::default()`].
///
/// # Example
///
/// ```rust
/// use yansi::Style;
///
/// assert_eq!(Style::new(), Style::default());
/// ```
#[inline]
pub const fn new() -> Style {
Style::DEFAULT
}
#[inline(always)]
pub(crate) const fn apply(mut self, a: Application) -> Style {
match a {
Application::fg(color) => self.foreground = Some(color),
Application::bg(color) => self.background = Some(color),
Application::whenever(cond) => self.condition = Some(cond),
Application::attr(attr) => self.attributes = self.attributes.insert(attr),
Application::quirk(quirk) => self.quirks = self.quirks.insert(quirk),
}
self
}
/// Returns `true` if this style is enabled, based on
/// [`condition`](Paint.condition).
///
/// **Note:** _For a style to be effected, both this method **and**
/// [`yansi::is_enabled()`](crate::is_enabled) must return `true`._
///
/// When there is no condition set, this method always returns `true`. When
/// a condition has been set, this evaluates the condition and returns the
/// result.
///
/// # Example
///
/// ```rust
/// use yansi::{Style, Condition};
///
/// let style = Style::new().whenever(Condition::ALWAYS);
/// assert!(style.enabled());
///
/// let style = Style::new().whenever(Condition::NEVER);
/// assert!(!style.enabled());
/// ```
pub fn enabled(&self) -> bool {
self.condition.map_or(true, |c| c())
}
/// Writes the ANSI code prefix for the currently set styles.
///
/// This method is intended to be used inside of [`fmt::Display`] and
/// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
/// users should use [`Painted`] for all painting needs.
///
/// This method writes the ANSI code prefix irrespective of whether painting
/// is currently enabled or disabled. To write the prefix only if painting
/// is enabled, condition a call to this method on [`is_enabled()`].
///
/// [`fmt::Display`]: fmt::Display
/// [`fmt::Debug`]: fmt::Debug
/// [`Painted`]: crate::Painted
/// [`is_enabled()`]: crate::is_enabled()
///
/// # Example
///
/// ```rust
/// use core::fmt;
/// use yansi::Style;
///
/// struct CustomItem {
/// item: u32,
/// style: Style
/// }
///
/// impl fmt::Display for CustomItem {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// self.style.fmt_prefix(f)?;
/// write!(f, "number: {}", self.item)?;
/// self.style.fmt_suffix(f)
/// }
/// }
/// ```
pub fn fmt_prefix(&self, f: &mut dyn fmt::Write) -> fmt::Result {
// Give a sequence-free string when no styles are applied.
if self == &Style::DEFAULT {
return Ok(());
}
let brighten = |color: Option<Color>, bright: bool| match (color, bright) {
(Some(color), true) => Some(color.to_bright()),
_ => color
};
let mut f = AnsiSplicer { f, splice: false };
f.write_str("\x1B[")?;
for attr in self.attributes.iter() {
f.splice()?;
attr.fmt(&mut f)?;
}
if let Some(color) = brighten(self.background, self.quirks.contains(Quirk::OnBright)) {
f.splice()?;
color.fmt(&mut f, Variant::Bg)?;
}
if let Some(color) = brighten(self.foreground, self.quirks.contains(Quirk::Bright)) {
f.splice()?;
color.fmt(&mut f, Variant::Fg)?;
}
// All of the sequences end with an `m`.
f.write_char('m')
}
/// Returns the ANSI code sequence prefix for the style as a string.
///
/// This returns a string with the exact same sequence written by
/// [`fmt_prefix()`](Self::fmt_prefix()). See that method for details.
#[cfg(feature = "alloc")]
#[cfg_attr(feature = "_nightly", doc(cfg(feature = "alloc")))]
pub fn prefix(&self) -> Cow<'static, str> {
let mut prefix = String::new();
let _ = self.fmt_prefix(&mut prefix);
prefix.into()
}
/// Writes the ANSI code sequence suffix for the style.
///
/// This method is intended to be used inside of [`fmt::Display`] and
/// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
/// users should use [`Painted`] for all painting needs.
///
/// This method writes the ANSI code suffix irrespective of whether painting
/// is currently enabled or disabled. To write the suffix only if painting
/// is enabled, condition a call to this method on [`is_enabled()`].
///
/// [`fmt::Display`]: fmt::Display
/// [`fmt::Debug`]: fmt::Debug
/// [`Painted`]: crate::Painted
/// [`is_enabled()`]: crate::is_enabled()
///
/// # Example
///
/// ```rust
/// use core::fmt;
/// use yansi::Style;
///
/// struct CustomItem {
/// item: u32,
/// style: Style
/// }
///
/// impl fmt::Display for CustomItem {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// self.style.fmt_prefix(f)?;
/// write!(f, "number: {}", self.item)?;
/// self.style.fmt_suffix(f)
/// }
/// }
/// ```
pub fn fmt_suffix(&self, f: &mut dyn fmt::Write) -> fmt::Result {
if !self.quirks.contains(Quirk::Resetting) && !self.quirks.contains(Quirk::Clear) {
if self.quirks.contains(Quirk::Linger) || self == &Style::DEFAULT {
return Ok(());
}
}
f.write_str("\x1B[0m")
}
/// Returns the ANSI code sequence suffix for the style as a string.
///
/// This returns a string with the exact same sequence written by
/// [`fmt_suffix()`](Self::fmt_suffix()). See that method for details.
#[cfg(feature = "alloc")]
#[cfg_attr(feature = "_nightly", doc(cfg(feature = "alloc")))]
pub fn suffix(&self) -> Cow<'static, str> {
if !self.quirks.contains(Quirk::Resetting) && !self.quirks.contains(Quirk::Clear) {
if self.quirks.contains(Quirk::Linger) || self == &Style::DEFAULT {
return Cow::from("");
}
}
Cow::from("\x1B[0m")
}
properties!([pub const] constructor(Self) -> Self);
}
impl AnsiSplicer<'_> {
fn splice(&mut self) -> fmt::Result {
if self.splice { self.f.write_char(';')?; }
self.splice = true;
Ok(())
}
}
impl fmt::Write for AnsiSplicer<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.f.write_str(s)
}
}
impl PartialEq for Style {
fn eq(&self, other: &Self) -> bool {
let Style {
foreground: fg_a,
background: bg_a,
attributes: attrs_a,
quirks: _,
condition: _,
} = self;
let Style {
foreground: fg_b,
background: bg_b,
attributes: attrs_b,
quirks: _,
condition: _,
} = other;
fg_a == fg_b && bg_a == bg_b && attrs_a == attrs_b
}
}
impl Eq for Style { }
impl core::hash::Hash for Style {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let Style { foreground, background, attributes, quirks: _, condition: _, } = self;
foreground.hash(state);
background.hash(state);
attributes.hash(state);
}
}
impl PartialOrd for Style {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
let Style {
foreground: fg_a,
background: bg_a,
attributes: attrs_a,
quirks: _,
condition: _,
} = self;
let Style {
foreground: fg_b,
background: bg_b,
attributes: attrs_b,
quirks: _,
condition: _,
} = other;
match fg_a.partial_cmp(&fg_b) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match bg_a.partial_cmp(&bg_b) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
attrs_a.partial_cmp(&attrs_b)
}
}
impl Ord for Style {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
let Style {
foreground: fg_a,
background: bg_a,
attributes: attrs_a,
quirks: _,
condition: _,
} = self;
let Style {
foreground: fg_b,
background: bg_b,
attributes: attrs_b,
quirks: _,
condition: _,
} = other;
match fg_a.cmp(&fg_b) {
core::cmp::Ordering::Equal => {}
ord => return ord,
}
match bg_a.cmp(&bg_b) {
core::cmp::Ordering::Equal => {}
ord => return ord,
}
attrs_a.cmp(&attrs_b)
}
}