Changed vec of Attributes to bitfield (#380)

This commit is contained in:
Canop 2020-02-07 14:06:41 +01:00 committed by GitHub
parent 2ba0ac07bc
commit f58aca9354
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 275 additions and 74 deletions

View File

@ -5,7 +5,7 @@ use crossterm::{cursor, queue, style};
use std::io::Write; use std::io::Write;
const ATTRIBUTES: [(style::Attribute, style::Attribute); 6] = [ const ATTRIBUTES: [(style::Attribute, style::Attribute); 6] = [
(style::Attribute::Bold, style::Attribute::NoBold), (style::Attribute::Bold, style::Attribute::NormalIntensity),
(style::Attribute::Italic, style::Attribute::NoItalic), (style::Attribute::Italic, style::Attribute::NoItalic),
(style::Attribute::Underlined, style::Attribute::NoUnderline), (style::Attribute::Underlined, style::Attribute::NoUnderline),
(style::Attribute::Reverse, style::Attribute::NoReverse), (style::Attribute::Reverse, style::Attribute::NoReverse),

View File

@ -118,6 +118,7 @@ use crate::{impl_display, Command};
pub(crate) use self::enums::Colored; pub(crate) use self::enums::Colored;
pub use self::{ pub use self::{
attributes::Attributes,
content_style::ContentStyle, content_style::ContentStyle,
enums::{Attribute, Color}, enums::{Attribute, Color},
styled_content::StyledContent, styled_content::StyledContent,
@ -127,6 +128,7 @@ pub use self::{
#[macro_use] #[macro_use]
mod macros; mod macros;
mod ansi; mod ansi;
mod attributes;
mod content_style; mod content_style;
mod enums; mod enums;
mod styled_content; mod styled_content;
@ -290,6 +292,30 @@ impl Command for SetAttribute {
} }
} }
/// A command that sets several attributes.
///
/// See [`Attributes`](struct.Attributes.html) for more info.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetAttributes(pub Attributes);
impl Command for SetAttributes {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::set_attrs_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
// attributes are not supported by WinAPI.
Ok(())
}
}
/// A command that prints styled content. /// A command that prints styled content.
/// ///
/// See [`StyledContent`](struct.StyledContent.html) for more info. /// See [`StyledContent`](struct.StyledContent.html) for more info.

View File

@ -3,7 +3,7 @@
use crate::{ use crate::{
csi, csi,
style::{Attribute, Color, Colored}, style::{Attribute, Attributes, Color, Colored},
}; };
pub(crate) fn set_fg_csi_sequence(fg_color: Color) -> String { pub(crate) fn set_fg_csi_sequence(fg_color: Color) -> String {
@ -21,7 +21,17 @@ pub(crate) fn set_bg_csi_sequence(bg_color: Color) -> String {
} }
pub(crate) fn set_attr_csi_sequence(attribute: Attribute) -> String { pub(crate) fn set_attr_csi_sequence(attribute: Attribute) -> String {
format!(csi!("{}m"), attribute as i16) format!(csi!("{}m"), attribute.sgr())
}
pub(crate) fn set_attrs_csi_sequence(attributes: Attributes) -> String {
let mut ansi = String::new();
for attr in Attribute::iterator() {
if attributes.has(attr) {
ansi.push_str(&format!(csi!("{}m"), attr.sgr()));
}
}
ansi
} }
pub(crate) const RESET_CSI_SEQUENCE: &str = csi!("0m"); pub(crate) const RESET_CSI_SEQUENCE: &str = csi!("0m");

119
src/style/attributes.rs Normal file
View File

@ -0,0 +1,119 @@
use crate::style::Attribute;
use std::ops::{BitAnd, BitOr, BitXor};
/// a bitset for all possible attributes
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Attributes(u32);
impl From<Attribute> for Attributes {
fn from(attribute: Attribute) -> Self {
Self(attribute.bytes())
}
}
impl From<&[Attribute]> for Attributes {
fn from(arr: &[Attribute]) -> Self {
let mut attributes = Attributes::default();
for &attr in arr {
attributes.set(attr);
}
attributes
}
}
impl BitAnd<Attribute> for Attributes {
type Output = Self;
fn bitand(self, rhs: Attribute) -> Self {
Self(self.0 & rhs.bytes())
}
}
impl BitAnd for Attributes {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
impl BitOr<Attribute> for Attributes {
type Output = Self;
fn bitor(self, rhs: Attribute) -> Self {
Self(self.0 | rhs.bytes())
}
}
impl BitOr for Attributes {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl BitXor<Attribute> for Attributes {
type Output = Self;
fn bitxor(self, rhs: Attribute) -> Self {
Self(self.0 ^ rhs.bytes())
}
}
impl BitXor for Attributes {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self {
Self(self.0 ^ rhs.0)
}
}
impl Attributes {
/// Sets the attribute.
/// If it's already set, this does nothing.
#[inline(always)]
pub fn set(&mut self, attribute: Attribute) {
self.0 |= attribute.bytes();
}
/// Unsets the attribute.
/// If it's not set, this changes nothing.
#[inline(always)]
pub fn unset(&mut self, attribute: Attribute) {
self.0 &= !attribute.bytes();
}
/// Sets the attribute if it's unset, unset it
/// if it is set.
#[inline(always)]
pub fn toggle(&mut self, attribute: Attribute) {
self.0 ^= attribute.bytes();
}
/// Returns whether the attribute is set.
#[inline(always)]
pub fn has(self, attribute: Attribute) -> bool {
self.0 & attribute.bytes() != 0
}
/// Sets all the passed attributes. Removes none.
#[inline(always)]
pub fn extend(&mut self, attributes: Attributes) {
self.0 |= attributes.0;
}
/// Returns whether there is no attribute set.
#[inline(always)]
pub fn is_empty(self) -> bool {
self.0 == 0
}
}
#[cfg(test)]
mod tests {
use super::{Attribute, Attributes};
#[test]
fn test_attributes() {
let mut attributes: Attributes = Attribute::Bold.into();
assert!(attributes.has(Attribute::Bold));
attributes.set(Attribute::Italic);
assert!(attributes.has(Attribute::Italic));
attributes.unset(Attribute::Italic);
assert!(!attributes.has(Attribute::Italic));
attributes.toggle(Attribute::Bold);
assert!(attributes.is_empty());
}
}

View File

@ -2,7 +2,7 @@
use std::fmt::Display; use std::fmt::Display;
use crate::style::{Attribute, Color, StyledContent}; use crate::style::{Attribute, Attributes, Color, StyledContent};
/// The style that can be put on content. /// The style that can be put on content.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -12,7 +12,7 @@ pub struct ContentStyle {
/// The background color. /// The background color.
pub background_color: Option<Color>, pub background_color: Option<Color>,
/// List of attributes. /// List of attributes.
pub attributes: Vec<Attribute>, pub attributes: Attributes,
} }
impl ContentStyle { impl ContentStyle {
@ -51,7 +51,7 @@ impl ContentStyle {
/// You can add more attributes by calling this method multiple times. /// You can add more attributes by calling this method multiple times.
#[inline] #[inline]
pub fn attribute(mut self, attr: Attribute) -> ContentStyle { pub fn attribute(mut self, attr: Attribute) -> ContentStyle {
self.attributes.push(attr); self.attributes.set(attr);
self self
} }
} }
@ -65,11 +65,11 @@ mod tests {
let content_style = ContentStyle::new() let content_style = ContentStyle::new()
.foreground(Color::Blue) .foreground(Color::Blue)
.background(Color::Red) .background(Color::Red)
.attribute(Attribute::Reset); .attribute(Attribute::Bold);
assert_eq!(content_style.foreground_color, Some(Color::Blue)); assert_eq!(content_style.foreground_color, Some(Color::Blue));
assert_eq!(content_style.background_color, Some(Color::Red)); assert_eq!(content_style.background_color, Some(Color::Red));
assert_eq!(content_style.attributes[0], Attribute::Reset); assert!(content_style.attributes.has(Attribute::Bold));
} }
#[test] #[test]
@ -83,6 +83,6 @@ mod tests {
assert_eq!(styled_content.style().foreground_color, Some(Color::Blue)); assert_eq!(styled_content.style().foreground_color, Some(Color::Blue));
assert_eq!(styled_content.style().background_color, Some(Color::Red)); assert_eq!(styled_content.style().background_color, Some(Color::Red));
assert_eq!(styled_content.style().attributes[0], Attribute::Reset); assert!(styled_content.style().attributes.has(Attribute::Reset));
} }
} }

View File

@ -5,59 +5,89 @@ use serde::{Deserialize, Serialize};
use super::super::SetAttribute; use super::super::SetAttribute;
/// Represents an attribute. // This macro generates the Attribute enum, its iterator
/// // function, and the static array containing the sgr code
/// # Platform-specific Notes // of each attribute
/// macro_rules! Attribute {
/// * Only UNIX and Windows 10 terminals do support text attributes. (
/// * Keep in mind that not all terminals support all attributes. $(
/// * Crossterm implements almost all attributes listed in the $(#[$inner:ident $($args:tt)*])*
/// [SGR parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters). $name:ident = $sgr:expr,
/// )*
/// | Attribute | Windows | UNIX | Notes | ) => {
/// | :-- | :--: | :--: | :-- | /// Represents an attribute.
/// | `Reset` | ✓ | ✓ | | ///
/// | `Bold` | ✓ | ✓ | | /// # Platform-specific Notes
/// | `Dim` | ✓ | ✓ | | ///
/// | `Italic` | ? | ? | Not widely supported, sometimes treated as inverse. | /// * Only UNIX and Windows 10 terminals do support text attributes.
/// | `Underlined` | ✓ | ✓ | | /// * Keep in mind that not all terminals support all attributes.
/// | `SlowBlink` | ? | ? | Not widely supported, sometimes treated as inverse. | /// * Crossterm implements almost all attributes listed in the
/// | `RapidBlink` | ? | ? | Not widely supported. MS-DOS ANSI.SYS; 150+ per minute. | /// [SGR parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters).
/// | `Reverse` | ✓ | ✓ | | ///
/// | `Hidden` | ✓ | ✓ | Also known as Conceal. | /// | Attribute | Windows | UNIX | Notes |
/// | `Fraktur` | ✗ | ✓ | Legible characters, but marked for deletion. | /// | :-- | :--: | :--: | :-- |
/// | `DefaultForegroundColor` | ? | ? | Implementation specific (according to standard). | /// | `Reset` | ✓ | ✓ | |
/// | `DefaultBackgroundColor` | ? | ? | Implementation specific (according to standard). | /// | `Bold` | ✓ | ✓ | |
/// | `Framed` | ? | ? | Not widely supported. | /// | `Dim` | ✓ | ✓ | |
/// | `Encircled` | ? | ? | This should turn on the encircled attribute. | /// | `Italic` | ? | ? | Not widely supported, sometimes treated as inverse. |
/// | `OverLined` | ? | ? | This should draw a line at the top of the text. | /// | `Underlined` | ✓ | ✓ | |
/// /// | `SlowBlink` | ? | ? | Not widely supported, sometimes treated as inverse. |
/// # Examples /// | `RapidBlink` | ? | ? | Not widely supported. MS-DOS ANSI.SYS; 150+ per minute. |
/// /// | `Reverse` | ✓ | ✓ | |
/// Basic usage: /// | `Hidden` | ✓ | ✓ | Also known as Conceal. |
/// /// | `Fraktur` | ✗ | ✓ | Legible characters, but marked for deletion. |
/// ```no_run /// | `DefaultForegroundColor` | ? | ? | Implementation specific (according to standard). |
/// use crossterm::style::Attribute; /// | `DefaultBackgroundColor` | ? | ? | Implementation specific (according to standard). |
/// /// | `Framed` | ? | ? | Not widely supported. |
/// println!( /// | `Encircled` | ? | ? | This should turn on the encircled attribute. |
/// "{} Underlined {} No Underline", /// | `OverLined` | ? | ? | This should draw a line at the top of the text. |
/// Attribute::Underlined, ///
/// Attribute::NoUnderline /// # Examples
/// ); ///
/// ``` /// Basic usage:
/// ///
/// Style existing text: /// ```no_run
/// /// use crossterm::style::Attribute;
/// ```no_run ///
/// use crossterm::style::Styler; /// println!(
/// /// "{} Underlined {} No Underline",
/// println!("{}", "Bold text".bold()); /// Attribute::Underlined,
/// println!("{}", "Underlined text".underlined()); /// Attribute::NoUnderline
/// println!("{}", "Negative text".negative()); /// );
/// ``` /// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] ///
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] /// Style existing text:
pub enum Attribute { ///
/// ```no_run
/// use crossterm::style::Styler;
///
/// println!("{}", "Bold text".bold());
/// println!("{}", "Underlined text".underlined());
/// println!("{}", "Negative text".negative());
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum Attribute {
$(
$(#[$inner $($args)*])*
$name,
)*
}
pub static SGR: &'static[i16] = &[
$($sgr,)*
];
impl Attribute {
/// Iterates over all the variants of the Attribute enum.
pub fn iterator() -> impl Iterator<Item = Attribute> {
use self::Attribute::*;
[ $($name,)* ].iter().copied()
}
}
}
}
Attribute! {
/// Resets all the attributes. /// Resets all the attributes.
Reset = 0, Reset = 0,
/// Increases the text intensity. /// Increases the text intensity.
@ -82,7 +112,7 @@ pub enum Attribute {
/// ///
/// Mostly used for [mathematical alphanumeric symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols). /// Mostly used for [mathematical alphanumeric symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols).
Fraktur = 20, Fraktur = 20,
/// Turns off the `Bold` attribute. /// Turns off the `Bold` attribute. - Inconsistent - Prefer to use NormalIntensity
NoBold = 21, NoBold = 21,
/// Switches the text back to normal intensity (no bold, italic). /// Switches the text back to normal intensity (no bold, italic).
NormalIntensity = 22, NormalIntensity = 22,
@ -108,8 +138,6 @@ pub enum Attribute {
NotFramedOrEncircled = 54, NotFramedOrEncircled = 54,
/// Turns off the `OverLined` attribute. /// Turns off the `OverLined` attribute.
NotOverLined = 55, NotOverLined = 55,
#[doc(hidden)]
__Nonexhaustive,
} }
impl Display for Attribute { impl Display for Attribute {
@ -118,3 +146,22 @@ impl Display for Attribute {
Ok(()) Ok(())
} }
} }
impl Attribute {
/// Returns a u32 with one bit set, which is the
/// signature of this attribute in the Attributes
/// bitset.
///
/// The +1 enables storing Reset (whose index is 0)
/// in the bitset Attributes.
#[inline(always)]
pub const fn bytes(self) -> u32 {
1 << ((self as u32) + 1)
}
/// Returns the SGR attribute value.
///
/// See https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
pub fn sgr(self) -> i16 {
SGR[self as usize]
}
}

View File

@ -39,7 +39,7 @@ macro_rules! def_str_attr {
fn $name(self) -> StyledContent<&'static str> { fn $name(self) -> StyledContent<&'static str> {
StyledContent::new( StyledContent::new(
ContentStyle { ContentStyle {
attributes: vec![ $attr ], attributes: $attr.into(),
..Default::default() ..Default::default()
}, },
self self

View File

@ -8,7 +8,7 @@ use std::{
use crate::{ use crate::{
queue, queue,
style::{ style::{
Attribute, Color, Colorize, ContentStyle, ResetColor, SetAttribute, SetBackgroundColor, Attribute, Color, Colorize, ContentStyle, ResetColor, SetAttributes, SetBackgroundColor,
SetForegroundColor, Styler, SetForegroundColor, Styler,
}, },
}; };
@ -104,8 +104,8 @@ impl<D: Display> Display for StyledContent<D> {
reset = true; reset = true;
} }
for attr in &self.style.attributes { if !self.style.attributes.is_empty() {
queue!(f, SetAttribute(*attr)).map_err(|_| fmt::Error)?; queue!(f, SetAttributes(self.style.attributes)).map_err(|_| fmt::Error)?;
reset = true; reset = true;
} }
@ -183,7 +183,7 @@ mod tests {
let style = ContentStyle::new() let style = ContentStyle::new()
.foreground(Color::Blue) .foreground(Color::Blue)
.background(Color::Red) .background(Color::Red)
.attribute(Attribute::Reset); .attribute(Attribute::Bold);
let mut styled_content = style.apply("test"); let mut styled_content = style.apply("test");
@ -194,8 +194,7 @@ mod tests {
assert_eq!(styled_content.style.foreground_color, Some(Color::Green)); assert_eq!(styled_content.style.foreground_color, Some(Color::Green));
assert_eq!(styled_content.style.background_color, Some(Color::Magenta)); assert_eq!(styled_content.style.background_color, Some(Color::Magenta));
assert_eq!(styled_content.style.attributes.len(), 2); assert!(styled_content.style.attributes.has(Attribute::Bold));
assert_eq!(styled_content.style.attributes[0], Attribute::Reset); assert!(styled_content.style.attributes.has(Attribute::NoItalic));
assert_eq!(styled_content.style.attributes[1], Attribute::NoItalic);
} }
} }