diff --git a/src/theme/color.rs b/src/theme/color.rs index bf78d3f..85b7e0f 100644 --- a/src/theme/color.rs +++ b/src/theme/color.rs @@ -155,14 +155,14 @@ impl Color { /// * `"#123456"` becomes `Color::Rgb(0x12, 0x34, 0x56)` pub fn parse(value: &str) -> Option { Some(match value { - "black" => Color::Dark(BaseColor::Black), - "red" => Color::Dark(BaseColor::Red), - "green" => Color::Dark(BaseColor::Green), - "yellow" => Color::Dark(BaseColor::Yellow), - "blue" => Color::Dark(BaseColor::Blue), - "magenta" => Color::Dark(BaseColor::Magenta), - "cyan" => Color::Dark(BaseColor::Cyan), - "white" => Color::Dark(BaseColor::White), + "dark black" | "black" => Color::Dark(BaseColor::Black), + "dark red" | "red" => Color::Dark(BaseColor::Red), + "dark green" | "green" => Color::Dark(BaseColor::Green), + "dark yellow" | "yellow" => Color::Dark(BaseColor::Yellow), + "dark blue" | "blue" => Color::Dark(BaseColor::Blue), + "dark magenta" | "magenta" => Color::Dark(BaseColor::Magenta), + "dark cyan" | "cyan" => Color::Dark(BaseColor::Cyan), + "dark white" | "white" => Color::Dark(BaseColor::White), "light black" => Color::Light(BaseColor::Black), "light red" => Color::Light(BaseColor::Red), "light green" => Color::Light(BaseColor::Green), @@ -172,73 +172,136 @@ impl Color { "light cyan" => Color::Light(BaseColor::Cyan), "light white" => Color::Light(BaseColor::White), "default" => Color::TerminalDefault, - value => return Color::parse_special(value), + value => { + return parse_special(value).or_else(|| { + log::warn!("Could not parse color `{}`.", value); + None + }) + } }) } +} - fn parse_special(value: &str) -> Option { - if value.starts_with('#') { - let value = &value[1..]; - // Compute per-color length, and amplitude - let (l, multiplier) = match value.len() { - 6 => (2, 1), - 3 => (1, 17), - _ => panic!("Cannot parse color: {}", value), - }; - let r = load_hex(&value[0..l]) * multiplier; - let g = load_hex(&value[l..2 * l]) * multiplier; - let b = load_hex(&value[2 * l..3 * l]) * multiplier; +fn parse_special(value: &str) -> Option { + if value.starts_with('#') { + parse_hex(&value[1..]) + } else if value.starts_with("0x") { + parse_hex(&value[2..]) + } else if value.len() == 6 { + parse_hex(value) + } else if value.len() == 3 { + // RGB values between 0 and 5 maybe? + // Like 050 for green + let rgb: Vec<_> = + value.chars().map(|c| c as i16 - '0' as i16).collect(); - Some(Color::Rgb(r as u8, g as u8, b as u8)) - } else if value.len() == 3 { - // RGB values between 0 and 5 maybe? - // Like 050 for green - let rgb: Vec<_> = - value.chars().map(|c| c as i16 - '0' as i16).collect(); - - assert_eq!(rgb.len(), 3); - if rgb.iter().all(|&i| i >= 0 && i < 6) { - Some(Color::RgbLowRes( - rgb[0] as u8, - rgb[1] as u8, - rgb[2] as u8, - )) - } else { - None - } + assert_eq!(rgb.len(), 3); + if rgb.iter().all(|&i| i >= 0 && i < 6) { + Some(Color::RgbLowRes(rgb[0] as u8, rgb[1] as u8, rgb[2] as u8)) } else { None } + } else { + None } } +fn parse_hex(value: &str) -> Option { + // Compute per-color length, and amplitude + let (l, multiplier) = match value.len() { + 6 => (2, 1), + 3 => (1, 17), + _ => return None, + }; + let r = load_hex(&value[0..l]) * multiplier; + let g = load_hex(&value[l..2 * l]) * multiplier; + let b = load_hex(&value[2 * l..3 * l]) * multiplier; + + Some(Color::Rgb(r as u8, g as u8, b as u8)) +} + /// Loads a hexadecimal code fn load_hex(s: &str) -> u16 { - let mut sum = 0; - for c in s.chars() { - sum *= 16; - sum += match c { - n @ '0'..='9' => n as i16 - '0' as i16, - n @ 'a'..='f' => n as i16 - 'a' as i16 + 10, - n @ 'A'..='F' => n as i16 - 'A' as i16 + 10, - _ => 0, - }; - } - - sum as u16 + s.chars() + .rev() + .filter_map(|c| { + Some(match c { + '0'..='9' => c as u16 - '0' as u16, + 'a'..='f' => c as u16 - 'a' as u16 + 10, + 'A'..='F' => c as u16 - 'A' as u16 + 10, + other => { + log::warn!( + "Invalid character `{}` in hexadecimal value `{}`.", + other, + s + ); + return None; + } + }) + }) + .enumerate() + .map(|(i, c)| c * 16u16.pow(i as u32)) + .sum() } #[cfg(test)] mod tests { + use super::Color; #[test] fn test_256_colors() { // Make sure Color::from_256colors never panics - use super::Color; - // TODO: use inclusive range when it gets stable - for i in 0..256u16 { - Color::from_256colors(i as u8); + for i in 0..=255u8 { + Color::from_256colors(i); + } + } + + #[test] + fn test_parse() { + assert_eq!(Color::parse("#fff"), Some(Color::Rgb(255, 255, 255))); + + assert_eq!( + Color::parse("#abcdef"), + Some(Color::Rgb(0xab, 0xcd, 0xef)) + ); + + assert_eq!( + Color::parse("0xFEDCBA"), + Some(Color::Rgb(0xfe, 0xdc, 0xba)) + ); + } + + #[test] + fn test_low_res() { + // Make sure Color::low_res always works with valid ranges. + for r in 0..=5 { + for g in 0..=5 { + for b in 0..=5 { + assert!( + Color::low_res(r, g, b).is_some(), + "Could not create lowres color {}:{}:{}", + r, + g, + b, + ); + } + } + } + + for r in 6..=10 { + for g in 6..=10 { + for b in 6..=10 { + assert_eq!( + Color::low_res(r, g, b), + None, + "Created invalid lowres color {}:{}:{}", + r, + g, + b, + ); + } + } } } }