1 | #[cfg (feature = "auto" )] |
2 | use crate::ColorChoice; |
3 | use crate::IsTerminal; |
4 | use crate::Lockable; |
5 | use crate::RawStream; |
6 | use crate::StripStream; |
7 | #[cfg (all(windows, feature = "wincon" ))] |
8 | use crate::WinconStream; |
9 | |
10 | /// [`std::io::Write`] that adapts ANSI escape codes to the underlying `Write`s capabilities |
11 | #[derive (Debug)] |
12 | pub struct AutoStream<S: RawStream> { |
13 | inner: StreamInner<S>, |
14 | } |
15 | |
16 | #[derive (Debug)] |
17 | enum StreamInner<S: RawStream> { |
18 | PassThrough(S), |
19 | Strip(StripStream<S>), |
20 | #[cfg (all(windows, feature = "wincon" ))] |
21 | Wincon(WinconStream<S>), |
22 | } |
23 | |
24 | impl<S> AutoStream<S> |
25 | where |
26 | S: RawStream, |
27 | { |
28 | /// Runtime control over styling behavior |
29 | #[cfg (feature = "auto" )] |
30 | #[inline ] |
31 | pub fn new(raw: S, choice: ColorChoice) -> Self { |
32 | match choice { |
33 | ColorChoice::Auto => Self::auto(raw), |
34 | ColorChoice::AlwaysAnsi => Self::always_ansi(raw), |
35 | ColorChoice::Always => Self::always(raw), |
36 | ColorChoice::Never => Self::never(raw), |
37 | } |
38 | } |
39 | |
40 | /// Auto-adapt for the stream's capabilities |
41 | #[cfg (feature = "auto" )] |
42 | #[inline ] |
43 | pub fn auto(raw: S) -> Self { |
44 | let choice = Self::choice(&raw); |
45 | debug_assert_ne!(choice, ColorChoice::Auto); |
46 | Self::new(raw, choice) |
47 | } |
48 | |
49 | /// Report the desired choice for the given stream |
50 | #[cfg (feature = "auto" )] |
51 | pub fn choice(raw: &S) -> ColorChoice { |
52 | choice(raw) |
53 | } |
54 | |
55 | /// Force ANSI escape codes to be passed through as-is, no matter what the inner `Write` |
56 | /// supports. |
57 | #[inline ] |
58 | pub fn always_ansi(raw: S) -> Self { |
59 | #[cfg (feature = "auto" )] |
60 | { |
61 | if raw.is_terminal() { |
62 | let _ = anstyle_query::windows::enable_ansi_colors(); |
63 | } |
64 | } |
65 | Self::always_ansi_(raw) |
66 | } |
67 | |
68 | #[inline ] |
69 | fn always_ansi_(raw: S) -> Self { |
70 | let inner = StreamInner::PassThrough(raw); |
71 | AutoStream { inner } |
72 | } |
73 | |
74 | /// Force color, no matter what the inner `Write` supports. |
75 | #[inline ] |
76 | pub fn always(raw: S) -> Self { |
77 | if cfg!(windows) { |
78 | #[cfg (feature = "auto" )] |
79 | let use_wincon = raw.is_terminal() |
80 | && !anstyle_query::windows::enable_ansi_colors().unwrap_or(true) |
81 | && !anstyle_query::term_supports_ansi_color(); |
82 | #[cfg (not(feature = "auto" ))] |
83 | let use_wincon = true; |
84 | if use_wincon { |
85 | Self::wincon(raw).unwrap_or_else(|raw| Self::always_ansi_(raw)) |
86 | } else { |
87 | Self::always_ansi_(raw) |
88 | } |
89 | } else { |
90 | Self::always_ansi(raw) |
91 | } |
92 | } |
93 | |
94 | /// Only pass printable data to the inner `Write`. |
95 | #[inline ] |
96 | pub fn never(raw: S) -> Self { |
97 | let inner = StreamInner::Strip(StripStream::new(raw)); |
98 | AutoStream { inner } |
99 | } |
100 | |
101 | #[inline ] |
102 | fn wincon(raw: S) -> Result<Self, S> { |
103 | #[cfg (all(windows, feature = "wincon" ))] |
104 | { |
105 | let console = anstyle_wincon::Console::new(raw)?; |
106 | Ok(Self { |
107 | inner: StreamInner::Wincon(WinconStream::new(console)), |
108 | }) |
109 | } |
110 | #[cfg (not(all(windows, feature = "wincon" )))] |
111 | { |
112 | Err(raw) |
113 | } |
114 | } |
115 | |
116 | /// Get the wrapped [`RawStream`] |
117 | #[inline ] |
118 | pub fn into_inner(self) -> S { |
119 | match self.inner { |
120 | StreamInner::PassThrough(w) => w, |
121 | StreamInner::Strip(w) => w.into_inner(), |
122 | #[cfg (all(windows, feature = "wincon" ))] |
123 | StreamInner::Wincon(w) => w.into_inner().into_inner(), |
124 | } |
125 | } |
126 | |
127 | #[inline ] |
128 | #[cfg (feature = "auto" )] |
129 | pub fn is_terminal(&self) -> bool { |
130 | match &self.inner { |
131 | StreamInner::PassThrough(w) => w.is_terminal(), |
132 | StreamInner::Strip(w) => w.is_terminal(), |
133 | #[cfg (all(windows, feature = "wincon" ))] |
134 | StreamInner::Wincon(_) => true, // its only ever a terminal |
135 | } |
136 | } |
137 | } |
138 | |
139 | #[cfg (feature = "auto" )] |
140 | fn choice(raw: &dyn RawStream) -> ColorChoice { |
141 | let choice: ColorChoice = ColorChoice::global(); |
142 | match choice { |
143 | ColorChoice::Auto => { |
144 | let clicolor: Option = anstyle_query::clicolor(); |
145 | let clicolor_enabled: bool = clicolor.unwrap_or(default:false); |
146 | let clicolor_disabled: bool = !clicolor.unwrap_or(default:true); |
147 | if raw.is_terminal() |
148 | && !anstyle_query::no_color() |
149 | && !clicolor_disabled |
150 | && (anstyle_query::term_supports_color() |
151 | || clicolor_enabled |
152 | || anstyle_query::is_ci()) |
153 | || anstyle_query::clicolor_force() |
154 | { |
155 | ColorChoice::Always |
156 | } else { |
157 | ColorChoice::Never |
158 | } |
159 | } |
160 | ColorChoice::AlwaysAnsi | ColorChoice::Always | ColorChoice::Never => choice, |
161 | } |
162 | } |
163 | |
164 | #[cfg (feature = "auto" )] |
165 | impl<S> IsTerminal for AutoStream<S> |
166 | where |
167 | S: RawStream, |
168 | { |
169 | #[inline ] |
170 | fn is_terminal(&self) -> bool { |
171 | self.is_terminal() |
172 | } |
173 | } |
174 | |
175 | impl AutoStream<std::io::Stdout> { |
176 | /// Get exclusive access to the `AutoStream` |
177 | /// |
178 | /// Why? |
179 | /// - Faster performance when writing in a loop |
180 | /// - Avoid other threads interleaving output with the current thread |
181 | #[inline ] |
182 | pub fn lock(self) -> <Self as Lockable>::Locked { |
183 | let inner: StreamInner> = match self.inner { |
184 | StreamInner::PassThrough(w: Stdout) => StreamInner::PassThrough(w.lock()), |
185 | StreamInner::Strip(w: StripStream) => StreamInner::Strip(w.lock()), |
186 | #[cfg (all(windows, feature = "wincon" ))] |
187 | StreamInner::Wincon(w) => StreamInner::Wincon(w.lock()), |
188 | }; |
189 | AutoStream { inner } |
190 | } |
191 | } |
192 | |
193 | impl AutoStream<std::io::Stderr> { |
194 | /// Get exclusive access to the `AutoStream` |
195 | /// |
196 | /// Why? |
197 | /// - Faster performance when writing in a loop |
198 | /// - Avoid other threads interleaving output with the current thread |
199 | #[inline ] |
200 | pub fn lock(self) -> <Self as Lockable>::Locked { |
201 | let inner: StreamInner> = match self.inner { |
202 | StreamInner::PassThrough(w: Stderr) => StreamInner::PassThrough(w.lock()), |
203 | StreamInner::Strip(w: StripStream) => StreamInner::Strip(w.lock()), |
204 | #[cfg (all(windows, feature = "wincon" ))] |
205 | StreamInner::Wincon(w) => StreamInner::Wincon(w.lock()), |
206 | }; |
207 | AutoStream { inner } |
208 | } |
209 | } |
210 | |
211 | impl<S> std::io::Write for AutoStream<S> |
212 | where |
213 | S: RawStream, |
214 | { |
215 | #[inline ] |
216 | fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { |
217 | match &mut self.inner { |
218 | StreamInner::PassThrough(w) => w.write(buf), |
219 | StreamInner::Strip(w) => w.write(buf), |
220 | #[cfg (all(windows, feature = "wincon" ))] |
221 | StreamInner::Wincon(w) => w.write(buf), |
222 | } |
223 | } |
224 | |
225 | #[inline ] |
226 | fn flush(&mut self) -> std::io::Result<()> { |
227 | match &mut self.inner { |
228 | StreamInner::PassThrough(w) => w.flush(), |
229 | StreamInner::Strip(w) => w.flush(), |
230 | #[cfg (all(windows, feature = "wincon" ))] |
231 | StreamInner::Wincon(w) => w.flush(), |
232 | } |
233 | } |
234 | |
235 | // Provide explicit implementations of trait methods |
236 | // - To reduce bookkeeping |
237 | // - Avoid acquiring / releasing locks in a loop |
238 | |
239 | #[inline ] |
240 | fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { |
241 | match &mut self.inner { |
242 | StreamInner::PassThrough(w) => w.write_all(buf), |
243 | StreamInner::Strip(w) => w.write_all(buf), |
244 | #[cfg (all(windows, feature = "wincon" ))] |
245 | StreamInner::Wincon(w) => w.write_all(buf), |
246 | } |
247 | } |
248 | |
249 | // Not bothering with `write_fmt` as it just calls `write_all` |
250 | } |
251 | |
252 | impl Lockable for AutoStream<std::io::Stdout> { |
253 | type Locked = AutoStream<<std::io::Stdout as Lockable>::Locked>; |
254 | |
255 | #[inline ] |
256 | fn lock(self) -> Self::Locked { |
257 | self.lock() |
258 | } |
259 | } |
260 | |
261 | impl Lockable for AutoStream<std::io::Stderr> { |
262 | type Locked = AutoStream<<std::io::Stderr as Lockable>::Locked>; |
263 | |
264 | #[inline ] |
265 | fn lock(self) -> Self::Locked { |
266 | self.lock() |
267 | } |
268 | } |
269 | |