Revision control
Copy as Markdown
Other Tools
/// A set of text effects
///
/// # Examples
///
/// ```rust
/// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
/// ```
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Effects(u16);
impl Effects {
/// No [`Effects`] applied
const PLAIN: Self = Effects(0);
#[allow(missing_docs)]
pub const BOLD: Self = Effects(1 << 0);
#[allow(missing_docs)]
pub const DIMMED: Self = Effects(1 << 1);
/// Not widely supported. Sometimes treated as inverse or blink
pub const ITALIC: Self = Effects(1 << 2);
/// Style extensions exist for Kitty, VTE, mintty and iTerm2.
pub const UNDERLINE: Self = Effects(1 << 3);
#[allow(missing_docs)]
pub const DOUBLE_UNDERLINE: Self = Effects(1 << 4);
#[allow(missing_docs)]
pub const CURLY_UNDERLINE: Self = Effects(1 << 5);
#[allow(missing_docs)]
pub const DOTTED_UNDERLINE: Self = Effects(1 << 6);
#[allow(missing_docs)]
pub const DASHED_UNDERLINE: Self = Effects(1 << 7);
#[allow(missing_docs)]
pub const BLINK: Self = Effects(1 << 8);
/// Swap foreground and background colors; inconsistent emulation
pub const INVERT: Self = Effects(1 << 9);
#[allow(missing_docs)]
pub const HIDDEN: Self = Effects(1 << 10);
/// Characters legible but marked as if for deletion. Not supported in Terminal.app
pub const STRIKETHROUGH: Self = Effects(1 << 11);
/// No effects enabled
///
/// # Examples
///
/// ```rust
/// let effects = anstyle::Effects::new();
/// ```
#[inline]
pub const fn new() -> Self {
Self::PLAIN
}
/// Check if no effects are enabled
///
/// # Examples
///
/// ```rust
/// let effects = anstyle::Effects::new();
/// assert!(effects.is_plain());
///
/// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
/// assert!(!effects.is_plain());
/// ```
#[inline]
pub const fn is_plain(self) -> bool {
self.0 == Self::PLAIN.0
}
/// Returns `true` if all of the effects in `other` are contained within `self`.
///
/// # Examples
///
/// ```rust
/// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
/// assert!(effects.contains(anstyle::Effects::BOLD));
///
/// let effects = anstyle::Effects::new();
/// assert!(!effects.contains(anstyle::Effects::BOLD));
/// ```
#[inline(always)]
pub const fn contains(self, other: Effects) -> bool {
(other.0 & self.0) == other.0
}
/// Inserts the specified effects in-place.
///
/// # Examples
///
/// ```rust
/// let effects = anstyle::Effects::new().insert(anstyle::Effects::new());
/// assert!(effects.is_plain());
///
/// let effects = anstyle::Effects::new().insert(anstyle::Effects::BOLD);
/// assert!(effects.contains(anstyle::Effects::BOLD));
/// ```
#[inline(always)]
#[must_use]
pub const fn insert(mut self, other: Effects) -> Self {
self.0 |= other.0;
self
}
/// Removes the specified effects in-place.
///
/// # Examples
///
/// ```rust
/// let effects = (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE).remove(anstyle::Effects::BOLD);
/// assert!(!effects.contains(anstyle::Effects::BOLD));
/// assert!(effects.contains(anstyle::Effects::UNDERLINE));
/// ```
#[inline(always)]
#[must_use]
pub const fn remove(mut self, other: Effects) -> Self {
self.0 &= !other.0;
self
}
/// Reset all effects in-place
/// ```rust
/// let effects = (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE).clear();
/// assert!(!effects.contains(anstyle::Effects::BOLD));
/// assert!(!effects.contains(anstyle::Effects::UNDERLINE));
/// ```
#[inline(always)]
#[must_use]
pub const fn clear(self) -> Self {
Self::new()
}
/// Enable or disable the specified effects depending on the passed value.
///
/// # Examples
///
/// ```rust
/// let effects = anstyle::Effects::new().set(anstyle::Effects::BOLD, true);
/// assert!(effects.contains(anstyle::Effects::BOLD));
/// ```
#[inline]
#[must_use]
pub const fn set(self, other: Self, enable: bool) -> Self {
if enable {
self.insert(other)
} else {
self.remove(other)
}
}
/// Iterate over enabled effects
#[inline(always)]
pub fn iter(self) -> EffectIter {
EffectIter {
index: 0,
effects: self,
}
}
/// Iterate over enabled effect indices
#[inline(always)]
pub(crate) fn index_iter(self) -> EffectIndexIter {
EffectIndexIter {
index: 0,
effects: self,
}
}
/// Render the ANSI code
#[inline]
pub fn render(self) -> impl core::fmt::Display + Copy {
EffectsDisplay(self)
}
#[inline]
#[cfg(feature = "std")]
pub(crate) fn write_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {
for index in self.index_iter() {
write.write_all(METADATA[index].escape.as_bytes())?;
}
Ok(())
}
}
/// # Examples
///
/// ```rust
/// let effects = anstyle::Effects::new();
/// assert_eq!(format!("{:?}", effects), "Effects()");
///
/// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
/// assert_eq!(format!("{:?}", effects), "Effects(BOLD | UNDERLINE)");
/// ```
impl core::fmt::Debug for Effects {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Effects(")?;
for (i, index) in self.index_iter().enumerate() {
if i != 0 {
write!(f, " | ")?;
}
write!(f, "{}", METADATA[index].name)?;
}
write!(f, ")")?;
Ok(())
}
}
/// # Examples
///
/// ```rust
/// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
/// assert_eq!(format!("{:?}", effects), "Effects(BOLD | UNDERLINE)");
/// ```
impl core::ops::BitOr for Effects {
type Output = Self;
#[inline(always)]
fn bitor(self, rhs: Self) -> Self {
self.insert(rhs)
}
}
/// # Examples
///
/// ```rust
/// let mut effects = anstyle::Effects::BOLD;
/// effects |= anstyle::Effects::UNDERLINE;
/// assert_eq!(format!("{:?}", effects), "Effects(BOLD | UNDERLINE)");
/// ```
impl core::ops::BitOrAssign for Effects {
#[inline]
fn bitor_assign(&mut self, other: Self) {
*self = self.insert(other);
}
}
/// # Examples
///
/// ```rust
/// let effects = (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE) - anstyle::Effects::BOLD;
/// assert_eq!(format!("{:?}", effects), "Effects(UNDERLINE)");
/// ```
impl core::ops::Sub for Effects {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self {
self.remove(other)
}
}
/// # Examples
///
/// ```rust
/// let mut effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
/// effects -= anstyle::Effects::BOLD;
/// assert_eq!(format!("{:?}", effects), "Effects(UNDERLINE)");
/// ```
impl core::ops::SubAssign for Effects {
#[inline]
fn sub_assign(&mut self, other: Self) {
*self = self.remove(other);
}
}
pub(crate) struct Metadata {
pub(crate) name: &'static str,
pub(crate) escape: &'static str,
}
pub(crate) const METADATA: [Metadata; 12] = [
Metadata {
name: "BOLD",
escape: escape!("1"),
},
Metadata {
name: "DIMMED",
escape: escape!("2"),
},
Metadata {
name: "ITALIC",
escape: escape!("3"),
},
Metadata {
name: "UNDERLINE",
escape: escape!("4"),
},
Metadata {
name: "DOUBLE_UNDERLINE",
escape: escape!("21"),
},
Metadata {
name: "CURLY_UNDERLINE",
escape: escape!("4:3"),
},
Metadata {
name: "DOTTED_UNDERLINE",
escape: escape!("4:4"),
},
Metadata {
name: "DASHED_UNDERLINE",
escape: escape!("4:5"),
},
Metadata {
name: "BLINK",
escape: escape!("5"),
},
Metadata {
name: "INVERT",
escape: escape!("7"),
},
Metadata {
name: "HIDDEN",
escape: escape!("8"),
},
Metadata {
name: "STRIKETHROUGH",
escape: escape!("9"),
},
];
#[derive(Copy, Clone, Default, Debug)]
struct EffectsDisplay(Effects);
impl core::fmt::Display for EffectsDisplay {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for index in self.0.index_iter() {
let escape = METADATA[index].escape;
write!(f, "{escape}")?;
}
Ok(())
}
}
/// Enumerate each enabled value in [`Effects`]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EffectIter {
index: usize,
effects: Effects,
}
impl Iterator for EffectIter {
type Item = Effects;
fn next(&mut self) -> Option<Self::Item> {
while self.index < METADATA.len() {
let index = self.index;
self.index += 1;
let effect = Effects(1 << index);
if self.effects.contains(effect) {
return Some(effect);
}
}
None
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct EffectIndexIter {
index: usize,
effects: Effects,
}
impl Iterator for EffectIndexIter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
while self.index < METADATA.len() {
let index = self.index;
self.index += 1;
let effect = Effects(1 << index);
if self.effects.contains(effect) {
return Some(index);
}
}
None
}
}
#[cfg(test)]
#[cfg(feature = "std")]
mod test {
use super::*;
#[test]
fn print_size_of() {
use std::mem::size_of;
dbg!(size_of::<Effects>());
dbg!(size_of::<EffectsDisplay>());
}
#[test]
fn no_align() {
#[track_caller]
fn assert_no_align(d: impl core::fmt::Display) {
let expected = format!("{d}");
let actual = format!("{d:<10}");
assert_eq!(expected, actual);
}
assert_no_align(Effects::BOLD.render());
}
}