| use crate::stream::AsLockedWrite; |
| use crate::stream::RawStream; |
| #[cfg(feature = "auto")] |
| use crate::ColorChoice; |
| 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"))] |
| { |
| Ok(Self { |
| inner: StreamInner::Wincon(WinconStream::new(raw)), |
| }) |
| } |
| #[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(), |
| } |
| } |
| |
| #[inline] |
| 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 |
| } |
| } |
| |
| /// Prefer [`AutoStream::choice`] |
| /// |
| /// This doesn't report what is requested but what is currently active. |
| #[inline] |
| pub fn current_choice(&self) -> ColorChoice { |
| match &self.inner { |
| StreamInner::PassThrough(_) => ColorChoice::AlwaysAnsi, |
| StreamInner::Strip(_) => ColorChoice::Never, |
| #[cfg(all(windows, feature = "wincon"))] |
| StreamInner::Wincon(_) => ColorChoice::Always, |
| } |
| } |
| } |
| |
| #[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, |
| } |
| } |
| |
| 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) -> AutoStream<std::io::StdoutLock<'static>> { |
| 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) -> AutoStream<std::io::StderrLock<'static>> { |
| 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 + AsLockedWrite, |
| { |
| // Must forward all calls to ensure locking happens appropriately |
| #[inline] |
| fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { |
| match &mut self.inner { |
| StreamInner::PassThrough(w) => w.as_locked_write().write(buf), |
| StreamInner::Strip(w) => w.write(buf), |
| #[cfg(all(windows, feature = "wincon"))] |
| StreamInner::Wincon(w) => w.write(buf), |
| } |
| } |
| #[inline] |
| fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> { |
| match &mut self.inner { |
| StreamInner::PassThrough(w) => w.as_locked_write().write_vectored(bufs), |
| StreamInner::Strip(w) => w.write_vectored(bufs), |
| #[cfg(all(windows, feature = "wincon"))] |
| StreamInner::Wincon(w) => w.write_vectored(bufs), |
| } |
| } |
| // is_write_vectored: nightly only |
| #[inline] |
| fn flush(&mut self) -> std::io::Result<()> { |
| match &mut self.inner { |
| StreamInner::PassThrough(w) => w.as_locked_write().flush(), |
| StreamInner::Strip(w) => w.flush(), |
| #[cfg(all(windows, feature = "wincon"))] |
| StreamInner::Wincon(w) => w.flush(), |
| } |
| } |
| #[inline] |
| fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { |
| match &mut self.inner { |
| StreamInner::PassThrough(w) => w.as_locked_write().write_all(buf), |
| StreamInner::Strip(w) => w.write_all(buf), |
| #[cfg(all(windows, feature = "wincon"))] |
| StreamInner::Wincon(w) => w.write_all(buf), |
| } |
| } |
| // write_all_vectored: nightly only |
| #[inline] |
| fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::io::Result<()> { |
| match &mut self.inner { |
| StreamInner::PassThrough(w) => w.as_locked_write().write_fmt(args), |
| StreamInner::Strip(w) => w.write_fmt(args), |
| #[cfg(all(windows, feature = "wincon"))] |
| StreamInner::Wincon(w) => w.write_fmt(args), |
| } |
| } |
| } |