1use 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)]
7pub 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
23impl 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.
39 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)]
142mod test {
143 use super::*;
144 use super::Difference::*;
145 use style::Colour::*;
146 use style::Style;
147
148 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