| 1 | use parking_lot::Once;
|
| 2 | use std::fmt::{self, Formatter};
|
| 3 | use std::sync::atomic::{AtomicBool, Ordering};
|
| 4 |
|
| 5 | #[cfg (feature = "serde" )]
|
| 6 | use serde::{Deserialize, Serialize};
|
| 7 |
|
| 8 | use crate::style::{parse_next_u8, Color};
|
| 9 |
|
| 10 | /// Represents a foreground or background color.
|
| 11 | ///
|
| 12 | /// This can be converted to a [Colors](struct.Colors.html) by calling `into()` and applied
|
| 13 | /// using the [SetColors](struct.SetColors.html) command.
|
| 14 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))]
|
| 15 | #[derive (Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
| 16 | pub enum Colored {
|
| 17 | /// A foreground color.
|
| 18 | ForegroundColor(Color),
|
| 19 | /// A background color.
|
| 20 | BackgroundColor(Color),
|
| 21 | /// An underline color.
|
| 22 | /// Important: doesn't work on windows 10 or lower.
|
| 23 | UnderlineColor(Color),
|
| 24 | }
|
| 25 |
|
| 26 | static ANSI_COLOR_DISABLED: AtomicBool = AtomicBool::new(false);
|
| 27 | static INITIALIZER: Once = Once::new();
|
| 28 |
|
| 29 | impl Colored {
|
| 30 | /// Parse an ANSI foreground or background color.
|
| 31 | /// This is the string that would appear within an `ESC [ <str> m` escape sequence, as found in
|
| 32 | /// various configuration files.
|
| 33 | ///
|
| 34 | /// # Examples
|
| 35 | ///
|
| 36 | /// ```
|
| 37 | /// use crossterm::style::{Colored::{self, ForegroundColor, BackgroundColor}, Color};
|
| 38 | ///
|
| 39 | /// assert_eq!(Colored::parse_ansi("38;5;0" ), Some(ForegroundColor(Color::Black)));
|
| 40 | /// assert_eq!(Colored::parse_ansi("38;5;26" ), Some(ForegroundColor(Color::AnsiValue(26))));
|
| 41 | /// assert_eq!(Colored::parse_ansi("48;2;50;60;70" ), Some(BackgroundColor(Color::Rgb { r: 50, g: 60, b: 70 })));
|
| 42 | /// assert_eq!(Colored::parse_ansi("49" ), Some(BackgroundColor(Color::Reset)));
|
| 43 | /// assert_eq!(Colored::parse_ansi("invalid color" ), None);
|
| 44 | /// ```
|
| 45 | ///
|
| 46 | /// Currently, 3/4 bit color values aren't supported so return `None`.
|
| 47 | ///
|
| 48 | /// See also: [`Color::parse_ansi`].
|
| 49 | pub fn parse_ansi(ansi: &str) -> Option<Self> {
|
| 50 | use Colored::{BackgroundColor, ForegroundColor, UnderlineColor};
|
| 51 |
|
| 52 | let values = &mut ansi.split(';' );
|
| 53 |
|
| 54 | let output = match parse_next_u8(values)? {
|
| 55 | 38 => return Color::parse_ansi_iter(values).map(ForegroundColor),
|
| 56 | 48 => return Color::parse_ansi_iter(values).map(BackgroundColor),
|
| 57 | 58 => return Color::parse_ansi_iter(values).map(UnderlineColor),
|
| 58 |
|
| 59 | 39 => ForegroundColor(Color::Reset),
|
| 60 | 49 => BackgroundColor(Color::Reset),
|
| 61 | 59 => UnderlineColor(Color::Reset),
|
| 62 |
|
| 63 | _ => return None,
|
| 64 | };
|
| 65 |
|
| 66 | if values.next().is_some() {
|
| 67 | return None;
|
| 68 | }
|
| 69 |
|
| 70 | Some(output)
|
| 71 | }
|
| 72 |
|
| 73 | /// Checks whether ansi color sequences are disabled by setting of NO_COLOR
|
| 74 | /// in environment as per <https://no-color.org/>
|
| 75 | pub fn ansi_color_disabled() -> bool {
|
| 76 | !std::env::var("NO_COLOR" )
|
| 77 | .unwrap_or("" .to_string())
|
| 78 | .is_empty()
|
| 79 | }
|
| 80 |
|
| 81 | pub fn ansi_color_disabled_memoized() -> bool {
|
| 82 | INITIALIZER.call_once(|| {
|
| 83 | ANSI_COLOR_DISABLED.store(Self::ansi_color_disabled(), Ordering::SeqCst);
|
| 84 | });
|
| 85 |
|
| 86 | ANSI_COLOR_DISABLED.load(Ordering::SeqCst)
|
| 87 | }
|
| 88 |
|
| 89 | pub fn set_ansi_color_disabled(val: bool) {
|
| 90 | // Force the one-time initializer to run.
|
| 91 | _ = Self::ansi_color_disabled_memoized();
|
| 92 | ANSI_COLOR_DISABLED.store(val, Ordering::SeqCst);
|
| 93 | }
|
| 94 | }
|
| 95 |
|
| 96 | impl fmt::Display for Colored {
|
| 97 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
| 98 | let color;
|
| 99 |
|
| 100 | if Self::ansi_color_disabled_memoized() {
|
| 101 | return Ok(());
|
| 102 | }
|
| 103 |
|
| 104 | match *self {
|
| 105 | Colored::ForegroundColor(new_color) => {
|
| 106 | if new_color == Color::Reset {
|
| 107 | return f.write_str("39" );
|
| 108 | } else {
|
| 109 | f.write_str("38;" )?;
|
| 110 | color = new_color;
|
| 111 | }
|
| 112 | }
|
| 113 | Colored::BackgroundColor(new_color) => {
|
| 114 | if new_color == Color::Reset {
|
| 115 | return f.write_str("49" );
|
| 116 | } else {
|
| 117 | f.write_str("48;" )?;
|
| 118 | color = new_color;
|
| 119 | }
|
| 120 | }
|
| 121 | Colored::UnderlineColor(new_color) => {
|
| 122 | if new_color == Color::Reset {
|
| 123 | return f.write_str("59" );
|
| 124 | } else {
|
| 125 | f.write_str("58;" )?;
|
| 126 | color = new_color;
|
| 127 | }
|
| 128 | }
|
| 129 | }
|
| 130 |
|
| 131 | match color {
|
| 132 | Color::Black => f.write_str("5;0" ),
|
| 133 | Color::DarkGrey => f.write_str("5;8" ),
|
| 134 | Color::Red => f.write_str("5;9" ),
|
| 135 | Color::DarkRed => f.write_str("5;1" ),
|
| 136 | Color::Green => f.write_str("5;10" ),
|
| 137 | Color::DarkGreen => f.write_str("5;2" ),
|
| 138 | Color::Yellow => f.write_str("5;11" ),
|
| 139 | Color::DarkYellow => f.write_str("5;3" ),
|
| 140 | Color::Blue => f.write_str("5;12" ),
|
| 141 | Color::DarkBlue => f.write_str("5;4" ),
|
| 142 | Color::Magenta => f.write_str("5;13" ),
|
| 143 | Color::DarkMagenta => f.write_str("5;5" ),
|
| 144 | Color::Cyan => f.write_str("5;14" ),
|
| 145 | Color::DarkCyan => f.write_str("5;6" ),
|
| 146 | Color::White => f.write_str("5;15" ),
|
| 147 | Color::Grey => f.write_str("5;7" ),
|
| 148 | Color::Rgb { r, g, b } => write!(f, "2; {r}; {g}; {b}" ),
|
| 149 | Color::AnsiValue(val) => write!(f, "5; {val}" ),
|
| 150 | _ => Ok(()),
|
| 151 | }
|
| 152 | }
|
| 153 | }
|
| 154 |
|
| 155 | #[cfg (test)]
|
| 156 | mod tests {
|
| 157 | use crate::style::{Color, Colored};
|
| 158 |
|
| 159 | fn check_format_color(colored: Colored, expected: &str) {
|
| 160 | Colored::set_ansi_color_disabled(true);
|
| 161 | assert_eq!(colored.to_string(), "" );
|
| 162 | Colored::set_ansi_color_disabled(false);
|
| 163 | assert_eq!(colored.to_string(), expected);
|
| 164 | }
|
| 165 |
|
| 166 | #[test ]
|
| 167 | fn test_format_fg_color() {
|
| 168 | let colored = Colored::ForegroundColor(Color::Red);
|
| 169 | check_format_color(colored, "38;5;9" );
|
| 170 | }
|
| 171 |
|
| 172 | #[test ]
|
| 173 | fn test_format_bg_color() {
|
| 174 | let colored = Colored::BackgroundColor(Color::Red);
|
| 175 | check_format_color(colored, "48;5;9" );
|
| 176 | }
|
| 177 |
|
| 178 | #[test ]
|
| 179 | fn test_format_reset_fg_color() {
|
| 180 | let colored = Colored::ForegroundColor(Color::Reset);
|
| 181 | check_format_color(colored, "39" );
|
| 182 | }
|
| 183 |
|
| 184 | #[test ]
|
| 185 | fn test_format_reset_bg_color() {
|
| 186 | let colored = Colored::BackgroundColor(Color::Reset);
|
| 187 | check_format_color(colored, "49" );
|
| 188 | }
|
| 189 |
|
| 190 | #[test ]
|
| 191 | fn test_format_fg_rgb_color() {
|
| 192 | let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 });
|
| 193 | check_format_color(colored, "48;2;1;2;3" );
|
| 194 | }
|
| 195 |
|
| 196 | #[test ]
|
| 197 | fn test_format_fg_ansi_color() {
|
| 198 | let colored = Colored::ForegroundColor(Color::AnsiValue(255));
|
| 199 | check_format_color(colored, "38;5;255" );
|
| 200 | }
|
| 201 |
|
| 202 | #[test ]
|
| 203 | fn test_parse_ansi_fg() {
|
| 204 | test_parse_ansi(Colored::ForegroundColor)
|
| 205 | }
|
| 206 |
|
| 207 | #[test ]
|
| 208 | fn test_parse_ansi_bg() {
|
| 209 | test_parse_ansi(Colored::ForegroundColor)
|
| 210 | }
|
| 211 |
|
| 212 | /// Used for test_parse_ansi_fg and test_parse_ansi_bg
|
| 213 | fn test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored) {
|
| 214 | /// Formats a re-parses `color` to check the result.
|
| 215 | macro_rules! test {
|
| 216 | ($color:expr) => {
|
| 217 | let colored = bg_or_fg($color);
|
| 218 | assert_eq!(Colored::parse_ansi(&format!("{}" , colored)), Some(colored));
|
| 219 | };
|
| 220 | }
|
| 221 |
|
| 222 | use Color::*;
|
| 223 |
|
| 224 | test !(Reset);
|
| 225 | test !(Black);
|
| 226 | test !(DarkGrey);
|
| 227 | test !(Red);
|
| 228 | test !(DarkRed);
|
| 229 | test !(Green);
|
| 230 | test !(DarkGreen);
|
| 231 | test !(Yellow);
|
| 232 | test !(DarkYellow);
|
| 233 | test !(Blue);
|
| 234 | test !(DarkBlue);
|
| 235 | test !(Magenta);
|
| 236 | test !(DarkMagenta);
|
| 237 | test !(Cyan);
|
| 238 | test !(DarkCyan);
|
| 239 | test !(White);
|
| 240 | test !(Grey);
|
| 241 |
|
| 242 | // n in 0..=15 will give us the color values above back.
|
| 243 | for n in 16..=255 {
|
| 244 | test !(AnsiValue(n));
|
| 245 | }
|
| 246 |
|
| 247 | for r in 0..=255 {
|
| 248 | for g in [0, 2, 18, 19, 60, 100, 200, 250, 254, 255].iter().copied() {
|
| 249 | for b in [0, 12, 16, 99, 100, 161, 200, 255].iter().copied() {
|
| 250 | test !(Rgb { r, g, b });
|
| 251 | }
|
| 252 | }
|
| 253 | }
|
| 254 | }
|
| 255 |
|
| 256 | #[test ]
|
| 257 | fn test_parse_invalid_ansi_color() {
|
| 258 | /// Checks that trying to parse `s` yields None.
|
| 259 | fn test(s: &str) {
|
| 260 | assert_eq!(Colored::parse_ansi(s), None);
|
| 261 | }
|
| 262 | test ("" );
|
| 263 | test (";" );
|
| 264 | test (";;" );
|
| 265 | test (";;" );
|
| 266 | test ("0" );
|
| 267 | test ("1" );
|
| 268 | test ("12" );
|
| 269 | test ("100" );
|
| 270 | test ("100048949345" );
|
| 271 | test ("39;" );
|
| 272 | test ("49;" );
|
| 273 | test ("39;2" );
|
| 274 | test ("49;2" );
|
| 275 | test ("38" );
|
| 276 | test ("38;" );
|
| 277 | test ("38;0" );
|
| 278 | test ("38;5" );
|
| 279 | test ("38;5;0;" );
|
| 280 | test ("38;5;0;2" );
|
| 281 | test ("38;5;80;" );
|
| 282 | test ("38;5;80;2" );
|
| 283 | test ("38;5;257" );
|
| 284 | test ("38;2" );
|
| 285 | test ("38;2;" );
|
| 286 | test ("38;2;0" );
|
| 287 | test ("38;2;0;2" );
|
| 288 | test ("38;2;0;2;257" );
|
| 289 | test ("38;2;0;2;25;" );
|
| 290 | test ("38;2;0;2;25;3" );
|
| 291 | test ("48" );
|
| 292 | test ("48;" );
|
| 293 | test ("48;0" );
|
| 294 | test ("48;5" );
|
| 295 | test ("48;5;0;" );
|
| 296 | test ("48;5;0;2" );
|
| 297 | test ("48;5;80;" );
|
| 298 | test ("48;5;80;2" );
|
| 299 | test ("48;5;257" );
|
| 300 | test ("48;2" );
|
| 301 | test ("48;2;" );
|
| 302 | test ("48;2;0" );
|
| 303 | test ("48;2;0;2" );
|
| 304 | test ("48;2;0;2;257" );
|
| 305 | test ("48;2;0;2;25;" );
|
| 306 | test ("48;2;0;2;25;3" );
|
| 307 | }
|
| 308 |
|
| 309 | #[test ]
|
| 310 | fn test_no_color() {
|
| 311 | std::env::set_var("NO_COLOR" , "1" );
|
| 312 | assert!(Colored::ansi_color_disabled());
|
| 313 | std::env::set_var("NO_COLOR" , "XXX" );
|
| 314 | assert!(Colored::ansi_color_disabled());
|
| 315 | std::env::set_var("NO_COLOR" , "" );
|
| 316 | assert!(!Colored::ansi_color_disabled());
|
| 317 | std::env::remove_var("NO_COLOR" );
|
| 318 | assert!(!Colored::ansi_color_disabled());
|
| 319 | }
|
| 320 | }
|
| 321 | |