]>
Commit | Line | Data |
---|---|---|
353b0b11 FG |
1 | /// Write colored text to the screen |
2 | #[derive(Debug)] | |
3 | pub struct Console<S> | |
4 | where | |
5 | S: crate::WinconStream + std::io::Write, | |
6 | { | |
7 | stream: Option<S>, | |
8 | initial_fg: Option<anstyle::AnsiColor>, | |
9 | initial_bg: Option<anstyle::AnsiColor>, | |
10 | last_fg: Option<anstyle::AnsiColor>, | |
11 | last_bg: Option<anstyle::AnsiColor>, | |
12 | } | |
13 | ||
14 | impl<S> Console<S> | |
15 | where | |
16 | S: crate::WinconStream + std::io::Write, | |
17 | { | |
18 | pub fn new(stream: S) -> Result<Self, S> { | |
19 | // HACK: Assuming the error from `get_colors()` will be present on `write` and doing basic | |
20 | // ops on the stream will cause the same result | |
21 | let (initial_fg, initial_bg) = match stream.get_colors() { | |
22 | Ok(ok) => ok, | |
23 | Err(_) => { | |
24 | return Err(stream); | |
25 | } | |
26 | }; | |
27 | Ok(Self { | |
28 | stream: Some(stream), | |
29 | initial_fg, | |
30 | initial_bg, | |
31 | last_fg: initial_fg, | |
32 | last_bg: initial_bg, | |
33 | }) | |
34 | } | |
35 | ||
36 | /// Write colored text to the screen | |
37 | pub fn write( | |
38 | &mut self, | |
39 | fg: Option<anstyle::AnsiColor>, | |
40 | bg: Option<anstyle::AnsiColor>, | |
41 | data: &[u8], | |
42 | ) -> std::io::Result<usize> { | |
43 | self.apply(fg, bg)?; | |
44 | let written = self.as_stream_mut().write(data)?; | |
45 | Ok(written) | |
46 | } | |
47 | ||
48 | pub fn flush(&mut self) -> std::io::Result<()> { | |
49 | self.as_stream_mut().flush() | |
50 | } | |
51 | ||
52 | /// Change the terminal back to the initial colors | |
53 | pub fn reset(&mut self) -> std::io::Result<()> { | |
54 | self.apply(self.initial_fg, self.initial_bg) | |
55 | } | |
56 | ||
57 | /// Close the stream, reporting any errors | |
58 | pub fn close(mut self) -> std::io::Result<()> { | |
59 | self.reset() | |
60 | } | |
61 | ||
62 | /// Allow changing the stream | |
63 | pub fn map<S1: crate::WinconStream + std::io::Write>( | |
64 | mut self, | |
65 | op: impl FnOnce(S) -> S1, | |
66 | ) -> Console<S1> { | |
67 | Console { | |
68 | stream: Some(op(self.stream.take().unwrap())), | |
69 | initial_fg: self.initial_fg, | |
70 | initial_bg: self.initial_bg, | |
71 | last_fg: self.last_fg, | |
72 | last_bg: self.last_bg, | |
73 | } | |
74 | } | |
75 | ||
76 | /// Get the inner writer | |
77 | #[inline] | |
78 | pub fn into_inner(mut self) -> S { | |
79 | let _ = self.reset(); | |
80 | self.stream.take().unwrap() | |
81 | } | |
82 | ||
83 | fn apply( | |
84 | &mut self, | |
85 | fg: Option<anstyle::AnsiColor>, | |
86 | bg: Option<anstyle::AnsiColor>, | |
87 | ) -> std::io::Result<()> { | |
88 | let fg = fg.or(self.initial_fg); | |
89 | let bg = bg.or(self.initial_bg); | |
90 | if fg == self.last_fg && bg == self.last_bg { | |
91 | return Ok(()); | |
92 | } | |
93 | ||
94 | // Ensure everything is written with the last set of colors before applying the next set | |
95 | self.as_stream_mut().flush()?; | |
96 | ||
97 | self.as_stream_mut().set_colors(fg, bg)?; | |
98 | self.last_fg = fg; | |
99 | self.last_bg = bg; | |
100 | ||
101 | Ok(()) | |
102 | } | |
103 | ||
104 | fn as_stream_mut(&mut self) -> &mut S { | |
105 | self.stream.as_mut().unwrap() | |
106 | } | |
107 | } | |
108 | ||
109 | impl<S> Console<S> | |
110 | where | |
111 | S: crate::WinconStream + std::io::Write, | |
112 | S: crate::Lockable, | |
113 | <S as crate::Lockable>::Locked: crate::WinconStream + std::io::Write, | |
114 | { | |
115 | /// Get exclusive access to the `Console` | |
116 | /// | |
117 | /// Why? | |
118 | /// - Faster performance when writing in a loop | |
119 | /// - Avoid other threads interleaving output with the current thread | |
120 | #[inline] | |
121 | pub fn lock(mut self) -> <Self as crate::Lockable>::Locked { | |
122 | Console { | |
123 | stream: Some(self.stream.take().unwrap().lock()), | |
124 | initial_fg: self.initial_fg, | |
125 | initial_bg: self.initial_bg, | |
126 | last_fg: self.last_fg, | |
127 | last_bg: self.last_bg, | |
128 | } | |
129 | } | |
130 | } | |
131 | ||
132 | impl<S> crate::Lockable for Console<S> | |
133 | where | |
134 | S: crate::WinconStream + std::io::Write, | |
135 | S: crate::Lockable, | |
136 | <S as crate::Lockable>::Locked: crate::WinconStream + std::io::Write, | |
137 | { | |
138 | type Locked = Console<<S as crate::Lockable>::Locked>; | |
139 | ||
140 | #[inline] | |
141 | fn lock(self) -> Self::Locked { | |
142 | self.lock() | |
143 | } | |
144 | } | |
145 | ||
146 | impl<S> Drop for Console<S> | |
147 | where | |
148 | S: crate::WinconStream + std::io::Write, | |
149 | { | |
150 | fn drop(&mut self) { | |
151 | // Otherwise `Console::lock` took it | |
152 | if self.stream.is_some() { | |
153 | let _ = self.reset(); | |
154 | } | |
155 | } | |
156 | } |