Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(wincon): Prepare for global stream state #125

Merged
merged 3 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 14 additions & 58 deletions crates/anstyle-wincon/src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,21 @@ where
S: crate::WinconStream + std::io::Write,
{
stream: Option<S>,
initial_fg: Option<anstyle::AnsiColor>,
initial_bg: Option<anstyle::AnsiColor>,
last_fg: Option<anstyle::AnsiColor>,
last_bg: Option<anstyle::AnsiColor>,
state: crate::stream::ConsoleState,
}

impl<S> Console<S>
where
S: crate::WinconStream + std::io::Write,
{
pub fn new(stream: S) -> Result<Self, S> {
// HACK: Assuming the error from `get_colors()` will be present on `write` and doing basic
// ops on the stream will cause the same result
let (initial_fg, initial_bg) = match stream.get_colors() {
Ok(ok) => ok,
Err(_) => {
return Err(stream);
}
};
Ok(Self {
stream: Some(stream),
initial_fg,
initial_bg,
last_fg: initial_fg,
last_bg: initial_bg,
})
match crate::stream::ConsoleState::new(&stream) {
Ok(state) => Ok(Self {
stream: Some(stream),
state,
}),
Err(_err) => Err(stream),
}
}

/// Write colored text to the screen
Expand All @@ -40,18 +29,17 @@ where
bg: Option<anstyle::AnsiColor>,
data: &[u8],
) -> std::io::Result<usize> {
self.apply(fg, bg)?;
let written = self.as_stream_mut().write(data)?;
Ok(written)
self.state
.write(self.stream.as_mut().unwrap(), fg, bg, data)
}

pub fn flush(&mut self) -> std::io::Result<()> {
self.as_stream_mut().flush()
self.stream.as_mut().unwrap().flush()
}

/// Change the terminal back to the initial colors
pub fn reset(&mut self) -> std::io::Result<()> {
self.apply(self.initial_fg, self.initial_bg)
self.state.reset(self.stream.as_mut().unwrap())
}

/// Close the stream, reporting any errors
Expand All @@ -65,43 +53,14 @@ where
let _ = self.reset();
self.stream.take().unwrap()
}

fn apply(
&mut self,
fg: Option<anstyle::AnsiColor>,
bg: Option<anstyle::AnsiColor>,
) -> std::io::Result<()> {
let fg = fg.or(self.initial_fg);
let bg = bg.or(self.initial_bg);
if fg == self.last_fg && bg == self.last_bg {
return Ok(());
}

// Ensure everything is written with the last set of colors before applying the next set
self.as_stream_mut().flush()?;

self.as_stream_mut().set_colors(fg, bg)?;
self.last_fg = fg;
self.last_bg = bg;

Ok(())
}

fn as_stream(&self) -> &S {
self.stream.as_ref().unwrap()
}

fn as_stream_mut(&mut self) -> &mut S {
self.stream.as_mut().unwrap()
}
}

impl<S> Console<S>
where
S: crate::WinconStream + std::io::Write + std::io::IsTerminal,
{
pub fn is_terminal(&self) -> bool {
std::io::IsTerminal::is_terminal(self.as_stream())
std::io::IsTerminal::is_terminal(self.stream.as_ref().unwrap())
}
}

Expand Down Expand Up @@ -132,10 +91,7 @@ where
pub fn lock(mut self) -> <Self as crate::Lockable>::Locked {
Console {
stream: Some(self.stream.take().unwrap().lock()),
initial_fg: self.initial_fg,
initial_bg: self.initial_bg,
last_fg: self.last_fg,
last_bg: self.last_bg,
state: self.state.clone(),
}
}
}
Expand Down
139 changes: 134 additions & 5 deletions crates/anstyle-wincon/src/stream.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#[cfg(not(windows))]
use ansi as inner;
#[cfg(windows)]
use wincon as inner;

/// Extend `std::io::Write` with wincon styling
///
/// Generally, you will want to use [`Console`][crate::Console] instead
Expand Down Expand Up @@ -98,6 +103,134 @@ impl WinconStream for std::fs::File {
}
}

/// Write colored text to the screen
#[derive(Clone, Debug)]
pub(crate) enum ConsoleState {
Wincon(WinconAdapter),
Pass(PassThroughAdapter),
}

impl ConsoleState {
pub(crate) fn new<S: crate::WinconStream + std::io::Write>(
stream: &S,
) -> std::io::Result<Self> {
let adapter = match stream.get_colors() {
Ok((Some(initial_fg), Some(initial_bg))) => {
Self::Wincon(WinconAdapter::new(initial_fg, initial_bg))
}
// Can only happen on non-wincon systems
Ok(_) => Self::Pass(PassThroughAdapter::new()),
Err(err) => {
return Err(err);
}
};
Ok(adapter)
}

pub(crate) fn write<S: crate::WinconStream + std::io::Write>(
&mut self,
stream: &mut S,
fg: Option<anstyle::AnsiColor>,
bg: Option<anstyle::AnsiColor>,
data: &[u8],
) -> std::io::Result<usize> {
self.apply(stream, fg, bg)?;
let written = stream.write(data)?;
Ok(written)
}

pub(crate) fn reset<S: crate::WinconStream + std::io::Write>(
&mut self,
stream: &mut S,
) -> std::io::Result<()> {
self.apply(stream, None, None)
}

fn apply<S: crate::WinconStream + std::io::Write>(
&mut self,
stream: &mut S,
fg: Option<anstyle::AnsiColor>,
bg: Option<anstyle::AnsiColor>,
) -> std::io::Result<()> {
match self {
Self::Wincon(adapter) => adapter.apply(stream, fg, bg),
Self::Pass(adapter) => adapter.apply(stream, fg, bg),
}
}
}

#[derive(Default, Clone, Debug)]
pub(crate) struct PassThroughAdapter {
last_fg: Option<anstyle::AnsiColor>,
last_bg: Option<anstyle::AnsiColor>,
}

impl PassThroughAdapter {
fn new() -> Self {
Default::default()
}

fn apply<S: crate::WinconStream + std::io::Write>(
&mut self,
stream: &mut S,
fg: Option<anstyle::AnsiColor>,
bg: Option<anstyle::AnsiColor>,
) -> std::io::Result<()> {
// Avoid writing out no-op resets
if fg == self.last_fg && bg == self.last_bg {
return Ok(());
}

stream.set_colors(fg, bg)?;

self.last_fg = fg;
self.last_bg = bg;

Ok(())
}
}

#[derive(Clone, Debug)]
pub(crate) struct WinconAdapter {
initial_fg: anstyle::AnsiColor,
initial_bg: anstyle::AnsiColor,
last_fg: anstyle::AnsiColor,
last_bg: anstyle::AnsiColor,
}

impl WinconAdapter {
fn new(initial_fg: anstyle::AnsiColor, initial_bg: anstyle::AnsiColor) -> Self {
Self {
initial_fg,
initial_bg,
last_fg: initial_fg,
last_bg: initial_bg,
}
}

fn apply<S: crate::WinconStream + std::io::Write>(
&mut self,
stream: &mut S,
fg: Option<anstyle::AnsiColor>,
bg: Option<anstyle::AnsiColor>,
) -> std::io::Result<()> {
let fg = fg.unwrap_or(self.initial_fg);
let bg = bg.unwrap_or(self.initial_bg);
if fg == self.last_fg && bg == self.last_bg {
return Ok(());
}

// Ensure everything is written with the last set of colors before applying the next set
stream.flush()?;

stream.set_colors(Some(fg), Some(bg))?;
self.last_fg = fg;
self.last_bg = bg;

Ok(())
}
}

#[cfg(windows)]
mod wincon {
use std::os::windows::io::AsHandle;
Expand Down Expand Up @@ -142,11 +275,7 @@ mod ansi {
pub(super) fn get_colors<S>(
_stream: &S,
) -> std::io::Result<(Option<anstyle::AnsiColor>, Option<anstyle::AnsiColor>)> {
// No idea what state the stream was left in, so just assume default
Ok((None, None))
}
}

#[cfg(not(windows))]
use ansi as inner;
#[cfg(windows)]
use wincon as inner;
Loading