Refactored macro's, moved Output, rewrote documentation. (#326)

This commit is contained in:
Timon 2019-12-04 17:26:57 +01:00 committed by GitHub
parent fb5a8f24cc
commit dec0d74b32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 374 additions and 272 deletions

View File

@ -5,9 +5,11 @@
- It's **highly recommended** to read the - It's **highly recommended** to read the
[Upgrade from 0.13 to 0.14](https://github.com/crossterm-rs/crossterm/wiki/Upgrade-from-0.13-to-0.14) [Upgrade from 0.13 to 0.14](https://github.com/crossterm-rs/crossterm/wiki/Upgrade-from-0.13-to-0.14)
documentation documentation
* Replace `docs/UPGRADE.md` with the [Upgrade Paths](https://github.com/crossterm-rs/crossterm/wiki#upgrade-paths) - Replace `docs/UPGRADE.md` with the [Upgrade Paths](https://github.com/crossterm-rs/crossterm/wiki#upgrade-paths)
documentation documentation
- Add `MoveToColumn`, `MoveToPreviousLine`, `MoveToNextLine` commands - Add `MoveToColumn`, `MoveToPreviousLine`, `MoveToNextLine` commands
- Replace `utils::Output` command with `style::Print` command
- Fix enable/disable mouse capture commands on Windows.
# Version 0.13.3 # Version 0.13.3

View File

@ -14,7 +14,7 @@ use crossterm::{
const HELP: &str = r#"Blocking poll() & non-blocking read() const HELP: &str = r#"Blocking poll() & non-blocking read()
- Keyboard, mouse and terminal resize events enabled - Keyboard, mouse and terminal resize events enabled
- Prints "." every second if there's no event - Prints "." every second if there's no event
- Hit "c" to print current cursor position - Hit "c" to print current cursor position
- Use Esc to quit - Use Esc to quit
"#; "#;

View File

@ -234,6 +234,11 @@ impl Command for EnableMouseCapture {
fn execute_winapi(&self) -> Result<()> { fn execute_winapi(&self) -> Result<()> {
sys::windows::enable_mouse_capture() sys::windows::enable_mouse_capture()
} }
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
false
}
} }
/// A command that disables mouse event capturing. /// A command that disables mouse event capturing.
@ -252,6 +257,11 @@ impl Command for DisableMouseCapture {
fn execute_winapi(&self) -> Result<()> { fn execute_winapi(&self) -> Result<()> {
sys::windows::disable_mouse_capture() sys::windows::disable_mouse_capture()
} }
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
false
}
} }
/// Represents an event. /// Represents an event.

View File

@ -1,7 +1,3 @@
use futures::{
task::{Context, Poll},
Stream,
};
use std::{ use std::{
pin::Pin, pin::Pin,
sync::{ sync::{
@ -12,6 +8,11 @@ use std::{
time::Duration, time::Duration,
}; };
use futures::{
task::{Context, Poll},
Stream,
};
use crate::Result; use crate::Result;
use super::{ use super::{

View File

@ -228,9 +228,7 @@
//! [stderr]: https://doc.rust-lang.org/std/io/fn.stderr.html //! [stderr]: https://doc.rust-lang.org/std/io/fn.stderr.html
//! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush //! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush
#[cfg(windows)] pub use utils::{Command, ErrorKind, ExecutableCommand, QueueableCommand, Result};
pub use utils::functions::supports_ansi;
pub use utils::{Command, ErrorKind, ExecutableCommand, Output, QueueableCommand, Result};
/// A module to work with the terminal cursor /// A module to work with the terminal cursor
pub mod cursor; pub mod cursor;

View File

@ -3,7 +3,7 @@ pub(crate) use ansi::AnsiAlternateScreen;
pub(crate) use windows::WinApiAlternateScreen; pub(crate) use windows::WinApiAlternateScreen;
#[cfg(windows)] #[cfg(windows)]
use crate::supports_ansi; use crate::utils::functions::supports_ansi;
use crate::utils::Result; use crate::utils::Result;
pub(crate) mod ansi; pub(crate) mod ansi;

View File

@ -1,4 +1,6 @@
use crate::{csi, utils::Result, write_cout}; use std::io::{stdout, Write};
use crate::{csi, utils::Result};
use super::AlternateScreen; use super::AlternateScreen;
@ -9,12 +11,16 @@ pub(crate) struct AnsiAlternateScreen;
impl AlternateScreen for AnsiAlternateScreen { impl AlternateScreen for AnsiAlternateScreen {
fn enter(&self) -> Result<()> { fn enter(&self) -> Result<()> {
write_cout!(ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE)?; let mut stdout = stdout();
write!(stdout, "{}", ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE)?;
stdout.flush()?;
Ok(()) Ok(())
} }
fn leave(&self) -> Result<()> { fn leave(&self) -> Result<()> {
write_cout!(LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE)?; let mut stdout = stdout();
write!(stdout, "{}", LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE)?;
stdout.flush()?;
Ok(()) Ok(())
} }
} }

View File

@ -30,8 +30,8 @@
//! ```no_run //! ```no_run
//! use std::io::{stdout, Write}; //! use std::io::{stdout, Write};
//! //!
//! use crossterm::{execute, Result, Output}; //! use crossterm::{execute, Result};
//! use crossterm::style::{SetForegroundColor, SetBackgroundColor, ResetColor, Color, Attribute}; //! use crossterm::style::{Print, SetForegroundColor, SetBackgroundColor, ResetColor, Color, Attribute};
//! //!
//! fn main() -> Result<()> { //! fn main() -> Result<()> {
//! execute!( //! execute!(
@ -40,8 +40,8 @@
//! SetForegroundColor(Color::Blue), //! SetForegroundColor(Color::Blue),
//! // Red background //! // Red background
//! SetBackgroundColor(Color::Red), //! SetBackgroundColor(Color::Red),
//! // output text //! // Print text
//! Output("Styled text here.".to_string()), //! Print("Blue text on Red.".to_string()),
//! // Reset to default colors //! // Reset to default colors
//! ResetColor //! ResetColor
//! ) //! )
@ -69,7 +69,7 @@
//! ```no_run //! ```no_run
//! use std::io::{stdout, Write}; //! use std::io::{stdout, Write};
//! //!
//! use crossterm::{execute, Result, Output}; //! use crossterm::{execute, Result, style::Print};
//! use crossterm::style::{SetAttribute, Attribute}; //! use crossterm::style::{SetAttribute, Attribute};
//! //!
//! fn main() -> Result<()> { //! fn main() -> Result<()> {
@ -77,7 +77,7 @@
//! stdout(), //! stdout(),
//! // Set to bold //! // Set to bold
//! SetAttribute(Attribute::Bold), //! SetAttribute(Attribute::Bold),
//! Output("Styled text here.".to_string()), //! Print("Bold text here.".to_string()),
//! // Reset all attributes //! // Reset all attributes
//! SetAttribute(Attribute::Reset) //! SetAttribute(Attribute::Reset)
//! ) //! )
@ -332,6 +332,34 @@ impl Command for ResetColor {
} }
} }
/// A command that prints the given displayable type.
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct Print<T: Display + Clone>(pub T);
impl<T: Display + Clone> Command for Print<T> {
type AnsiType = T;
fn ansi_code(&self) -> Self::AnsiType {
self.0.clone()
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
print!("{}", self.0);
Ok(())
}
}
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_display!(for SetForegroundColor); impl_display!(for SetForegroundColor);
impl_display!(for SetBackgroundColor); impl_display!(for SetBackgroundColor);
impl_display!(for SetAttribute); impl_display!(for SetAttribute);

View File

@ -195,7 +195,7 @@ mod tests {
#[cfg(windows)] #[cfg(windows)]
{ {
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
use crate::utils::sys::winapi::ansi::set_virtual_terminal_processing; use crate::utils::sys::windows::set_virtual_terminal_processing;
// if it is not listed we should try with WinApi to check if we do support ANSI-codes. // if it is not listed we should try with WinApi to check if we do support ANSI-codes.
match set_virtual_terminal_processing(true) { match set_virtual_terminal_processing(true) {

View File

@ -1,12 +1,13 @@
//! # Utils //! # Utils
pub use self::{ pub use self::{
command::{Command, ExecutableCommand, Output, QueueableCommand}, command::{Command, ExecutableCommand, QueueableCommand},
error::{ErrorKind, Result}, error::{ErrorKind, Result},
}; };
mod command; mod command;
mod error; mod error;
#[cfg(windows)]
pub(crate) mod functions; pub(crate) mod functions;
pub(crate) mod macros; pub(crate) mod macros;
pub(crate) mod sys; pub(crate) mod sys;

View File

@ -1,43 +1,53 @@
use std::{fmt::Display, io::Write}; use std::{fmt::Display, io::Write};
use crate::{execute, queue, write_cout}; use crate::{execute, queue};
use super::error::Result; use super::error::Result;
/// A command is an action that can be performed on the terminal. /// An interface for a command that performs an action on the terminal.
/// ///
/// crossterm already delivers a number of commands. /// Crossterm provides a set of commands,
/// There is no need to implement them yourself. /// and there is no immediate reason to implement a command yourself.
/// Also, you don't have to execute the commands yourself by calling a function. /// 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 { pub trait Command {
type AnsiType: Display; type AnsiType: Display;
/// Returns the ANSI code representation of this command. /// Returns an ANSI code representation of this command.
/// You can manipulate the terminal behaviour by writing an ANSI escape code to the terminal. /// An ANSI code can manipulate the terminal by writing it to the terminal buffer.
/// You are able to use ANSI escape codes only for windows 10 and UNIX systems. /// However, only Windows 10 and UNIX systems support this.
/// ///
/// **This method is mainly used internally by crossterm!** /// 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 ansi_code(&self) -> Self::AnsiType;
/// Execute this command. /// Execute this command.
/// ///
/// On operating systems that do not support ANSI escape codes ( < Windows 10) we need to call WinApi to execute this command. /// Windows versions lower than windows 10 do not support ANSI escape codes,
/// therefore a direct WinAPI call is made.
/// ///
/// **This method is mainly used internally by crossterm!** /// This method does not need to be accessed manually, as it is used by the crossterm's [Command Api](../#command-api)
#[cfg(windows)] #[cfg(windows)]
fn execute_winapi(&self) -> Result<()>; fn execute_winapi(&self) -> Result<()>;
/// Returns whether the ansi code representation of this command is supported by windows.
///
/// A list of supported ANSI escape codes
/// can be found [here](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences).
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
super::functions::supports_ansi()
}
} }
/// A trait that defines behaviour for a command which can be executed at a later time. /// An interface for commands that can be queued for further execution.
/// This can be used in order to get more performance.
pub trait QueueableCommand<T: Display>: Sized { pub trait QueueableCommand<T: Display>: Sized {
/// Queues the given command for later execution. /// 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<AnsiType = T>) -> Result<&mut Self>;
} }
/// A trait that defines behaviour for a command which will be executed immediately. /// An interface for commands that are directly executed.
pub trait ExecutableCommand<T: Display>: Sized { pub trait ExecutableCommand<T: Display>: Sized {
/// Execute the given command directly. /// Executes the given command directly.
fn execute(&mut self, command: impl Command<AnsiType = T>) -> Result<&mut Self>; fn execute(&mut self, command: impl Command<AnsiType = T>) -> Result<&mut Self>;
} }
@ -46,24 +56,57 @@ where
A: Display, A: Display,
T: Write, T: Write,
{ {
/// Queue the given command for later execution. /// Queues the given command for further execution.
/// ///
/// Queued commands will be executed in the following cases: /// Queued commands will be executed in the following cases:
/// - When you manually call `flush` on the given writer.
/// - When the buffer is to full, then the terminal will flush for you.
/// - Incase of `stdout` each line, because `stdout` is line buffered.
/// ///
/// # Parameters /// * When `flush` is called manually on the given type implementing `io::Write`.
/// * The terminal will `flush` automatically if the buffer is full.
/// * Each line is flushed in case of `stdout`, because it is line buffered.
///
/// # Arguments
///
/// - [Command](./trait.Command.html) /// - [Command](./trait.Command.html)
/// ///
/// The command that you want to queue for later execution. /// The command that you want to queue for later execution.
/// ///
/// # Remarks /// # Examples
/// - In the case of UNIX and windows 10, ANSI codes are written to the given 'writer'. ///
/// - In case of Windows versions lower than 10, a direct WinApi call will be made. /// ```rust
/// This is happening because windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer. /// use std::io::{Write, stdout};
/// Because of that there is no difference between `execute` and `queue` for those windows versions. /// use crossterm::{Result, QueueableCommand, style::Print};
/// - Queuing might sound that there is some scheduling going on, however, this means that we write to the stdout without flushing which will cause commands to be stored in the buffer without them being written to the terminal. ///
/// fn main() -> Result<()> {
/// let mut stdout = stdout();
///
/// // `Print` will executed executed when `flush` is called.
/// stdout
/// .queue(Print("foo 1\n".to_string()))?
/// .queue(Print("foo 2".to_string()))?;
///
/// // some other code (no execution happening here) ...
///
/// // when calling `flush` on `stdout`, all commands will be written to the stdout and therefore executed.
/// stdout.flush()?;
///
/// Ok(())
///
/// // ==== Output ====
/// // foo 1
/// // foo 2
/// }
/// ```
///
/// Have a look over at the [Command API](./#command-api) for more details.
///
/// # Notes
///
/// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'.
/// * In case of Windows versions lower than 10, a direct WinApi call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// 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> { fn queue(&mut self, command: impl Command<AnsiType = A>) -> Result<&mut Self> {
queue!(self, command)?; queue!(self, command)?;
Ok(self) Ok(self)
@ -75,47 +118,48 @@ where
A: Display, A: Display,
T: Write, T: Write,
{ {
/// Execute the given command directly. /// Executes the given command directly.
/// This function will `write` the ANSI escape code to this type and call `flush`.
/// ///
/// In case you have many executions after on and another you can use `queue(command)` to get some better performance. /// The given command its ANSI escape code will be written and flushed onto `Self`.
/// The `queue` function will not call `flush`.
/// ///
/// # Remarks /// # Arguments
/// - In the case of UNIX and windows 10, ANSI codes are written to the given 'writer'. ///
/// - In case of Windows versions lower than 10, a direct WinApi call will be made. /// - [Command](./trait.Command.html)
/// This is happening because Windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer. ///
/// Because of that there is no difference between `execute` and `queue` for those windows versions. /// The command that you want to execute directly.
///
/// # Example
///
/// ```rust
/// use std::io::{Write, stdout};
/// use crossterm::{Result, ExecutableCommand, style::Print};
///
/// fn main() -> Result<()> {
/// // will be executed directly
/// stdout()
/// .execute(Print("sum:\n".to_string()))?
/// .execute(Print(format!("1 + 1= {} ", 1 + 1)))?;
///
/// Ok(())
///
/// // ==== Output ====
/// // sum:
/// // 1 + 1 = 2
/// }
/// ```
///
/// Have a look over at the [Command API](./#command-api) for more details.
///
/// # Notes
///
/// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'.
/// * In case of Windows versions lower than 10, a direct WinApi call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// 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> { fn execute(&mut self, command: impl Command<AnsiType = A>) -> Result<&mut Self> {
execute!(self, command)?; execute!(self, command)?;
Ok(self) Ok(self)
} }
} }
/// When executed, this command will output the given displayable to the buffer.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Output<T: Display + Clone>(pub T);
impl<T: Display + Clone> Command for Output<T> {
type AnsiType = T;
fn ansi_code(&self) -> Self::AnsiType {
self.0.clone()
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
print!("{}", self.0);
Ok(())
}
}
impl<T: Display + Clone> Display for Output<T> {
fn fmt(
&self,
f: &mut ::std::fmt::Formatter<'_>,
) -> ::std::result::Result<(), ::std::fmt::Error> {
write!(f, "{}", self.ansi_code())
}
}

View File

@ -1,24 +1,29 @@
#[cfg(windows)] use super::sys::windows::set_virtual_terminal_processing;
use super::sys::winapi::ansi::set_virtual_terminal_processing; use lazy_static::lazy_static;
#[cfg(windows)] lazy_static! {
pub fn supports_ansi() -> bool { static ref SUPPORTS_ANSI_ESCAPE_CODES: bool = {
// Some terminals on windows like GitBash can't use WinaApi calls directly so when we try to enable the ANSI-flag for windows this won't work. // Some terminals on windows like GitBash can't use WinaApi calls directly
// Because of that we should check first if the TERM-variable is set and see if the current terminal is a terminal who does support ANSI. // so when we try to enable the ANSI-flag for windows this won't work.
// Because of that we should check first if the TERM-variable is set
if is_specific_term() { // and see if the current terminal is a terminal who does support ANSI.
return true; if is_specific_term() {
} true
} else {
// if it is not listed we should try with WinApi to check if we do support ANSI-codes. // if it is not listed we should try with WinApi to check if we do support ANSI-codes.
set_virtual_terminal_processing(true) set_virtual_terminal_processing(true)
.map(|_| true) .map(|_| true)
.unwrap_or(false) .unwrap_or(false)
}
};
} }
// checks if the 'TERM' environment variable is set to check if the terminal supports ANSI-codes. pub fn supports_ansi() -> bool {
*SUPPORTS_ANSI_ESCAPE_CODES
}
// Checks if the 'TERM' environment variable is set to check if the terminal supports ANSI-codes.
// I got the list of terminals from here: https://github.com/keqingrong/supports-ansi/blob/master/index.js // I got the list of terminals from here: https://github.com/keqingrong/supports-ansi/blob/master/index.js
#[cfg(windows)]
fn is_specific_term() -> bool { fn is_specific_term() -> bool {
const TERMS: [&str; 15] = [ const TERMS: [&str; 15] = [
"xterm", // xterm, PuTTY, Mintty "xterm", // xterm, PuTTY, Mintty

View File

@ -5,168 +5,178 @@ macro_rules! csi {
($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) }; ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) };
} }
/// Write a string to standard output whereafter the stdout will be flushed. /// Writes an ansi code to the given writer.
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! write_cout { macro_rules! write_ansi_code {
($write:expr, $string:expr) => {{ ($writer:expr, $ansi_code:expr) => {{
use $crate::ErrorKind; use std::{
error::Error,
io::{self, ErrorKind},
};
let fmt = format!("{}", $string); write!($writer, "{}", $ansi_code)
let bytes = fmt.as_bytes(); .map_err(|e| io::Error::new(ErrorKind::Other, e.description()))
.map_err($crate::ErrorKind::IoError)
$write
.write_all(bytes)
.and_then(|_| $write.flush().map(|_| bytes.len()))
.map_err(ErrorKind::IoError)
}};
($string:expr) => {{
// Bring Write into the scope and ignore unused imports if it's
// already imported by the user
#[allow(unused_imports)]
use std::io::Write;
write_cout!(::std::io::stdout(), $string)
}}; }};
} }
/// Queue one or more command(s) for execution in the near future. /// Writes/executes the given command.
/// #[doc(hidden)]
/// Queued commands will be executed in the following cases:
/// - When you manually call `flush` on the given writer.
/// - When the buffer is to full, then the terminal will flush for you.
/// - Incase of `stdout` each line, because `stdout` is line buffered.
///
/// # Parameters
/// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html)
///
/// Crossterm will write the ANSI escape codes to this given writer (No flush will be done).
/// - [Command](./trait.Command.html)
///
/// Give one or more commands that you want to queue for execution
///
/// # Example
/// ```rust
///
/// use std::io::{Write, stdout};
///
/// use crossterm::{queue, Output};
///
/// let mut stdout = stdout();
///
/// // will be executed when flush is called
/// queue!(stdout, Output("foo".to_string()));
///
/// // some other code (no execution happening here) ...
///
/// // when calling flush on stdout, all commands will be written to the stdout and therefor executed.
/// stdout.flush();
/// ```
///
/// # Remarks
/// - In the case of UNIX and windows 10, ANSI codes are written to the given 'writer'.
/// - In case of Windows versions lower than 10, a direct WinApi call will be made.
/// This is happening because windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer.
/// Because of that there is no difference between `execute` and `queue` for those windows versions.
/// - Queuing might sound that there is some scheduling going on, however, this means that we write to the stdout without flushing which will cause commands to be stored in the buffer without them being written to the terminal.
#[macro_export] #[macro_export]
macro_rules! queue { macro_rules! handle_command {
($write:expr, $($command:expr), * $(,)? ) => {{ ($writer:expr, $command:expr) => {{
// Silent warning when the macro is used inside the `command` module // Silent warning when the macro is used inside the `command` module
#[allow(unused_imports)] #[allow(unused_imports)]
use $crate::Command; use $crate::{write_ansi_code, Command};
let mut error = None;
#[cfg(windows)]
{
// ansi code is not always a string, however it does implement `Display` and `Write`.
// In order to check if the code is supported we have to make it a string.
let ansi_code = format!("{}", $command.ansi_code());
if $command.is_ansi_code_supported() {
write_ansi_code!($writer, &ansi_code)
} else {
$command.execute_winapi().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 will be executed in the following cases:
///
/// * When `flush` is called manually on the given type implementing `io::Write`.
/// * The terminal will `flush` automatically if the buffer is full.
/// * Each line is flushed in case of `stdout`, because it is line buffered.
///
/// # Arguments
///
/// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html)
///
/// ANSI escape codes are written on the given 'writer', after which they are flushed.
///
/// - [Command](./trait.Command.html)
///
/// One or more commands
///
/// # Examples
///
/// ```rust
/// use std::io::{Write, stdout};
/// use crossterm::{queue, style::Print};
///
/// fn main() {
/// let mut stdout = stdout();
///
/// // `Print` will executed executed when `flush` is called.
/// queue!(stdout, Print("foo".to_string()));
///
/// // some other code (no execution happening here) ...
///
/// // when calling `flush` on `stdout`, all commands will be written to the stdout and therefore executed.
/// stdout.flush();
///
/// // ==== Output ====
/// // foo
/// }
/// ```
///
/// Have a look over at the [Command API](./#command-api) for more details.
///
/// # Notes
///
/// In case of Windows versions lower than 10, a direct WinApi call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// and can therefore not be written to the given `writer`.
/// Therefore, there is no difference between [execute](macro.execute.html)
/// and [queue](macro.queue.html) for those old Windows versions.
///
#[macro_export]
macro_rules! queue {
($writer:expr, $($command:expr), * $(,)?) => {{
// Silent warning when the macro is used inside the `command` module
#[allow(unused_imports)]
use $crate::{Command, handle_command};
#[allow(unused_assignments)]
let mut error = Ok(());
$( $(
#[cfg(windows)] error = handle_command!($writer, $command);
{
if $crate::supports_ansi() {
if let Err(e) = write!($write, "{}", $command.ansi_code()) {
error = Some(Err($crate::ErrorKind::from(e)));
};
} else {
if let Err(e) = $command.execute_winapi() {
error = Some(Err($crate::ErrorKind::from(e)));
}
};
}
#[cfg(unix)]
{
if let Err(e) = write!($write, "{}", $command.ansi_code()) {
error = Some(Err($crate::ErrorKind::from(e)));
}
}
)* )*
if let Some(error) = error { error
error
} else {
Ok(())
}
}} }}
} }
/// Execute one or more command(s) /// Executes one or more command(s).
///
/// # Arguments
/// ///
/// # Parameters
/// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html) /// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html)
/// ///
/// Crossterm will write the ANSI escape codes to this given. (A flush will be done) /// ANSI escape codes are written on the given 'writer', after which they are flushed.
///
/// - [Command](./trait.Command.html) /// - [Command](./trait.Command.html)
/// ///
/// Give one or more commands that you want to execute /// One or more commands
///
/// # Examples
/// ///
/// # Example
/// ```rust /// ```rust
/// use std::io::Write; /// use std::io::{Write, stdout};
/// use crossterm::{execute, style::Print};
/// ///
/// use crossterm::{execute, Output}; /// fn main() {
/// // will be executed directly
/// execute!(stdout(), Print("sum:\n".to_string()));
/// ///
/// // will be executed directly /// // will be executed directly
/// execute!(std::io::stdout(), Output("foo".to_string())); /// execute!(stdout(), Print("1 + 1= ".to_string()), Print((1+1).to_string()));
/// ///
/// // will be executed directly /// // ==== Output ====
/// execute!(std::io::stdout(), Output("foo".to_string()), Output("bar".to_string())); /// // sum:
/// // 1 + 1 = 2
/// }
/// ``` /// ```
/// ///
/// # Remarks /// Have a look over at the [Command API](./#command-api) for more details.
/// - In the case of UNIX and windows 10, ANSI codes are written to the given 'writer'. ///
/// - In case of Windows versions lower than 10, a direct WinApi call will be made. /// # Notes
/// This is happening because Windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer. ///
/// Because of that there is no difference between `execute` and `queue` for those windows versions. /// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'.
/// * In case of Windows versions lower than 10, a direct WinApi call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// and can therefore not be written to the given `writer`.
/// Therefore, there is no difference between [execute](macro.execute.html)
/// and [queue](macro.queue.html) for those old Windows versions.
#[macro_export] #[macro_export]
macro_rules! execute { macro_rules! execute {
($write:expr, $($command:expr), * $(,)? ) => {{ ($write:expr, $($command:expr), * $(,)? ) => {{
// Silent warning when the macro is used inside the `command` module // Silent warning when the macro is used inside the `command` module
#[allow(unused_imports)] #[allow(unused_imports)]
use $crate::{Command, write_cout}; use $crate::{handle_command, Command};
let mut error = None;
#[allow(unused_assignments)]
let mut error = Ok(());
$( $(
#[cfg(windows)] if let Err(e) = handle_command!($write, $command) {
{ error = Err(e);
if $crate::supports_ansi() { }else {
if let Err(e) = write_cout!($write, $command.ansi_code()) { $write.flush().map_err($crate::ErrorKind::IoError).unwrap();
error = Some($crate::ErrorKind::from(e));
};
} else {
if let Err(e) = $command.execute_winapi() {
error = Some($crate::ErrorKind::from(e));
};
};
}
#[cfg(unix)]
{
if let Err(e) = write_cout!($write, $command.ansi_code()) {
error = Some($crate::ErrorKind::from(e));
}
} }
)* )*
if let Some(error) = error { error
Err(error)
} else {
Ok(())
}
}} }}
} }
@ -176,8 +186,7 @@ macro_rules! impl_display {
(for $($t:ty),+) => { (for $($t:ty),+) => {
$(impl ::std::fmt::Display for $t { $(impl ::std::fmt::Display for $t {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> {
use $crate::Command; $crate::queue!(f, self).map_err(|_| ::std::fmt::Error)
write!(f, "{}", self.ansi_code())
} }
})* })*
} }

View File

@ -1,5 +1,5 @@
#[cfg(windows)] #[cfg(windows)]
pub mod winapi; pub(crate) mod windows;
#[cfg(unix)] #[cfg(unix)]
pub mod unix; pub(crate) mod unix;

View File

@ -15,11 +15,11 @@ lazy_static! {
static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<Option<Termios>> = Mutex::new(None); static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<Option<Termios>> = Mutex::new(None);
} }
pub fn is_raw_mode_enabled() -> bool { pub(crate) fn is_raw_mode_enabled() -> bool {
TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some() TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some()
} }
pub fn wrap_with_result(result: i32) -> Result<bool> { pub(crate) fn wrap_with_result(result: i32) -> Result<bool> {
if result == -1 { if result == -1 {
Err(ErrorKind::IoError(io::Error::last_os_error())) Err(ErrorKind::IoError(io::Error::last_os_error()))
} else { } else {
@ -28,11 +28,11 @@ pub fn wrap_with_result(result: i32) -> Result<bool> {
} }
/// Transform the given mode into an raw mode (non-canonical) mode. /// Transform the given mode into an raw mode (non-canonical) mode.
pub fn raw_terminal_attr(termios: &mut Termios) { pub(crate) fn raw_terminal_attr(termios: &mut Termios) {
unsafe { cfmakeraw(termios) } unsafe { cfmakeraw(termios) }
} }
pub fn get_terminal_attr() -> Result<Termios> { pub(crate) fn get_terminal_attr() -> Result<Termios> {
unsafe { unsafe {
let mut termios = mem::zeroed(); let mut termios = mem::zeroed();
wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?; wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?;
@ -40,11 +40,11 @@ pub fn get_terminal_attr() -> Result<Termios> {
} }
} }
pub fn set_terminal_attr(termios: &Termios) -> Result<bool> { pub(crate) fn set_terminal_attr(termios: &Termios) -> Result<bool> {
wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) }) wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) })
} }
pub fn enable_raw_mode() -> Result<()> { pub(crate) fn enable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap();
if original_mode.is_some() { if original_mode.is_some() {
@ -63,7 +63,7 @@ pub fn enable_raw_mode() -> Result<()> {
Ok(()) Ok(())
} }
pub fn disable_raw_mode() -> Result<()> { pub(crate) fn disable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap();
if let Some(original_mode_ios) = original_mode.as_ref() { if let Some(original_mode_ios) = original_mode.as_ref() {

View File

@ -1,34 +0,0 @@
pub mod ansi {
use crossterm_winapi::ConsoleMode;
use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
use super::super::super::error::Result;
/// Toggle virtual terminal processing.
///
/// This method attempts to toggle virtual terminal processing for this
/// console. If there was a problem toggling it, then an error returned.
/// On success, the caller may assume that toggling it was successful.
///
/// When virtual terminal processing is enabled, characters emitted to the
/// console are parsed for VT100 and similar control character sequences
/// that control color and other similar operations.
pub fn set_virtual_terminal_processing(yes: bool) -> Result<()> {
let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
let console_mode = ConsoleMode::new()?;
let old_mode = console_mode.mode()?;
let new_mode = if yes {
old_mode | mask
} else {
old_mode & !mask
};
if old_mode != new_mode {
console_mode.set_mode(new_mode)?;
}
Ok(())
}
}

32
src/utils/sys/windows.rs Normal file
View File

@ -0,0 +1,32 @@
use crossterm_winapi::ConsoleMode;
use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
use crate::Result;
/// Toggle virtual terminal processing.
///
/// This method attempts to toggle virtual terminal processing for this
/// console. If there was a problem toggling it, then an error returned.
/// On success, the caller may assume that toggling it was successful.
///
/// When virtual terminal processing is enabled, characters emitted to the
/// console are parsed for VT100 and similar control character sequences
/// that control color and other similar operations.
pub(crate) fn set_virtual_terminal_processing(yes: bool) -> Result<()> {
let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
let console_mode = ConsoleMode::new()?;
let old_mode = console_mode.mode()?;
let new_mode = if yes {
old_mode | mask
} else {
old_mode & !mask
};
if old_mode != new_mode {
console_mode.set_mode(new_mode)?;
}
Ok(())
}