Replace AnsiType with write_ansi (#523)

This commit is contained in:
Koxiaet 2020-12-28 06:56:32 +00:00 committed by GitHub
parent 52bed07ce8
commit 5be7d18475
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 238 additions and 424 deletions

View File

@ -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,

View File

@ -34,7 +34,7 @@ pub fn read_line() -> Result<String> {
}
}
return Ok(line);
Ok(line)
}
fn main() {

View File

@ -2,7 +2,7 @@
//!
//! cargo run --example event-read
use std::io::{stdout, Write};
use std::io::stdout;
use crossterm::{
cursor::position,

View File

@ -1,4 +0,0 @@
/// Wrapper type for write dynamic ansi string
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[doc(hidden)]
pub struct Ansi<T>(pub T);

View File

@ -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<T: Command> Command for &T {
type AnsiType = T::AnsiType;
#[inline]
fn ansi_code(&self) -> Self::AnsiType {
T::ansi_code(self)
impl<T: Command + ?Sized> Command for &T {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
(**self).write_ansi(f)
}
#[inline]
@ -60,23 +54,19 @@ impl<T: Command> Command for &T {
}
}
/// An interface for commands that can be queued for further execution.
pub trait QueueableCommand<T: Display>: 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<AnsiType = T>) -> Result<&mut Self>;
fn queue(&mut self, command: impl Command) -> Result<&mut Self>;
}
/// An interface for commands that are directly executed.
pub trait ExecutableCommand<T: Display>: 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<AnsiType = T>) -> Result<&mut Self>;
fn execute(&mut self, command: impl Command) -> Result<&mut Self>;
}
impl<T, A> QueueableCommand<A> for T
where
A: Display,
T: Write,
{
impl<T: Write + ?Sized> 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<AnsiType = A>) -> 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<T, A> ExecutableCommand<A> for T
where
A: Display,
T: Write,
{
impl<T: Write + ?Sized> 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<AnsiType = A>) -> 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<C: Command>(
io: &mut (impl io::Write + ?Sized),
command: C,
) -> io::Result<()> {
struct Adapter<T> {
inner: T,
res: io::Result<()>,
}
impl<T: Write> fmt::Write for Adapter<T> {
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::<C>()
),
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)
}

View File

@ -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<MoveTo> {
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<Self>;
#[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<MoveToNextLine> {
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<Self>;
#[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<MoveToPreviousLine> {
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<Self>;
#[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<MoveToColumn> {
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<Self>;
#[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<MoveUp> {
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<Self>;
#[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<MoveRight> {
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<Self>;
#[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<MoveDown> {
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<Self>;
#[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<MoveLeft> {
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<Self>;
#[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;

View File

@ -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)
}

View File

@ -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)]

View File

@ -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;

View File

@ -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

View File

@ -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<SetForegroundColor> {
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<Self>;
#[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<SetBackgroundColor> {
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<Self>;
#[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<SetColors> {
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<Self>;
#[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<SetAttribute> {
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<Self>;
#[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<SetAttributes> {
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<Self>;
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<D: Display + Clone>(pub StyledContent<D>);
pub struct PrintStyledContent<D: Display>(pub StyledContent<D>);
impl<D> Command for PrintStyledContent<D>
where
D: Display + Clone,
{
type AnsiType = StyledContent<D>;
fn ansi_code(&self) -> Self::AnsiType {
self.0.clone()
impl<D: Display> Command for PrintStyledContent<D> {
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<T: Display + Clone>(pub T);
pub struct Print<T: Display>(pub T);
impl<T: Display + Clone> Command for Print<T> {
type AnsiType = T;
fn ansi_code(&self) -> Self::AnsiType {
self.0.clone()
impl<T: Display> Command for Print<T> {
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<T: Display + Clone> Display for Print<T> {
fn fmt(
&self,
f: &mut ::std::fmt::Formatter<'_>,
) -> ::std::result::Result<(), ::std::fmt::Error> {
write!(f, "{}", self.ansi_code())
impl<T: Display> Display for Print<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

View File

@ -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 {

View File

@ -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<D: Display> Display for StyledContent<D> {
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<D: Display> Display for StyledContent<D> {
// 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)?;
}
}

View File

@ -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;

View File

@ -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)
}