1#[cfg(feature = "auto")]
2use crate::ColorChoice;
3use crate::IsTerminal;
4use crate::Lockable;
5use crate::RawStream;
6use crate::StripStream;
7#[cfg(all(windows, feature = "wincon"))]
8use crate::WinconStream;
9
10/// [`std::io::Write`] that adapts ANSI escape codes to the underlying `Write`s capabilities
11#[derive(Debug)]
12pub struct AutoStream<S: RawStream> {
13 inner: StreamInner<S>,
14}
15
16#[derive(Debug)]
17enum StreamInner<S: RawStream> {
18 PassThrough(S),
19 Strip(StripStream<S>),
20 #[cfg(all(windows, feature = "wincon"))]
21 Wincon(WinconStream<S>),
22}
23
24impl<S> AutoStream<S>
25where
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")]
140fn 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")]
165impl<S> IsTerminal for AutoStream<S>
166where
167 S: RawStream,
168{
169 #[inline]
170 fn is_terminal(&self) -> bool {
171 self.is_terminal()
172 }
173}
174
175impl 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
193impl 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
211impl<S> std::io::Write for AutoStream<S>
212where
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
252impl 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
261impl 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