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