anstyle_stream/
auto.rs

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