ansi_escapes/
lib.rs

1#![no_std]
2
3use core::fmt;
4
5macro_rules! escape_code {
6    ($doc:expr, $name:ident, $value:expr) => {
7        #[doc = $doc]
8        pub struct $name;
9
10        impl fmt::Display for $name {
11            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12                write!(f, $value)
13            }
14        }
15    };
16}
17
18/// Set the absolute position of the cursor. x=0 y=0 is the top left of the screen.
19pub enum CursorTo {
20    TopLeft,
21    AbsoluteX(u16),
22    AbsoluteXY(u16, u16),
23}
24
25impl fmt::Display for CursorTo {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        match *self {
28            CursorTo::TopLeft => write!(f, "\x1B[{};{}H", 1, 1),
29            CursorTo::AbsoluteX(x) => write!(f, "\x1B[{}G", x + 1),
30            CursorTo::AbsoluteXY(x, y) => write!(f, "\x1B[{};{}H", y + 1, x + 1),
31        }
32    }
33}
34
35/// Set the position of the cursor relative to its current position.
36pub enum CursorMove {
37    X(i16),
38    XY(i16, i16),
39    Y(i16),
40}
41
42impl fmt::Display for CursorMove {
43    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44        match *self {
45            CursorMove::X(x) if x > 0 => write!(f, "\x1B[{}C", x),
46            CursorMove::X(x) if x < 0 => write!(f, "\x1B[{}D", -x),
47            CursorMove::X(_) => fmt::Result::Ok(()),
48
49            CursorMove::XY(x, y) => {
50                CursorMove::X(x).fmt(f)?;
51                CursorMove::Y(y).fmt(f)?;
52                fmt::Result::Ok(())
53            }
54
55            CursorMove::Y(y) if y > 0 => write!(f, "\x1B[{}B", y),
56            CursorMove::Y(y) if y < 0 => write!(f, "\x1B[{}A", -y),
57            CursorMove::Y(_) => fmt::Result::Ok(()),
58        }
59    }
60}
61
62/// Move cursor up a specific amount of rows.
63pub struct CursorUp(pub u16);
64
65impl fmt::Display for CursorUp {
66    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67        write!(f, "\x1B[{}A", self.0)
68    }
69}
70
71/// Move cursor down a specific amount of rows.
72pub struct CursorDown(pub u16);
73
74impl fmt::Display for CursorDown {
75    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76        write!(f, "\x1B[{}B", self.0)
77    }
78}
79
80/// Move cursor forward a specific amount of rows.
81pub struct CursorForward(pub u16);
82
83impl fmt::Display for CursorForward {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        write!(f, "\x1B[{}C", self.0)
86    }
87}
88
89/// Move cursor backward a specific amount of rows.
90pub struct CursorBackward(pub u16);
91
92impl fmt::Display for CursorBackward {
93    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94        write!(f, "\x1B[{}D", self.0)
95    }
96}
97
98escape_code!("Move cursor to the left side.", CursorLeft, "\x1B[1000D");
99escape_code!("Save cursor position.", CursorSavePosition, "\x1B[s");
100escape_code!("Restore saved cursor position.", CursorRestorePosition, "\x1B[u");
101escape_code!("Get cursor position.", CursorGetPosition, "\x1B[6n");
102escape_code!("Move cursor to the next line.", CursorNextLine, "\x1B[E");
103escape_code!("Move cursor to the previous line.", CursorPrevLine, "\x1B[F");
104escape_code!("Hide cursor.", CursorHide, "\x1B[?25l");
105escape_code!("Show cursor.", CursorShow, "\x1B[?25h");
106
107/// Erase from the current cursor position up the specified amount of rows.
108pub struct EraseLines(pub u16);
109
110impl fmt::Display for EraseLines {
111    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112        for idx in 0..self.0 {
113            if idx > 0 {
114                write!(f, "{}", CursorUp(1))?;
115            }
116
117            write!(f, "{}", CursorLeft)?;
118            write!(f, "{}", EraseEndLine)?;
119        }
120
121        fmt::Result::Ok(())
122    }
123}
124
125escape_code!("Erase from the current cursor position to the end of the current line.", EraseEndLine, "\x1B[K");
126escape_code!("Erase from the current cursor position to the start of the current line.", EraseStartLine, "\x1B[1K");
127escape_code!("Erase the entire current line.", EraseLine, "\x1B[2K");
128
129escape_code!("Erase the screen from the current line down to the bottom of the screen.", EraseDown, "\x1B[J");
130escape_code!("Erase the screen from the current line up to the top of the screen.", EraseUp, "\x1B[1J");
131escape_code!("Erase the screen and move the cursor the top left position.", EraseScreen, "\x1B[2J");
132escape_code!("Scroll display up one line.", ScrollUp, "\x1B[S");
133escape_code!("Scroll display down one line.", ScrollDown, "\x1B[T");
134
135escape_code!("Clear the terminal screen.", ClearScreen, "\u{001b}c");
136escape_code!("Enter the [alternative screen](https://terminalguide.namepad.de/mode/p47/).", EnterAlternativeScreen, "\x1B[?1049h");
137escape_code!("Exit the [alternative screen](https://terminalguide.namepad.de/mode/p47/).", ExitAlternativeScreen, "\x1B[?1049l");
138escape_code!("Output a beeping sound.", Beep, "\u{0007}");
139
140#[cfg(test)]
141extern crate std;
142
143#[cfg(test)]
144mod tests {
145    use std::{io::Write, string::String, vec::Vec};
146
147    macro_rules! assert_escape_output {
148        ($name:ident, $code:expr, $expected:expr) => {
149            #[test]
150            fn $name() {
151                let mut buf = Vec::new();
152                write!(buf, "{}", $code).unwrap();
153
154                let result = String::from_utf8(buf).unwrap();
155                assert_eq!(result, $expected);
156            }
157        };
158    }
159
160    assert_escape_output!(cursor_up_1, super::CursorUp(1), "\x1B[1A");
161    assert_escape_output!(cursor_up_23, super::CursorUp(23), "\x1B[23A");
162
163    assert_escape_output!(cursor_down_1, super::CursorDown(1), "\x1B[1B");
164    assert_escape_output!(cursor_down_23, super::CursorDown(23), "\x1B[23B");
165
166    assert_escape_output!(cursor_forward_1, super::CursorForward(1), "\x1B[1C");
167    assert_escape_output!(cursor_forward_23, super::CursorForward(23), "\x1B[23C");
168
169    assert_escape_output!(cursor_backward_1, super::CursorBackward(1), "\x1B[1D");
170    assert_escape_output!(cursor_backward_23, super::CursorBackward(23), "\x1B[23D");
171
172    assert_escape_output!(cursor_left, super::CursorLeft, "\x1B[1000D");
173    assert_escape_output!(cursor_save_position, super::CursorSavePosition, "\x1B[s");
174    assert_escape_output!(cursor_restore_position, super::CursorRestorePosition, "\x1B[u");
175    assert_escape_output!(cursor_get_position, super::CursorGetPosition, "\x1B[6n");
176    assert_escape_output!(cursor_next_line, super::CursorNextLine, "\x1B[E");
177    assert_escape_output!(cursor_prev_line, super::CursorPrevLine, "\x1B[F");
178    assert_escape_output!(cursor_hide, super::CursorHide, "\x1B[?25l");
179    assert_escape_output!(cursor_show, super::CursorShow, "\x1B[?25h");
180
181    assert_escape_output!(erase_lines_1, super::EraseLines(1), "\x1B[1000D\x1B[K");
182    assert_escape_output!(erase_lines_2, super::EraseLines(2), "\x1B[1000D\x1B[K\x1B[1A\x1B[1000D\x1B[K");
183    assert_escape_output!(erase_lines_3, super::EraseLines(3), "\x1B[1000D\x1B[K\x1B[1A\x1B[1000D\x1B[K\x1B[1A\x1B[1000D\x1B[K");
184}