blob: 969a8da99f6a7eef697b0c99a6268b55aca1bf5e [file] [log] [blame]
#[cfg(feature = "auto")]
use crate::ColorChoice;
use crate::IsTerminal;
use crate::Lockable;
use crate::RawStream;
use crate::StripStream;
#[cfg(all(windows, feature = "wincon"))]
use crate::WinconStream;
/// [`std::io::Write`] that adapts ANSI escape codes to the underlying `Write`s capabilities
#[derive(Debug)]
pub struct AutoStream<S: RawStream> {
inner: StreamInner<S>,
}
#[derive(Debug)]
enum StreamInner<S: RawStream> {
PassThrough(S),
Strip(StripStream<S>),
#[cfg(all(windows, feature = "wincon"))]
Wincon(WinconStream<S>),
}
impl<S> AutoStream<S>
where
S: RawStream,
{
/// Runtime control over styling behavior
#[cfg(feature = "auto")]
#[inline]
pub fn new(raw: S, choice: ColorChoice) -> Self {
match choice {
ColorChoice::Auto => Self::auto(raw),
ColorChoice::AlwaysAnsi => Self::always_ansi(raw),
ColorChoice::Always => Self::always(raw),
ColorChoice::Never => Self::never(raw),
}
}
/// Auto-adapt for the stream's capabilities
#[cfg(feature = "auto")]
#[inline]
pub fn auto(raw: S) -> Self {
let choice = Self::choice(&raw);
debug_assert_ne!(choice, ColorChoice::Auto);
Self::new(raw, choice)
}
/// Report the desired choice for the given stream
#[cfg(feature = "auto")]
pub fn choice(raw: &S) -> ColorChoice {
choice(raw)
}
/// Force ANSI escape codes to be passed through as-is, no matter what the inner `Write`
/// supports.
#[inline]
pub fn always_ansi(raw: S) -> Self {
#[cfg(feature = "auto")]
{
if raw.is_terminal() {
let _ = anstyle_query::windows::enable_ansi_colors();
}
}
Self::always_ansi_(raw)
}
#[inline]
fn always_ansi_(raw: S) -> Self {
let inner = StreamInner::PassThrough(raw);
AutoStream { inner }
}
/// Force color, no matter what the inner `Write` supports.
#[inline]
pub fn always(raw: S) -> Self {
if cfg!(windows) {
#[cfg(feature = "auto")]
let use_wincon = raw.is_terminal()
&& !anstyle_query::windows::enable_ansi_colors().unwrap_or(true)
&& !anstyle_query::term_supports_ansi_color();
#[cfg(not(feature = "auto"))]
let use_wincon = true;
if use_wincon {
Self::wincon(raw).unwrap_or_else(|raw| Self::always_ansi_(raw))
} else {
Self::always_ansi_(raw)
}
} else {
Self::always_ansi(raw)
}
}
/// Only pass printable data to the inner `Write`.
#[inline]
pub fn never(raw: S) -> Self {
let inner = StreamInner::Strip(StripStream::new(raw));
AutoStream { inner }
}
#[inline]
fn wincon(raw: S) -> Result<Self, S> {
#[cfg(all(windows, feature = "wincon"))]
{
let console = anstyle_wincon::Console::new(raw)?;
Ok(Self {
inner: StreamInner::Wincon(WinconStream::new(console)),
})
}
#[cfg(not(all(windows, feature = "wincon")))]
{
Err(raw)
}
}
/// Get the wrapped [`RawStream`]
#[inline]
pub fn into_inner(self) -> S {
match self.inner {
StreamInner::PassThrough(w) => w,
StreamInner::Strip(w) => w.into_inner(),
#[cfg(all(windows, feature = "wincon"))]
StreamInner::Wincon(w) => w.into_inner().into_inner(),
}
}
#[inline]
#[cfg(feature = "auto")]
pub fn is_terminal(&self) -> bool {
match &self.inner {
StreamInner::PassThrough(w) => w.is_terminal(),
StreamInner::Strip(w) => w.is_terminal(),
#[cfg(all(windows, feature = "wincon"))]
StreamInner::Wincon(_) => true, // its only ever a terminal
}
}
}
#[cfg(feature = "auto")]
fn choice(raw: &dyn RawStream) -> ColorChoice {
let choice = ColorChoice::global();
match choice {
ColorChoice::Auto => {
let clicolor = anstyle_query::clicolor();
let clicolor_enabled = clicolor.unwrap_or(false);
let clicolor_disabled = !clicolor.unwrap_or(true);
if raw.is_terminal()
&& !anstyle_query::no_color()
&& !clicolor_disabled
&& (anstyle_query::term_supports_color()
|| clicolor_enabled
|| anstyle_query::is_ci())
|| anstyle_query::clicolor_force()
{
ColorChoice::Always
} else {
ColorChoice::Never
}
}
ColorChoice::AlwaysAnsi | ColorChoice::Always | ColorChoice::Never => choice,
}
}
#[cfg(feature = "auto")]
impl<S> IsTerminal for AutoStream<S>
where
S: RawStream,
{
#[inline]
fn is_terminal(&self) -> bool {
self.is_terminal()
}
}
impl AutoStream<std::io::Stdout> {
/// Get exclusive access to the `AutoStream`
///
/// Why?
/// - Faster performance when writing in a loop
/// - Avoid other threads interleaving output with the current thread
#[inline]
pub fn lock(self) -> <Self as Lockable>::Locked {
let inner = match self.inner {
StreamInner::PassThrough(w) => StreamInner::PassThrough(w.lock()),
StreamInner::Strip(w) => StreamInner::Strip(w.lock()),
#[cfg(all(windows, feature = "wincon"))]
StreamInner::Wincon(w) => StreamInner::Wincon(w.lock()),
};
AutoStream { inner }
}
}
impl AutoStream<std::io::Stderr> {
/// Get exclusive access to the `AutoStream`
///
/// Why?
/// - Faster performance when writing in a loop
/// - Avoid other threads interleaving output with the current thread
#[inline]
pub fn lock(self) -> <Self as Lockable>::Locked {
let inner = match self.inner {
StreamInner::PassThrough(w) => StreamInner::PassThrough(w.lock()),
StreamInner::Strip(w) => StreamInner::Strip(w.lock()),
#[cfg(all(windows, feature = "wincon"))]
StreamInner::Wincon(w) => StreamInner::Wincon(w.lock()),
};
AutoStream { inner }
}
}
impl<S> std::io::Write for AutoStream<S>
where
S: RawStream,
{
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match &mut self.inner {
StreamInner::PassThrough(w) => w.write(buf),
StreamInner::Strip(w) => w.write(buf),
#[cfg(all(windows, feature = "wincon"))]
StreamInner::Wincon(w) => w.write(buf),
}
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
match &mut self.inner {
StreamInner::PassThrough(w) => w.flush(),
StreamInner::Strip(w) => w.flush(),
#[cfg(all(windows, feature = "wincon"))]
StreamInner::Wincon(w) => w.flush(),
}
}
// Provide explicit implementations of trait methods
// - To reduce bookkeeping
// - Avoid acquiring / releasing locks in a loop
#[inline]
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
match &mut self.inner {
StreamInner::PassThrough(w) => w.write_all(buf),
StreamInner::Strip(w) => w.write_all(buf),
#[cfg(all(windows, feature = "wincon"))]
StreamInner::Wincon(w) => w.write_all(buf),
}
}
// Not bothering with `write_fmt` as it just calls `write_all`
}
impl Lockable for AutoStream<std::io::Stdout> {
type Locked = AutoStream<<std::io::Stdout as Lockable>::Locked>;
#[inline]
fn lock(self) -> Self::Locked {
self.lock()
}
}
impl Lockable for AutoStream<std::io::Stderr> {
type Locked = AutoStream<<std::io::Stderr as Lockable>::Locked>;
#[inline]
fn lock(self) -> Self::Locked {
self.lock()
}
}