1 use super::Style; 2 3 4 /// When printing out one coloured string followed by another, use one of 5 /// these rules to figure out which *extra* control codes need to be sent. 6 #[derive(PartialEq, Clone, Copy, Debug)] 7 pub enum Difference { 8 9 /// Print out the control codes specified by this style to end up looking 10 /// like the second string's styles. 11 ExtraStyles(Style), 12 13 /// Converting between these two is impossible, so just send a reset 14 /// command and then the second string's styles. 15 Reset, 16 17 /// The before style is exactly the same as the after style, so no further 18 /// control codes need to be printed. 19 NoDifference, 20 } 21 22 23 impl Difference { 24 25 /// Compute the 'style difference' required to turn an existing style into 26 /// the given, second style. 27 /// 28 /// For example, to turn green text into green bold text, it's redundant 29 /// to write a reset command then a second green+bold command, instead of 30 /// just writing one bold command. This method should see that both styles 31 /// use the foreground colour green, and reduce it to a single command. 32 /// 33 /// This method returns an enum value because it's not actually always 34 /// possible to turn one style into another: for example, text could be 35 /// made bold and underlined, but you can't remove the bold property 36 /// without also removing the underline property. So when this has to 37 /// happen, this function returns None, meaning that the entire set of 38 /// styles should be reset and begun again. between(first: &Style, next: &Style) -> Difference39 pub fn between(first: &Style, next: &Style) -> Difference { 40 use self::Difference::*; 41 42 // XXX(Havvy): This algorithm is kind of hard to replicate without 43 // having the Plain/Foreground enum variants, so I'm just leaving 44 // it commented out for now, and defaulting to Reset. 45 46 if first == next { 47 return NoDifference; 48 } 49 50 // Cannot un-bold, so must Reset. 51 if first.is_bold && !next.is_bold { 52 return Reset; 53 } 54 55 if first.is_dimmed && !next.is_dimmed { 56 return Reset; 57 } 58 59 if first.is_italic && !next.is_italic { 60 return Reset; 61 } 62 63 // Cannot un-underline, so must Reset. 64 if first.is_underline && !next.is_underline { 65 return Reset; 66 } 67 68 if first.is_blink && !next.is_blink { 69 return Reset; 70 } 71 72 if first.is_reverse && !next.is_reverse { 73 return Reset; 74 } 75 76 if first.is_hidden && !next.is_hidden { 77 return Reset; 78 } 79 80 if first.is_strikethrough && !next.is_strikethrough { 81 return Reset; 82 } 83 84 // Cannot go from foreground to no foreground, so must Reset. 85 if first.foreground.is_some() && next.foreground.is_none() { 86 return Reset; 87 } 88 89 // Cannot go from background to no background, so must Reset. 90 if first.background.is_some() && next.background.is_none() { 91 return Reset; 92 } 93 94 let mut extra_styles = Style::default(); 95 96 if first.is_bold != next.is_bold { 97 extra_styles.is_bold = true; 98 } 99 100 if first.is_dimmed != next.is_dimmed { 101 extra_styles.is_dimmed = true; 102 } 103 104 if first.is_italic != next.is_italic { 105 extra_styles.is_italic = true; 106 } 107 108 if first.is_underline != next.is_underline { 109 extra_styles.is_underline = true; 110 } 111 112 if first.is_blink != next.is_blink { 113 extra_styles.is_blink = true; 114 } 115 116 if first.is_reverse != next.is_reverse { 117 extra_styles.is_reverse = true; 118 } 119 120 if first.is_hidden != next.is_hidden { 121 extra_styles.is_hidden = true; 122 } 123 124 if first.is_strikethrough != next.is_strikethrough { 125 extra_styles.is_strikethrough = true; 126 } 127 128 if first.foreground != next.foreground { 129 extra_styles.foreground = next.foreground; 130 } 131 132 if first.background != next.background { 133 extra_styles.background = next.background; 134 } 135 136 ExtraStyles(extra_styles) 137 } 138 } 139 140 141 #[cfg(test)] 142 mod test { 143 use super::*; 144 use super::Difference::*; 145 use style::Colour::*; 146 use style::Style; 147 style() -> Style148 fn style() -> Style { 149 Style::new() 150 } 151 152 macro_rules! test { 153 ($name: ident: $first: expr; $next: expr => $result: expr) => { 154 #[test] 155 fn $name() { 156 assert_eq!($result, Difference::between(&$first, &$next)); 157 } 158 }; 159 } 160 161 test!(nothing: Green.normal(); Green.normal() => NoDifference); 162 test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold())); 163 test!(lowercase: Green.bold(); Green.normal() => Reset); 164 test!(nothing2: Green.bold(); Green.bold() => NoDifference); 165 166 test!(colour_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal())); 167 168 test!(addition_of_blink: style(); style().blink() => ExtraStyles(style().blink())); 169 test!(addition_of_dimmed: style(); style().dimmed() => ExtraStyles(style().dimmed())); 170 test!(addition_of_hidden: style(); style().hidden() => ExtraStyles(style().hidden())); 171 test!(addition_of_reverse: style(); style().reverse() => ExtraStyles(style().reverse())); 172 test!(addition_of_strikethrough: style(); style().strikethrough() => ExtraStyles(style().strikethrough())); 173 174 test!(removal_of_strikethrough: style().strikethrough(); style() => Reset); 175 test!(removal_of_reverse: style().reverse(); style() => Reset); 176 test!(removal_of_hidden: style().hidden(); style() => Reset); 177 test!(removal_of_dimmed: style().dimmed(); style() => Reset); 178 test!(removal_of_blink: style().blink(); style() => Reset); 179 } 180