1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
use std::fmt;

macro_rules! escape_code {
    ($doc:expr, $name:ident, $value:expr) => {
        #[doc = $doc]
        pub struct $name;

        impl fmt::Display for $name {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                write!(f, $value)
            }
        }
    }
}

/// Set the absolute position of the cursor. x=0 y=0 is the top left of the screen.
pub enum CursorTo {
    TopLeft,
    AbsoluteX(u16),
    AbsoluteXY(u16, u16),
}

impl fmt::Display for CursorTo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CursorTo::TopLeft => write!(f, "\x1B[{};{}H", 1, 1),
            CursorTo::AbsoluteX(x) => write!(f, "\x1B[{}G", x + 1),
            CursorTo::AbsoluteXY(x, y) => write!(f, "\x1B[{};{}H", x + 1, y + 1),
        }
    }
}

/// Set the position of the cursor relative to its current position.
pub enum CursorMove {
    X(i16),
    XY(i16, i16),
    Y(i16),
}

impl fmt::Display for CursorMove {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CursorMove::X(x) if x > 0 => write!(f, "\x1B[{}C", x),
            CursorMove::X(x) if x < 0 => write!(f, "\x1B[{}D", -x),
            CursorMove::X(_) => std::result::Result::Ok(()),

            CursorMove::XY(x, y) => {
                try!(CursorMove::X(x).fmt(f));
                try!(CursorMove::Y(y).fmt(f));
                std::result::Result::Ok(())
            }

            CursorMove::Y(y) if y > 0 => write!(f, "\x1B[{}B", y),
            CursorMove::Y(y) if y < 0 => write!(f, "\x1B[{}A", -y),
            CursorMove::Y(_) => std::result::Result::Ok(()),
        }
    }
}


/// Move cursor up a specific amount of rows.
pub struct CursorUp(pub u16);

impl fmt::Display for CursorUp {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "\x1B[{}A", self.0)
    }
}

/// Move cursor down a specific amount of rows.
pub struct CursorDown(pub u16);

impl fmt::Display for CursorDown {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "\x1B[{}B", self.0)
    }
}

/// Move cursor forward a specific amount of rows.
pub struct CursorForward(pub u16);

impl fmt::Display for CursorForward {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "\x1B[{}C", self.0)
    }
}

/// Move cursor backward a specific amount of rows.
pub struct CursorBackward(pub u16);

impl fmt::Display for CursorBackward {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "\x1B[{}D", self.0)
    }
}

escape_code!("Move cursor to the left side.", CursorLeft, "\x1B[1000D");
escape_code!("Save cursor position.", CursorSavePosition, "\x1B[s");
escape_code!("Restore saved cursor position.", CursorRestorePosition, "\x1B[u");
escape_code!("Get cursor position.", CursorGetPosition, "\x1B[6n");
escape_code!("Move cursor to the next line.", CursorNextLine, "\x1B[E");
escape_code!("Move cursor to the previous line.", CursorPrevLine, "\x1B[F");
escape_code!("Hide cursor.", CursorHide, "\x1B[?25l");
escape_code!("Show cursor.", CursorShow, "\x1B[?25h");

/// Erase from the current cursor position up the specified amount of rows.
pub struct EraseLines(pub u16);

impl fmt::Display for EraseLines {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for idx in 0..self.0 {
            if idx > 0 {
                try!(write!(f, "{}", CursorUp(1)));
            }

            try!(write!(f, "{}", CursorLeft));
            try!(write!(f, "{}", EraseEndLine));
        }

        std::result::Result::Ok(())
    }
}

escape_code!("Erase from the current cursor position to the end of the current line.", EraseEndLine, "\x1B[K");
escape_code!("Erase from the current cursor position to the start of the current line.", EraseStartLine, "\x1B[1K");
escape_code!("Erase the entire current line.", EraseLine, "\x1B[2K");

escape_code!("Erase the screen from the current line down to the bottom of the screen.", EraseDown, "\x1B[J");
escape_code!("Erase the screen from the current line up to the top of the screen.", EraseUp, "\x1B[1J");
escape_code!("Erase the screen and move the cursor the top left position.", EraseScreen, "\x1B[2J");
escape_code!("Scroll display up one line.", ScrollUp, "\x1B[S");
escape_code!("Scroll display down one line.", ScrollDown, "\x1B[T");

escape_code!("Clear the terminal screen.", ClearScreen, "\u{001b}c");
escape_code!("Output a beeping sound.", Beep, "\u{0007}");

#[cfg(test)]
extern crate tempfile;

#[cfg(test)]
mod tests {
    use tempfile::tempfile;
    use std::fs::File;
    use std::io::{Write, Read, Seek, SeekFrom};

    macro_rules! assert_escape_output {
        ($name:ident, $code:expr, $expected:expr) => {
            #[test]
            fn $name() {
                let mut file: File = tempfile().unwrap();
                write!(file, "{}", $code).unwrap();

                file.seek(SeekFrom::Start(0)).unwrap();

                let mut buf = String::new();
                file.read_to_string(&mut buf).unwrap();
                assert_eq!($expected, buf);
            }
        }
    }

    assert_escape_output!(cursor_up_1, super::CursorUp(1), "\x1B[1A");
    assert_escape_output!(cursor_up_23, super::CursorUp(23), "\x1B[23A");

    assert_escape_output!(cursor_down_1, super::CursorDown(1), "\x1B[1B");
    assert_escape_output!(cursor_down_23, super::CursorDown(23), "\x1B[23B");

    assert_escape_output!(cursor_forward_1, super::CursorForward(1), "\x1B[1C");
    assert_escape_output!(cursor_forward_23, super::CursorForward(23), "\x1B[23C");

    assert_escape_output!(cursor_backward_1, super::CursorBackward(1), "\x1B[1D");
    assert_escape_output!(cursor_backward_23, super::CursorBackward(23), "\x1B[23D");

    assert_escape_output!(cursor_left, super::CursorLeft, "\x1B[1000D");
    assert_escape_output!(cursor_save_position, super::CursorSavePosition, "\x1B[s");
    assert_escape_output!(cursor_restore_position, super::CursorRestorePosition, "\x1B[u");
    assert_escape_output!(cursor_get_position, super::CursorGetPosition, "\x1B[6n");
    assert_escape_output!(cursor_next_line, super::CursorNextLine, "\x1B[E");
    assert_escape_output!(cursor_prev_line, super::CursorPrevLine, "\x1B[F");
    assert_escape_output!(cursor_hide, super::CursorHide, "\x1B[?25l");
    assert_escape_output!(cursor_show, super::CursorShow, "\x1B[?25h");

    assert_escape_output!(erase_lines_1, super::EraseLines(1), "\x1B[1000D\x1B[K");
    assert_escape_output!(erase_lines_2, super::EraseLines(2), "\x1B[1000D\x1B[K\x1B[1A\x1B[1000D\x1B[K");
    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");
}