ansi_term/
difference.rs

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}