diff --git a/examples/event-poll-read.rs b/examples/event-poll-read.rs index 2d33a34a..345a301f 100644 --- a/examples/event-poll-read.rs +++ b/examples/event-poll-read.rs @@ -2,10 +2,7 @@ //! //! cargo run --example event-poll-read -use std::{ - io::{stdout, Write}, - time::Duration, -}; +use std::{io::stdout, time::Duration}; use crossterm::{ cursor::position, diff --git a/examples/event-read-char-line.rs b/examples/event-read-char-line.rs index d5fd4f1e..c6ef38bd 100644 --- a/examples/event-read-char-line.rs +++ b/examples/event-read-char-line.rs @@ -34,7 +34,7 @@ pub fn read_line() -> Result { } } - return Ok(line); + Ok(line) } fn main() { diff --git a/examples/event-read.rs b/examples/event-read.rs index f4de7784..bf505471 100644 --- a/examples/event-read.rs +++ b/examples/event-read.rs @@ -2,7 +2,7 @@ //! //! cargo run --example event-read -use std::io::{stdout, Write}; +use std::io::stdout; use crossterm::{ cursor::position, diff --git a/src/ansi.rs b/src/ansi.rs deleted file mode 100644 index 9dfa1d61..00000000 --- a/src/ansi.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// Wrapper type for write dynamic ansi string -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[doc(hidden)] -pub struct Ansi(pub T); diff --git a/src/command.rs b/src/command.rs index 6842f827..f24532b1 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,6 +1,5 @@ -use std::{fmt::Display, io::Write}; - -use crate::{execute, queue}; +use std::fmt; +use std::io::{self, Write}; use super::error::Result; @@ -11,14 +10,12 @@ use super::error::Result; /// In order to understand how to use and execute commands, /// it is recommended that you take a look at [Command Api](../#command-api) chapter. pub trait Command { - type AnsiType: Display; - - /// Returns an ANSI code representation of this command. + /// Write an ANSI representation of this commmand to the given writer. /// An ANSI code can manipulate the terminal by writing it to the terminal buffer. /// However, only Windows 10 and UNIX systems support this. /// /// This method does not need to be accessed manually, as it is used by the crossterm's [Command Api](../#command-api) - fn ansi_code(&self) -> Self::AnsiType; + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result; /// Execute this command. /// @@ -39,12 +36,9 @@ pub trait Command { } } -impl Command for &T { - type AnsiType = T::AnsiType; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - T::ansi_code(self) +impl Command for &T { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + (**self).write_ansi(f) } #[inline] @@ -60,23 +54,19 @@ impl Command for &T { } } -/// An interface for commands that can be queued for further execution. -pub trait QueueableCommand: Sized { +/// An interface for types that can queue commands for further execution. +pub trait QueueableCommand { /// Queues the given command for further execution. - fn queue(&mut self, command: impl Command) -> Result<&mut Self>; + fn queue(&mut self, command: impl Command) -> Result<&mut Self>; } -/// An interface for commands that are directly executed. -pub trait ExecutableCommand: Sized { +/// An interface for types that can directly execute commands. +pub trait ExecutableCommand { /// Executes the given command directly. - fn execute(&mut self, command: impl Command) -> Result<&mut Self>; + fn execute(&mut self, command: impl Command) -> Result<&mut Self>; } -impl QueueableCommand for T -where - A: Display, - T: Write, -{ +impl QueueableCommand for T { /// Queues the given command for further execution. /// /// Queued commands will be executed in the following cases: @@ -128,17 +118,24 @@ where /// and can therefore not be written to the given `writer`. /// Therefore, there is no difference between [execute](./trait.ExecutableCommand.html) /// and [queue](./trait.QueueableCommand.html) for those old Windows versions. - fn queue(&mut self, command: impl Command) -> Result<&mut Self> { - queue!(self, command)?; + fn queue(&mut self, command: impl Command) -> Result<&mut Self> { + #[cfg(windows)] + if !command.is_ansi_code_supported() { + command.execute_winapi(|| { + write_command_ansi(self, &command)?; + // winapi doesn't support queuing + self.flush()?; + Ok(()) + })?; + return Ok(self); + } + + write_command_ansi(self, command)?; Ok(self) } } -impl ExecutableCommand for T -where - A: Display, - T: Write, -{ +impl ExecutableCommand for T { /// Executes the given command directly. /// /// The given command its ANSI escape code will be written and flushed onto `Self`. @@ -179,8 +176,56 @@ where /// and can therefore not be written to the given `writer`. /// Therefore, there is no difference between [execute](./trait.ExecutableCommand.html) /// and [queue](./trait.QueueableCommand.html) for those old Windows versions. - fn execute(&mut self, command: impl Command) -> Result<&mut Self> { - execute!(self, command)?; + fn execute(&mut self, command: impl Command) -> Result<&mut Self> { + self.queue(command)?; + self.flush()?; Ok(self) } } + +/// Writes the ANSI representation of a command to the given writer. +fn write_command_ansi( + io: &mut (impl io::Write + ?Sized), + command: C, +) -> io::Result<()> { + struct Adapter { + inner: T, + res: io::Result<()>, + } + + impl fmt::Write for Adapter { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.inner.write_all(s.as_bytes()).map_err(|e| { + self.res = Err(e); + fmt::Error + }) + } + } + + let mut adapter = Adapter { + inner: io, + res: Ok(()), + }; + + command + .write_ansi(&mut adapter) + .map_err(|fmt::Error| match adapter.res { + Ok(()) => panic!( + "<{}>::write_ansi incorrectly errored", + std::any::type_name::() + ), + Err(e) => e, + }) +} + +/// Executes the ANSI representation of a command, using the given `fmt::Write`. +pub(crate) fn execute_fmt(f: &mut impl fmt::Write, command: impl Command) -> fmt::Result { + #[cfg(windows)] + if !command.is_ansi_code_supported() { + return command + .execute_winapi(|| panic!("this writer should not be possible to use here")) + .map_err(|_| fmt::Error); + } + + command.write_ansi(f) +} diff --git a/src/cursor.rs b/src/cursor.rs index 21d02a3b..ee8d1b2e 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -46,7 +46,7 @@ pub use sys::position; #[cfg(windows)] use crate::Result; -use crate::{impl_display, Ansi, Command}; +use crate::{impl_display, Command}; use std::fmt; mod ansi; @@ -61,18 +61,9 @@ pub(crate) mod sys; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveTo(pub u16, pub u16); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::move_to_csi_sequence(f, (self.0).0, (self.0).1) - } -} - impl Command for MoveTo { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::move_to_csi_sequence(f, self.0, self.1) } #[cfg(windows)] @@ -90,18 +81,9 @@ impl Command for MoveTo { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToNextLine(pub u16); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::move_to_next_line_csi_sequence(f, (self.0).0) - } -} - impl Command for MoveToNextLine { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::move_to_next_line_csi_sequence(f, self.0) } #[cfg(windows)] @@ -119,18 +101,9 @@ impl Command for MoveToNextLine { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToPreviousLine(pub u16); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::move_to_previous_line_csi_sequence(f, (self.0).0) - } -} - impl Command for MoveToPreviousLine { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::move_to_previous_line_csi_sequence(f, self.0) } #[cfg(windows)] @@ -147,18 +120,9 @@ impl Command for MoveToPreviousLine { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToColumn(pub u16); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::move_to_column_csi_sequence(f, (self.0).0) - } -} - impl Command for MoveToColumn { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::move_to_column_csi_sequence(f, self.0) } #[cfg(windows)] @@ -175,18 +139,9 @@ impl Command for MoveToColumn { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveUp(pub u16); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::move_up_csi_sequence(f, (self.0).0) - } -} - impl Command for MoveUp { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::move_up_csi_sequence(f, self.0) } #[cfg(windows)] @@ -203,18 +158,9 @@ impl Command for MoveUp { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveRight(pub u16); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::move_right_csi_sequence(f, (self.0).0) - } -} - impl Command for MoveRight { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::move_right_csi_sequence(f, self.0) } #[cfg(windows)] @@ -231,18 +177,9 @@ impl Command for MoveRight { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveDown(pub u16); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::move_down_csi_sequence(f, (self.0).0) - } -} - impl Command for MoveDown { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::move_down_csi_sequence(f, self.0) } #[cfg(windows)] @@ -259,18 +196,9 @@ impl Command for MoveDown { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveLeft(pub u16); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::move_left_csi_sequence(f, (self.0).0) - } -} - impl Command for MoveLeft { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::move_left_csi_sequence(f, self.0) } #[cfg(windows)] @@ -291,11 +219,8 @@ impl Command for MoveLeft { pub struct SavePosition; impl Command for SavePosition { - type AnsiType = &'static str; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - ansi::SAVE_POSITION_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::SAVE_POSITION_CSI_SEQUENCE) } #[cfg(windows)] @@ -316,11 +241,8 @@ impl Command for SavePosition { pub struct RestorePosition; impl Command for RestorePosition { - type AnsiType = &'static str; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - ansi::RESTORE_POSITION_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::RESTORE_POSITION_CSI_SEQUENCE) } #[cfg(windows)] @@ -338,11 +260,8 @@ impl Command for RestorePosition { pub struct Hide; impl Command for Hide { - type AnsiType = &'static str; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - ansi::HIDE_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::HIDE_CSI_SEQUENCE) } #[cfg(windows)] @@ -360,11 +279,8 @@ impl Command for Hide { pub struct Show; impl Command for Show { - type AnsiType = &'static str; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - ansi::SHOW_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::SHOW_CSI_SEQUENCE) } #[cfg(windows)] @@ -383,11 +299,8 @@ impl Command for Show { pub struct EnableBlinking; impl Command for EnableBlinking { - type AnsiType = &'static str; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - ansi::ENABLE_BLINKING_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::ENABLE_BLINKING_CSI_SEQUENCE) } #[cfg(windows)] @@ -406,11 +319,8 @@ impl Command for EnableBlinking { pub struct DisableBlinking; impl Command for DisableBlinking { - type AnsiType = &'static str; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - ansi::DISABLE_BLINKING_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::DISABLE_BLINKING_CSI_SEQUENCE) } #[cfg(windows)] @@ -436,7 +346,7 @@ impl_display!(for DisableBlinking); #[cfg(test)] mod tests { - use std::io::{self, stdout, Write}; + use std::io::{self, stdout}; use crate::execute; diff --git a/src/cursor/ansi.rs b/src/cursor/ansi.rs index 492dea15..c3c6b91c 100644 --- a/src/cursor/ansi.rs +++ b/src/cursor/ansi.rs @@ -1,13 +1,13 @@ //! This module provides cursor related ANSI escape codes. use crate::csi; -use std::fmt::{self, Formatter}; +use std::fmt; -pub(crate) fn move_to_csi_sequence(f: &mut Formatter, x: u16, y: u16) -> fmt::Result { +pub(crate) fn move_to_csi_sequence(f: &mut impl fmt::Write, x: u16, y: u16) -> fmt::Result { write!(f, csi!("{};{}H"), y + 1, x + 1) } -pub(crate) fn move_up_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Result { +pub(crate) fn move_up_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { if count != 0 { write!(f, csi!("{}A"), count) } else { @@ -15,7 +15,7 @@ pub(crate) fn move_up_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Result } } -pub(crate) fn move_right_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Result { +pub(crate) fn move_right_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { if count != 0 { write!(f, csi!("{}C"), count) } else { @@ -23,7 +23,7 @@ pub(crate) fn move_right_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Res } } -pub(crate) fn move_down_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Result { +pub(crate) fn move_down_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { if count != 0 { write!(f, csi!("{}B"), count) } else { @@ -31,7 +31,7 @@ pub(crate) fn move_down_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Resu } } -pub(crate) fn move_left_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Result { +pub(crate) fn move_left_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { if count != 0 { write!(f, csi!("{}D"), count) } else { @@ -39,15 +39,18 @@ pub(crate) fn move_left_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Resu } } -pub(crate) fn move_to_column_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Result { +pub(crate) fn move_to_column_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { write!(f, csi!("{}G"), count) } -pub(crate) fn move_to_previous_line_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Result { +pub(crate) fn move_to_previous_line_csi_sequence( + f: &mut impl fmt::Write, + count: u16, +) -> fmt::Result { write!(f, csi!("{}F"), count) } -pub(crate) fn move_to_next_line_csi_sequence(f: &mut Formatter, count: u16) -> fmt::Result { +pub(crate) fn move_to_next_line_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { write!(f, csi!("{}E"), count) } diff --git a/src/event.rs b/src/event.rs index 25dc389f..c0705ff6 100644 --- a/src/event.rs +++ b/src/event.rs @@ -70,6 +70,7 @@ //! Check the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder for more of //! them (`event-*`). +use std::fmt; use std::time::Duration; use parking_lot::RwLock; @@ -225,10 +226,8 @@ where pub struct EnableMouseCapture; impl Command for EnableMouseCapture { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::ENABLE_MOUSE_MODE_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::ENABLE_MOUSE_MODE_CSI_SEQUENCE) } #[cfg(windows)] @@ -249,10 +248,8 @@ impl Command for EnableMouseCapture { pub struct DisableMouseCapture; impl Command for DisableMouseCapture { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::DISABLE_MOUSE_MODE_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::DISABLE_MOUSE_MODE_CSI_SEQUENCE) } #[cfg(windows)] diff --git a/src/lib.rs b/src/lib.rs index d9a48103..b6195ec3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -231,7 +231,6 @@ //! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush pub use crate::{ - ansi::Ansi, command::{Command, ExecutableCommand, QueueableCommand}, error::{ErrorKind, Result}, }; @@ -248,7 +247,6 @@ pub mod terminal; /// A module to query if the current instance is a tty. pub mod tty; -mod ansi; #[cfg(windows)] /// A module that exposes one function to check if the current terminal supports ansi sequences. pub mod ansi_support; diff --git a/src/macros.rs b/src/macros.rs index 8115b559..26c73874 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -5,77 +5,6 @@ macro_rules! csi { ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) }; } -/// Writes an ansi code to the given writer. -#[doc(hidden)] -#[macro_export] -macro_rules! write_ansi_code { - ($writer:expr, $ansi_code:expr) => {{ - use std::io::{self, ErrorKind}; - - write!($writer, "{}", $ansi_code) - .map_err(|e| io::Error::new(ErrorKind::Other, e)) - .map_err($crate::ErrorKind::IoError) - }}; -} - -/// Writes/executes the given command. -#[doc(hidden)] -#[macro_export] -macro_rules! handle_command { - ($writer:expr, $command:expr) => {{ - // Silent warning when the macro is used inside the `command` module - #[allow(unused_imports)] - use $crate::{write_ansi_code, Command}; - - #[cfg(windows)] - { - let command = $command; - if command.is_ansi_code_supported() { - write_ansi_code!($writer, command.ansi_code()) - } else { - command - .execute_winapi(|| { - write!($writer, "{}", command.ansi_code())?; - // winapi doesn't support queuing - $writer.flush()?; - Ok(()) - }) - .map_err($crate::ErrorKind::from) - } - } - #[cfg(unix)] - { - write_ansi_code!($writer, $command.ansi_code()) - } - }}; -} - -// Offer the same functionality as queue! macro, but is used only internally and with std::fmt::Write as $writer -// The difference is in case of winapi we ignore the $writer and use a fake one -#[doc(hidden)] -#[macro_export] -macro_rules! handle_fmt_command { - ($writer:expr, $command:expr) => {{ - use $crate::{write_ansi_code, Command}; - - #[cfg(windows)] - { - let command = $command; - if command.is_ansi_code_supported() { - write_ansi_code!($writer, command.ansi_code()) - } else { - command - .execute_winapi(|| panic!("this writer should not be possible to use here")) - .map_err($crate::ErrorKind::from) - } - } - #[cfg(unix)] - { - write_ansi_code!($writer, $command.ansi_code()) - } - }}; -} - /// Queues one or more command(s) for further execution. /// /// Queued commands must be flushed to the underlying device to be executed. @@ -129,11 +58,14 @@ macro_rules! handle_fmt_command { /// #[macro_export] macro_rules! queue { - ($writer:expr $(, $command:expr)* $(,)?) => { - Ok(()) $( - .and_then(|()| $crate::handle_command!($writer, $command)) - )* - } + ($writer:expr $(, $command:expr)* $(,)?) => {{ + use ::std::io::Write; + + // This allows the macro to take both mut impl Write and &mut impl Write. + Ok($writer.by_ref()) + $(.and_then(|writer| $crate::QueueableCommand::queue(writer, $command)))* + .map(|_| ()) + }} } /// Executes one or more command(s). @@ -179,12 +111,15 @@ macro_rules! queue { /// and [queue](macro.queue.html) for those old Windows versions. #[macro_export] macro_rules! execute { - ($writer:expr $(, $command:expr)* $(,)? ) => { + ($writer:expr $(, $command:expr)* $(,)? ) => {{ + use ::std::io::Write; + // Queue each command, then flush - $crate::queue!($writer $(, $command)*).and_then(|()| { - $writer.flush().map_err($crate::ErrorKind::IoError) - }) - } + $crate::queue!($writer $(, $command)*) + .and_then(|()| { + ::std::io::Write::flush($writer.by_ref()).map_err($crate::ErrorKind::IoError) + }) + }} } #[doc(hidden)] @@ -192,8 +127,8 @@ macro_rules! execute { macro_rules! impl_display { (for $($t:ty),+) => { $(impl ::std::fmt::Display for $t { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> { - $crate::handle_fmt_command!(f, self).map_err(|_| ::std::fmt::Error) + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + $crate::command::execute_fmt(f, self) } })* } @@ -215,6 +150,7 @@ macro_rules! impl_from { mod tests { use std::io; use std::str; + // Helper for execute tests to confirm flush #[derive(Default, Debug, Clone)] pub(self) struct FakeWrite { @@ -239,7 +175,7 @@ mod tests { #[cfg(not(windows))] mod unix { - use std::io::Write; + use std::fmt; use super::FakeWrite; use crate::command::Command; @@ -247,10 +183,8 @@ mod tests { pub struct FakeCommand; impl Command for FakeCommand { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - "cmd" + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str("cmd") } } @@ -305,9 +239,10 @@ mod tests { #[cfg(windows)] mod windows { + use std::fmt; + use std::cell::RefCell; use std::fmt::Debug; - use std::io::Write; use super::FakeWrite; use crate::command::Command; @@ -339,10 +274,8 @@ mod tests { } impl<'a> Command for FakeCommand<'a> { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - self.value + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(self.value) } fn execute_winapi( @@ -379,12 +312,12 @@ mod tests { } // We need this for type inference, for whatever reason. - const EMPTY_RESULT: [&'static str; 0] = []; + const EMPTY_RESULT: [&str; 0] = []; // TODO: confirm that the correct sink was used, based on // is_ansi_code_supported match (writer.buffer.is_empty(), stream.is_empty()) { - (true, true) if stream_result == &EMPTY_RESULT => {} + (true, true) if stream_result == EMPTY_RESULT => {} (true, true) => panic!( "Neither the event stream nor the writer were populated. Expected {:?}", stream_result diff --git a/src/style.rs b/src/style.rs index 74e59335..9dc57888 100644 --- a/src/style.rs +++ b/src/style.rs @@ -110,12 +110,14 @@ //! ); //! ``` -use std::{env, fmt::Display}; +use std::{ + env, + fmt::{self, Display}, +}; #[cfg(windows)] use crate::Result; -use crate::{impl_display, Ansi, Command}; -use std::fmt; +use crate::{impl_display, Command}; pub use self::{ attributes::Attributes, @@ -201,18 +203,9 @@ pub fn available_color_count() -> u16 { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SetForegroundColor(pub Color); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::set_fg_csi_sequence(f, (self.0).0) - } -} - impl Command for SetForegroundColor { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::set_fg_csi_sequence(f, self.0) } #[cfg(windows)] @@ -234,18 +227,9 @@ impl Command for SetForegroundColor { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SetBackgroundColor(pub Color); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::set_bg_csi_sequence(f, (self.0).0) - } -} - impl Command for SetBackgroundColor { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::set_bg_csi_sequence(f, self.0) } #[cfg(windows)] @@ -277,25 +261,16 @@ impl Command for SetBackgroundColor { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SetColors(pub Colors); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(color) = (self.0).0.foreground { +impl Command for SetColors { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + if let Some(color) = self.0.foreground { ansi::set_fg_csi_sequence(f, color)?; } - if let Some(color) = (self.0).0.background { + if let Some(color) = self.0.background { ansi::set_bg_csi_sequence(f, color)?; } Ok(()) } -} - -impl Command for SetColors { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) - } #[cfg(windows)] fn execute_winapi(&self, _writer: impl FnMut() -> Result<()>) -> Result<()> { @@ -319,18 +294,9 @@ impl Command for SetColors { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SetAttribute(pub Attribute); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::set_attr_csi_sequence(f, (self.0).0) - } -} - impl Command for SetAttribute { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::set_attr_csi_sequence(f, self.0) } #[cfg(windows)] @@ -350,17 +316,9 @@ impl Command for SetAttribute { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SetAttributes(pub Attributes); -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::set_attrs_csi_sequence(f, (self.0).0) - } -} - impl Command for SetAttributes { - type AnsiType = Ansi; - - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::set_attrs_csi_sequence(f, self.0) } #[cfg(windows)] @@ -378,16 +336,11 @@ impl Command for SetAttributes { /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Copy, Clone)] -pub struct PrintStyledContent(pub StyledContent); +pub struct PrintStyledContent(pub StyledContent); -impl Command for PrintStyledContent -where - D: Display + Clone, -{ - type AnsiType = StyledContent; - - fn ansi_code(&self) -> Self::AnsiType { - self.0.clone() +impl Command for PrintStyledContent { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + write!(f, "{}", self.0) } #[cfg(windows)] @@ -405,10 +358,8 @@ where pub struct ResetColor; impl Command for ResetColor { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::RESET_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::RESET_CSI_SEQUENCE) } #[cfg(windows)] @@ -421,28 +372,22 @@ impl Command for ResetColor { /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Print(pub T); +pub struct Print(pub T); -impl Command for Print { - type AnsiType = T; - - fn ansi_code(&self) -> Self::AnsiType { - self.0.clone() +impl Command for Print { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + write!(f, "{}", self.0) } #[cfg(windows)] fn execute_winapi(&self, mut writer: impl FnMut() -> Result<()>) -> Result<()> { - writer()?; - Ok(()) + writer() } } -impl Display for Print { - fn fmt( - &self, - f: &mut ::std::fmt::Formatter<'_>, - ) -> ::std::result::Result<(), ::std::fmt::Error> { - write!(f, "{}", self.ansi_code()) +impl Display for Print { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) } } diff --git a/src/style/ansi.rs b/src/style/ansi.rs index 46bbb06a..8107125e 100644 --- a/src/style/ansi.rs +++ b/src/style/ansi.rs @@ -8,19 +8,22 @@ use crate::{ style::{Attribute, Attributes, Color, Colored}, }; -pub(crate) fn set_fg_csi_sequence(f: &mut Formatter, fg_color: Color) -> fmt::Result { +pub(crate) fn set_fg_csi_sequence(f: &mut impl fmt::Write, fg_color: Color) -> fmt::Result { write!(f, csi!("{}m"), Colored::ForegroundColor(fg_color)) } -pub(crate) fn set_bg_csi_sequence(f: &mut Formatter, bg_color: Color) -> fmt::Result { +pub(crate) fn set_bg_csi_sequence(f: &mut impl fmt::Write, bg_color: Color) -> fmt::Result { write!(f, csi!("{}m"), Colored::BackgroundColor(bg_color)) } -pub(crate) fn set_attr_csi_sequence(f: &mut Formatter, attribute: Attribute) -> fmt::Result { +pub(crate) fn set_attr_csi_sequence(f: &mut impl fmt::Write, attribute: Attribute) -> fmt::Result { write!(f, csi!("{}m"), attribute.sgr()) } -pub(crate) fn set_attrs_csi_sequence(f: &mut Formatter, attributes: Attributes) -> fmt::Result { +pub(crate) fn set_attrs_csi_sequence( + f: &mut impl fmt::Write, + attributes: Attributes, +) -> fmt::Result { for attr in Attribute::iterator() { if attributes.has(attr) { write!(f, csi!("{}m"), attr.sgr())?; @@ -32,7 +35,7 @@ pub(crate) fn set_attrs_csi_sequence(f: &mut Formatter, attributes: Attributes) pub(crate) const RESET_CSI_SEQUENCE: &str = csi!("0m"); impl fmt::Display for Colored { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let color; match *self { diff --git a/src/style/styled_content.rs b/src/style/styled_content.rs index a5f34f33..8c58bf63 100644 --- a/src/style/styled_content.rs +++ b/src/style/styled_content.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::{ - handle_fmt_command, + command::execute_fmt, style::{ Attribute, Color, Colorize, ContentStyle, ResetColor, SetAttributes, SetBackgroundColor, SetForegroundColor, Styler, @@ -98,16 +98,16 @@ impl Display for StyledContent { let mut reset = false; if let Some(bg) = self.style.background_color { - handle_fmt_command!(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?; + execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?; reset_background = true; } if let Some(fg) = self.style.foreground_color { - handle_fmt_command!(f, SetForegroundColor(fg)).map_err(|_| fmt::Error)?; + execute_fmt(f, SetForegroundColor(fg)).map_err(|_| fmt::Error)?; reset_foreground = true; } if !self.style.attributes.is_empty() { - handle_fmt_command!(f, SetAttributes(self.style.attributes)).map_err(|_| fmt::Error)?; + execute_fmt(f, SetAttributes(self.style.attributes)).map_err(|_| fmt::Error)?; reset = true; } @@ -117,14 +117,14 @@ impl Display for StyledContent { // NOTE: This will reset colors even though self has no colors, hence produce unexpected // resets. // TODO: reset the set attributes only. - handle_fmt_command!(f, ResetColor).map_err(|_| fmt::Error)?; + execute_fmt(f, ResetColor).map_err(|_| fmt::Error)?; } else { // NOTE: Since the above bug, we do not need to reset colors when we reset attributes. if reset_background { - handle_fmt_command!(f, SetBackgroundColor(Color::Reset)).map_err(|_| fmt::Error)?; + execute_fmt(f, SetBackgroundColor(Color::Reset)).map_err(|_| fmt::Error)?; } if reset_foreground { - handle_fmt_command!(f, SetForegroundColor(Color::Reset)).map_err(|_| fmt::Error)?; + execute_fmt(f, SetForegroundColor(Color::Reset)).map_err(|_| fmt::Error)?; } } diff --git a/src/terminal.rs b/src/terminal.rs index 89434b49..4d521123 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -81,6 +81,8 @@ //! //! For manual execution control check out [crossterm::queue](../macro.queue.html). +use std::fmt; + #[cfg(windows)] use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer}; #[cfg(feature = "serde")] @@ -121,10 +123,8 @@ pub fn size() -> Result<(u16, u16)> { pub struct DisableLineWrap; impl Command for DisableLineWrap { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::DISABLE_LINE_WRAP_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::DISABLE_LINE_WRAP_CSI_SEQUENCE) } #[cfg(windows)] @@ -142,10 +142,8 @@ impl Command for DisableLineWrap { pub struct EnableLineWrap; impl Command for EnableLineWrap { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::ENABLE_LINE_WRAP_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::ENABLE_LINE_WRAP_CSI_SEQUENCE) } #[cfg(windows)] @@ -184,10 +182,8 @@ impl Command for EnableLineWrap { pub struct EnterAlternateScreen; impl Command for EnterAlternateScreen { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE) } #[cfg(windows)] @@ -224,10 +220,8 @@ impl Command for EnterAlternateScreen { pub struct LeaveAlternateScreen; impl Command for LeaveAlternateScreen { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(ansi::LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE) } #[cfg(windows)] @@ -263,10 +257,8 @@ pub enum ClearType { pub struct ScrollUp(pub u16); impl Command for ScrollUp { - type AnsiType = String; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::scroll_up_csi_sequence(self.0) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::scroll_up_csi_sequence(f, self.0) } #[cfg(windows)] @@ -284,10 +276,8 @@ impl Command for ScrollUp { pub struct ScrollDown(pub u16); impl Command for ScrollDown { - type AnsiType = String; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::scroll_down_csi_sequence(self.0) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::scroll_down_csi_sequence(f, self.0) } #[cfg(windows)] @@ -307,16 +297,14 @@ impl Command for ScrollDown { pub struct Clear(pub ClearType); impl Command for Clear { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - match self.0 { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(match self.0 { ClearType::All => ansi::CLEAR_ALL_CSI_SEQUENCE, ClearType::FromCursorDown => ansi::CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE, ClearType::FromCursorUp => ansi::CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE, ClearType::CurrentLine => ansi::CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE, ClearType::UntilNewLine => ansi::CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE, - } + }) } #[cfg(windows)] @@ -334,10 +322,8 @@ impl Command for Clear { pub struct SetSize(pub u16, pub u16); impl Command for SetSize { - type AnsiType = String; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::set_size_csi_sequence(self.0, self.1) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::set_size_csi_sequence(f, self.0, self.1) } #[cfg(windows)] @@ -355,10 +341,8 @@ impl Command for SetSize { pub struct SetTitle<'a>(pub &'a str); impl<'a> Command for SetTitle<'a> { - type AnsiType = String; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::set_title_ansi_sequence(self.0) + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + ansi::set_title_ansi_sequence(f, self.0) } #[cfg(windows)] @@ -374,10 +358,7 @@ impl_display!(for Clear); #[cfg(test)] mod tests { - use std::{ - io::{stdout, Write}, - thread, time, - }; + use std::{io::stdout, thread, time}; use crate::execute; diff --git a/src/terminal/ansi.rs b/src/terminal/ansi.rs index 513c4a1f..427509ab 100644 --- a/src/terminal/ansi.rs +++ b/src/terminal/ansi.rs @@ -1,5 +1,7 @@ //! This module provides terminal related ANSI escape codes. +use std::fmt; + use crate::csi; pub(crate) const CLEAR_ALL_CSI_SEQUENCE: &str = csi!("2J"); @@ -12,18 +14,22 @@ pub(crate) const LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE: &str = csi!("?1049l"); pub(crate) const DISABLE_LINE_WRAP_CSI_SEQUENCE: &str = csi!("?7l"); pub(crate) const ENABLE_LINE_WRAP_CSI_SEQUENCE: &str = csi!("?7h"); -pub(crate) fn scroll_up_csi_sequence(count: u16) -> String { - format!(csi!("{}S"), count) +pub(crate) fn scroll_up_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { + write!(f, csi!("{}S"), count) } -pub(crate) fn scroll_down_csi_sequence(count: u16) -> String { - format!(csi!("{}T"), count) +pub(crate) fn scroll_down_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { + write!(f, csi!("{}T"), count) } -pub(crate) fn set_size_csi_sequence(width: u16, height: u16) -> String { - format!(csi!("8;{};{}t"), height, width) +pub(crate) fn set_size_csi_sequence( + f: &mut impl fmt::Write, + width: u16, + height: u16, +) -> fmt::Result { + write!(f, csi!("8;{};{}t"), height, width) } -pub(crate) fn set_title_ansi_sequence(title: &str) -> String { - format!("\x1B]0;{}\x07", title) +pub(crate) fn set_title_ansi_sequence(f: &mut impl fmt::Write, title: &str) -> fmt::Result { + write!(f, "\x1B]0;{}\x07", title) }