From 26a30a0e55fbd21d1d4b61ff0467fc503c0ca35b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 31 Aug 2023 13:34:11 -0500 Subject: [PATCH 1/3] style(wincon): Move `use` to top --- crates/anstyle-wincon/src/stream.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/anstyle-wincon/src/stream.rs b/crates/anstyle-wincon/src/stream.rs index dd16c7ef..79d58203 100644 --- a/crates/anstyle-wincon/src/stream.rs +++ b/crates/anstyle-wincon/src/stream.rs @@ -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 @@ -145,8 +150,3 @@ mod ansi { Ok((None, None)) } } - -#[cfg(not(windows))] -use ansi as inner; -#[cfg(windows)] -use wincon as inner; From ccc6ee799e7301a78bbc8473089de9bc05177215 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 9 Aug 2023 12:00:45 -0500 Subject: [PATCH 2/3] refactor(wincon): Decouple state from stream --- crates/anstyle-wincon/src/console.rs | 72 ++++++---------------------- crates/anstyle-wincon/src/stream.rs | 69 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 58 deletions(-) diff --git a/crates/anstyle-wincon/src/console.rs b/crates/anstyle-wincon/src/console.rs index f89de843..a8bfb0e7 100644 --- a/crates/anstyle-wincon/src/console.rs +++ b/crates/anstyle-wincon/src/console.rs @@ -5,10 +5,7 @@ where S: crate::WinconStream + std::io::Write, { stream: Option, - initial_fg: Option, - initial_bg: Option, - last_fg: Option, - last_bg: Option, + state: crate::stream::ConsoleState, } impl Console @@ -16,21 +13,13 @@ where S: crate::WinconStream + std::io::Write, { pub fn new(stream: S) -> Result { - // 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 @@ -40,18 +29,17 @@ where bg: Option, data: &[u8], ) -> std::io::Result { - 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 @@ -65,35 +53,6 @@ where let _ = self.reset(); self.stream.take().unwrap() } - - fn apply( - &mut self, - fg: Option, - bg: Option, - ) -> 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 Console @@ -101,7 +60,7 @@ 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()) } } @@ -132,10 +91,7 @@ where pub fn lock(mut self) -> ::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(), } } } diff --git a/crates/anstyle-wincon/src/stream.rs b/crates/anstyle-wincon/src/stream.rs index 79d58203..14b7cf2d 100644 --- a/crates/anstyle-wincon/src/stream.rs +++ b/crates/anstyle-wincon/src/stream.rs @@ -103,6 +103,75 @@ impl WinconStream for std::fs::File { } } +/// Write colored text to the screen +#[derive(Clone, Debug)] +pub(crate) struct ConsoleState { + initial_fg: Option, + initial_bg: Option, + last_fg: Option, + last_bg: Option, +} + +impl ConsoleState { + pub(crate) fn new( + stream: &S, + ) -> std::io::Result { + let (initial_fg, initial_bg) = match stream.get_colors() { + Ok(ok) => ok, + Err(err) => { + return Err(err); + } + }; + Ok(Self { + initial_fg, + initial_bg, + last_fg: initial_fg, + last_bg: initial_bg, + }) + } + + pub(crate) fn write( + &mut self, + stream: &mut S, + fg: Option, + bg: Option, + data: &[u8], + ) -> std::io::Result { + self.apply(stream, fg, bg)?; + let written = stream.write(data)?; + Ok(written) + } + + pub(crate) fn reset( + &mut self, + stream: &mut S, + ) -> std::io::Result<()> { + self.apply(stream, self.initial_fg, self.initial_bg) + } + + fn apply( + &mut self, + stream: &mut S, + fg: Option, + bg: Option, + ) -> 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 + stream.flush()?; + + stream.set_colors(fg, bg)?; + self.last_fg = fg; + self.last_bg = bg; + + Ok(()) + } +} + #[cfg(windows)] mod wincon { use std::os::windows::io::AsHandle; From 89d9532ca556d33dbc601fc9008f90714bea8c76 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 31 Aug 2023 12:19:12 -0500 Subject: [PATCH 3/3] refactor(wincon): Pull out adapters --- crates/anstyle-wincon/src/stream.rs | 94 +++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 17 deletions(-) diff --git a/crates/anstyle-wincon/src/stream.rs b/crates/anstyle-wincon/src/stream.rs index 14b7cf2d..671e73bb 100644 --- a/crates/anstyle-wincon/src/stream.rs +++ b/crates/anstyle-wincon/src/stream.rs @@ -105,29 +105,26 @@ impl WinconStream for std::fs::File { /// Write colored text to the screen #[derive(Clone, Debug)] -pub(crate) struct ConsoleState { - initial_fg: Option, - initial_bg: Option, - last_fg: Option, - last_bg: Option, +pub(crate) enum ConsoleState { + Wincon(WinconAdapter), + Pass(PassThroughAdapter), } impl ConsoleState { pub(crate) fn new( stream: &S, ) -> std::io::Result { - let (initial_fg, initial_bg) = match stream.get_colors() { - Ok(ok) => ok, + 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(Self { - initial_fg, - initial_bg, - last_fg: initial_fg, - last_bg: initial_bg, - }) + Ok(adapter) } pub(crate) fn write( @@ -146,7 +143,7 @@ impl ConsoleState { &mut self, stream: &mut S, ) -> std::io::Result<()> { - self.apply(stream, self.initial_fg, self.initial_bg) + self.apply(stream, None, None) } fn apply( @@ -155,8 +152,70 @@ impl ConsoleState { fg: Option, bg: Option, ) -> std::io::Result<()> { - let fg = fg.or(self.initial_fg); - let bg = bg.or(self.initial_bg); + 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, + last_bg: Option, +} + +impl PassThroughAdapter { + fn new() -> Self { + Default::default() + } + + fn apply( + &mut self, + stream: &mut S, + fg: Option, + bg: Option, + ) -> 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( + &mut self, + stream: &mut S, + fg: Option, + bg: Option, + ) -> 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(()); } @@ -164,7 +223,7 @@ impl ConsoleState { // Ensure everything is written with the last set of colors before applying the next set stream.flush()?; - stream.set_colors(fg, bg)?; + stream.set_colors(Some(fg), Some(bg))?; self.last_fg = fg; self.last_bg = bg; @@ -216,6 +275,7 @@ mod ansi { pub(super) fn get_colors( _stream: &S, ) -> std::io::Result<(Option, Option)> { + // No idea what state the stream was left in, so just assume default Ok((None, None)) } }