1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
pub use colored::{Color, ColoredString, Colorize, Styles};
use super::Action;
/// Renders in the given style.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Style {
/// Foreground color.
pub fg: Option<Color>,
/// Background color.
pub bg: Option<Color>,
/// Styles to apply.
pub styles: Vec<Styles>,
}
impl Style {
/// Creates a style with red foreground color and bold font weight.
#[must_use]
pub fn red_bold() -> Self {
Self {
fg: Some(Color::Red),
bg: None,
styles: vec![Styles::Bold],
}
}
/// Creates a style with green foreground color and bold font weight.
#[must_use]
pub fn green_bold() -> Self {
Self {
fg: Some(Color::Green),
bg: None,
styles: vec![Styles::Bold],
}
}
}
impl Action for Style {
fn act(&self, input: &str) -> String {
const NEWLINE: char = '\n';
input
// Split on lines: only that way, terminal coloring has a chance to *reset*
// after each line and relaunch correctly on the next. Otherwise, escape
// codes etc. are dragged across lines/contexts and might not work.
//
// This sadly encodes knowledge `Style` isn't supposed to have (the fact
// that sometimes, we're operating line-based.)
.split_inclusive(NEWLINE)
.map(|s| {
// *Only style the part without newline*, if any. Including the newline
// gives bad results for... reasons. Put the suffix, if any, back later
// manually. We might not get a suffix at all if there's none at all in
// `input`.
let (s, suffix) = s
.strip_suffix(NEWLINE)
.map_or((s, None), |without_suffix| (without_suffix, Some(NEWLINE)));
// Debug-only: not mission-critical if this fires
debug_assert!(!s.ends_with(NEWLINE));
let mut s = ColoredString::from(s);
if let Some(c) = self.fg {
s = s.color(c);
}
if let Some(c) = self.bg {
s = s.on_color(c);
}
for style in &self.styles {
s = match style {
Styles::Clear => s.clear(),
Styles::Bold => s.bold(),
Styles::Dimmed => s.dimmed(),
Styles::Underline => s.underline(),
Styles::Reversed => s.reversed(),
Styles::Italic => s.italic(),
Styles::Blink => s.blink(),
Styles::Hidden => s.hidden(),
Styles::Strikethrough => s.strikethrough(),
}
}
let mut res = s.to_string();
if let Some(suffix) = suffix {
// Put it back... it'll not contain styling. This properly resets if
// styling across lines.
res.push(suffix);
}
res
})
.collect()
}
}