diff --git a/src/shell/colors.rs b/src/shell/colors.rs index cae15cb5b4cc0e21b86cd160a6a502b452c5a46d..60349789dd615103386d9953968276f8157af685 100644 --- a/src/shell/colors.rs +++ b/src/shell/colors.rs @@ -65,7 +65,8 @@ lazy_static! { /// Colors may be called by name, or by a hexadecimal-converted decimal value. enum Mode { Name(&'static str), - bits256(u16), + Range256(u8), + TrueColor(u8, u8, u8), } #[derive(Debug, PartialEq)] @@ -91,7 +92,7 @@ impl Colors { colors.foreground = Some(Mode::Name(*color)); } else if let Some(color) = BG_COLORS.get(&variable) { colors.background = Some(Mode::Name(*color)); - } else if !colors.set_256bit_color(variable) { + } else if !colors.parse_colors(variable) { eprintln!("ion: {} is not a valid color", variable) } } @@ -110,28 +111,55 @@ impl Colors { } } + /// If no matches were made, then this will attempt to parse the variable as either a - /// two-digit hexadecimal value, or a decimal value, which corresponds to a 256-bit color. - fn set_256bit_color(&mut self, variable: &str) -> bool { - if variable.len() > 3 && variable.starts_with("0x") { - if let Ok(value) = u16::from_str_radix(&variable[2..4], 16) { - if variable.ends_with("bg") && variable.len() == 6 { - self.background = Some(Mode::bits256(value)); - return true; - } else if variable.len() == 4 { - self.foreground = Some(Mode::bits256(value)); + /// 24-bit true color color, or one of 256 colors. It supports both hexadecimal and decimals. + fn parse_colors(&mut self, variable: &str) -> bool { + // First, determine which field we will write to. + let (field, variable) = if variable.ends_with("bg") { + (&mut self.background, &variable[..variable.len()-2]) + } else { + (&mut self.foreground, variable) + }; + + // Then, check if the value is a hexadecimal value + if variable.starts_with("0x") { + let variable = &variable[2..]; + + match variable.len() { + // 256 colors: 0xF | 0xFF + 1 | 2 => if let Ok(value) = u8::from_str_radix(variable, 16) { + *field = Some(Mode::Range256(value)); return true; - } + }, + // 24-bit Color 0xRGB + 3 => { + let mut chars = variable.chars(); + if let Some(red) = hex_char_to_u8_range(chars.next().unwrap()) { + if let Some(green) = hex_char_to_u8_range(chars.next().unwrap()) { + if let Some(blue) = hex_char_to_u8_range(chars.next().unwrap()) { + *field = Some(Mode::TrueColor(red, green, blue)); + return true; + } + } + } + }, + // 24-bit Color 0xRRGGBB + 6 => if let Ok(red) = u8::from_str_radix(&variable[0..2], 16) { + if let Ok(green) = u8::from_str_radix(&variable[2..4], 16) { + if let Ok(blue) = u8::from_str_radix(&variable[4..6], 16) { + *field = Some(Mode::TrueColor(red, green, blue)); + return true; + } + } + }, + _ => () } - } else if variable.ends_with("bg") && variable.len() > 2 { - let (number, _) = variable.split_at(variable.len()-2); - if let Ok(value) = number.parse::<u16>() { - self.background = Some(Mode::bits256(value)); - return true + } else { + if let Ok(value) = variable.parse::<u8>() { + *field = Some(Mode::Range256(value)); + return true; } - } else if let Ok(value) = variable.parse::<u16>() { - self.foreground = Some(Mode::bits256(value)); - return true } false @@ -145,13 +173,15 @@ impl Colors { let foreground = match self.foreground { Some(Mode::Name(string)) => Some(string.to_owned()), - Some(Mode::bits256(value)) => Some(format!("38;5;{}", value)), + Some(Mode::Range256(value)) => Some(format!("38;5;{}", value)), + Some(Mode::TrueColor(red, green, blue)) => Some(format!("38;2;{};{};{}", red, green, blue)), None => None }; let background = match self.background { Some(Mode::Name(string)) => Some(string.to_owned()), - Some(Mode::bits256(value)) => Some(format!("48;5;{}", value)), + Some(Mode::Range256(value)) => Some(format!("48;5;{}", value)), + Some(Mode::TrueColor(red, green, blue)) => Some(format!("48;2;{};{};{}", red, green, blue)), None => None }; @@ -172,10 +202,33 @@ impl Colors { } } +fn hex_char_to_u8_range(character: char) -> Option<u8> { + if character >= '0' && character <= '9' { + Some((character as u8 - b'0') * 16) + } else { + // Convert the character to uppercase, if it isn't already. + let mut character = character as u8 & !0x20; + if character >= b'A' { + character -= 54; + if character < 17 { + return Some(character * 15 + 15); + } + } + None + } +} + #[cfg(test)] mod test { use super::*; + #[test] + fn convert_hex_digit() { + assert_eq!(Some(255), hex_char_to_u8_range('F')); + assert_eq!(Some(255), hex_char_to_u8_range('f')); + assert_eq!(Some(0), hex_char_to_u8_range('0')); + } + #[test] fn set_multiple_color_attributes() { let expected = Colors { @@ -201,11 +254,11 @@ mod test { } #[test] - fn hexadecimal_256bit_colors() { + fn hexadecimal_256_colors() { let expected = Colors { attributes: None, - background: Some(Mode::bits256(77)), - foreground: Some(Mode::bits256(75)) + background: Some(Mode::Range256(77)), + foreground: Some(Mode::Range256(75)) }; let actual = Colors::collect("0x4b,0x4dbg"); assert_eq!(actual, expected); @@ -213,14 +266,37 @@ mod test { } #[test] - fn decimal_256bit_colors() { + fn decimal_256_colors() { let expected = Colors { attributes: None, - background: Some(Mode::bits256(78)), - foreground: Some(Mode::bits256(32)) + background: Some(Mode::Range256(78)), + foreground: Some(Mode::Range256(32)) }; let actual = Colors::collect("78bg,32"); assert_eq!(actual, expected); assert_eq!(Some("\x1b[38;5;32;48;5;78m".to_owned()), actual.into_string()) } + + #[test] + fn three_digit_hex_24bit_colors() { + let expected = Colors { + attributes: None, + background: Some(Mode::TrueColor(255, 255, 255)), + foreground: Some(Mode::TrueColor(0, 0, 0)) + }; + let actual = Colors::collect("0x000,0xFFFbg"); + assert_eq!(expected, actual); + assert_eq!(Some("\x1b[38;2;0;0;0;48;2;255;255;255m".to_owned()), actual.into_string()); + } + + #[test] + fn six_digit_hex_24bit_colors() { + let expected = Colors { + attributes: None, + background: Some(Mode::TrueColor(255, 0, 0)), + foreground: Some(Mode::TrueColor(0, 255, 0)), + }; + let actual = Colors::collect("0x00FF00,0xFF0000bg"); + assert_eq!(expected, actual); + } }