blob: 1955214fc2c959172e2c3105126b78dbca3366e7 [file] [log] [blame]
/// Creates a control sequence.
///
/// This macro prepends provided sequence with the control sequence introducer `ESC [` (`\x1B[`).
///
/// # Examples
///
/// ```
/// use anes::csi;
///
/// assert_eq!(csi!("?1049h"), "\x1B[?1049h");
/// ```
#[macro_export]
macro_rules! csi {
($($arg:expr),*) => { concat!("\x1B[", $($arg),*) };
}
/// Creates an escape sequence.
///
/// This macro prepends provided sequence with the `ESC` (`\x1B`) character.
///
/// # Examples
///
/// ```
/// use anes::esc;
///
/// assert_eq!(esc!("7"), "\x1B7");
/// ```
#[macro_export]
macro_rules! esc {
($($arg:expr),*) => { concat!("\x1B", $($arg),*) };
}
/// Creates a select graphic rendition sequence.
///
/// This macro prepends provided sequence with the `ESC[` (`\x1B[`) character and appends `m` character.
///
/// Also known as Set Graphics Rendition on Linux.
///
/// # Examples
///
/// ```
/// use anes::sgr;
///
/// assert_eq!(sgr!("0"), "\x1B[0m");
/// ```
#[macro_export]
macro_rules! sgr {
($($arg:expr),*) => { concat!("\x1B[", $($arg),* , "m") };
}
/// Creates an ANSI sequence.
///
/// You can use this macro to create your own ANSI sequence. All `anes` sequences are
/// created with this macro.
///
/// # Examples
///
/// An unit struct:
///
/// ```
/// use anes::{esc, sequence};
///
/// sequence!(
/// /// Saves the cursor position.
/// struct SaveCursorPosition => esc!("7")
/// );
///
/// assert_eq!(&format!("{}", SaveCursorPosition), "\x1B7");
/// ```
///
/// An enum:
///
/// ```
/// use anes::{csi, sequence};
///
/// sequence!(
/// /// Clears part of the buffer.
/// enum ClearBuffer {
/// /// Clears from the cursor position to end of the screen.
/// Below => csi!("J"),
/// /// Clears from the cursor position to beginning of the screen.
/// Above => csi!("1J"),
/// /// Clears the entire buffer.
/// All => csi!("2J"),
/// /// Clears the entire buffer and all saved lines in the scrollback buffer.
/// SavedLines => csi!("3J"),
/// }
/// );
///
/// assert_eq!(&format!("{}", ClearBuffer::Below), "\x1B[J");
/// assert_eq!(&format!("{}", ClearBuffer::Above), "\x1B[1J");
/// assert_eq!(&format!("{}", ClearBuffer::All), "\x1B[2J");
/// assert_eq!(&format!("{}", ClearBuffer::SavedLines), "\x1B[3J");
/// ```
///
/// A struct:
///
/// ```
/// use anes::{csi, sequence};
///
/// sequence!(
/// /// Moves the cursor to the given location (column, row).
/// ///
/// /// # Notes
/// ///
/// /// Top/left cell is represented as `1, 1`.
/// struct MoveCursorTo(u16, u16) =>
/// |this, f| write!(f, csi!("{};{}H"), this.0, this.1)
/// );
///
/// assert_eq!(&format!("{}", MoveCursorTo(10, 5)), "\x1B[10;5H");
/// ```
#[macro_export]
macro_rules! sequence {
// Static unit struct
(
$(#[$meta:meta])*
struct $name:ident => $value:expr
) => {
$(#[$meta])*
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct $name;
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, $value)
}
}
};
// Static enum
(
$(#[$meta:meta])*
enum $name:ident {
$(
$(#[$variant_meta:meta])*
$variant:ident => $variant_value:expr
),*
$(,)?
}
) => {
$(#[$meta])*
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum $name {
$(
$(#[$variant_meta])*
$variant,
)*
}
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{}", match self {
$(
$name::$variant => $variant_value,
)*
})
}
}
};
// Dynamic struct
(
$(#[$meta:meta])*
struct $type:ident(
$($fields:ty),*
$(,)?
)
=>
$write:expr
) => {
$(#[$meta])*
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct $type($(pub $fields),*);
impl ::std::fmt::Display for $type {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
let write: &dyn Fn(&Self, &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result =
&$write;
write(self, f)
}
}
};
}
/// Queues ANSI escape sequence(s).
///
/// What does queue mean exactly? All sequences are queued with the
/// `write!($dst, "{}", $sequence)` macro without calling the
/// [`flush`](https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush) method.
///
/// Check the [`execute!`](macro.execute.html) macro if you'd like execute them
/// immediately (call the `flush` method after all sequences were queued).
///
/// # Examples
///
/// ```no_run
/// use std::io::{Result, Write};
///
/// use anes::queue;
///
/// fn main() -> Result<()> {
/// let mut stdout = std::io::stdout();
/// queue!(
/// &mut stdout,
/// anes::SaveCursorPosition,
/// anes::MoveCursorTo(10, 10)
/// )?;
///
/// queue!(&mut stdout, anes::RestoreCursorPosition,)?;
///
/// // ANSI sequences are not executed until you flush it!
/// stdout.flush()
/// }
/// ```
#[macro_export]
macro_rules! queue {
($dst:expr, $($sequence:expr),* $(,)?) => {{
let mut error = None;
$(
if let Err(e) = write!($dst, "{}", $sequence) {
error = Some(e);
}
)*
if let Some(error) = error {
Err(error)
} else {
Ok(())
}
}}
}
/// Executes ANSI escape sequence(s).
///
/// What does execute mean exactly? All sequences are queued with the
/// `write!($dst, "{}", $sequence)` macro and then the
/// [`flush`](https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush) method
/// is called.
///
/// Check the [`queue!`](macro.queue.html) macro if you'd like queue sequences
/// and execute them later.
///
/// ```no_run
/// use std::io::{Result, Write};
///
/// use anes::execute;
///
/// fn main() -> Result<()> {
/// let mut stdout = std::io::stdout();
/// execute!(
/// &mut stdout,
/// anes::SaveCursorPosition,
/// anes::MoveCursorTo(10, 10),
/// anes::RestoreCursorPosition
/// )?;
/// Ok(())
/// }
/// ```
#[macro_export]
macro_rules! execute {
($dst:expr, $($sequence:expr),* $(,)?) => {{
if let Err(e) = $crate::queue!($dst, $($sequence),*) {
Err(e)
} else {
$dst.flush()
}
}}
}
#[cfg(test)]
macro_rules! test_sequences {
(
$(
$name:ident(
$($left:expr => $right:expr),*
$(,)?
)
),*
$(,)?
) => {
#[cfg(test)]
mod tests {
use super::*;
$(
#[test]
fn $name() {
$(
assert_eq!(&format!("{}", $left), $right);
)*
}
)*
}
}
}
#[cfg(test)]
mod tests {
use std::io::{Error, ErrorKind, Write};
#[test]
fn csi() {
assert_eq!(csi!("foo"), "\x1B[foo");
}
#[test]
fn esc() {
assert_eq!(esc!("bar"), "\x1Bbar");
}
#[test]
fn sgr() {
assert_eq!(sgr!("bar"), "\x1B[barm");
}
#[test]
fn static_struct_sequence() {
sequence!(
struct TestSeq => csi!("foo")
);
assert_eq!(&format!("{}", TestSeq), "\x1B[foo");
}
#[test]
fn static_enum_sequence() {
sequence!(
enum TestSeq {
Foo => csi!("foo"),
Bar => esc!("bar"),
}
);
assert_eq!(&format!("{}", TestSeq::Foo), "\x1B[foo");
assert_eq!(&format!("{}", TestSeq::Bar), "\x1Bbar");
}
#[test]
fn dynamic_struct_sequence() {
sequence!(
struct TestSeq(u16) =>
|this, f| write!(f, csi!("foo{}bar"), this.0)
);
assert_eq!(&format!("{}", TestSeq(10)), "\x1B[foo10bar");
}
#[test]
fn queue_allows_trailing_comma() {
let mut writer = Writer::default();
assert!(queue!(&mut writer, "foo",).is_ok());
assert_eq!(&writer.buffer, "foo");
}
#[test]
fn queue_writes_single_sequence() {
let mut writer = Writer::default();
assert!(queue!(&mut writer, "foo").is_ok());
assert_eq!(&writer.buffer, "foo");
}
#[test]
fn queue_writes_multiple_sequences() {
let mut writer = Writer::default();
assert!(queue!(&mut writer, "foo", "bar", "baz").is_ok());
assert_eq!(&writer.buffer, "foobarbaz");
}
#[test]
fn queue_does_not_flush() {
let mut writer = Writer::default();
assert!(queue!(&mut writer, "foo").is_ok());
assert!(!writer.flushed);
assert!(writer.flushed_buffer.is_empty());
}
#[test]
fn execute_allows_trailing_comma() {
let mut writer = Writer::default();
assert!(execute!(&mut writer, "foo",).is_ok());
assert_eq!(&writer.flushed_buffer, "foo");
}
#[test]
fn execute_writes_single_sequence() {
let mut writer = Writer::default();
assert!(execute!(&mut writer, "foo").is_ok());
assert_eq!(&writer.flushed_buffer, "foo");
}
#[test]
fn execute_writes_multiple_sequences() {
let mut writer = Writer::default();
assert!(execute!(&mut writer, "foo", "bar", "baz").is_ok());
assert_eq!(&writer.flushed_buffer, "foobarbaz");
}
#[test]
fn execute_does_flush() {
let mut writer = Writer::default();
assert!(execute!(&mut writer, "foo").is_ok());
assert!(writer.flushed);
assert_eq!(&writer.flushed_buffer, "foo");
assert!(writer.buffer.is_empty());
}
#[derive(Default)]
struct Writer {
buffer: String,
flushed_buffer: String,
flushed: bool,
}
impl Write for Writer {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
let s = std::str::from_utf8(buf).map_err(|_| ErrorKind::InvalidData)?;
self.buffer.push_str(s);
Ok(s.len())
}
fn flush(&mut self) -> Result<(), Error> {
self.flushed_buffer = self.buffer.clone();
self.buffer = String::new();
self.flushed = true;
Ok(())
}
}
}