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
//! Contains hide_cursor(), show_cursor, get_cursor_position() and
//! set_cursor_position() for dealing with the cursor and its position in a
//! console window.


use std::io::{stdin, stdout, Read, Write};
use crossterm::{
    ExecutableCommand,
    cursor,
    terminal,
};


/// Hide or turn off the cursor that is normally always visible on the console.
///
/// We could use an ANSI sequence for this (ESC[?25l), but the sequence is
/// private and not necessarily supported everywhere.  The crossterm crate
/// makes this simple and hides any system differences.
pub fn hide_cursor() {
    let mut stdout = stdout();
    stdout.execute(cursor::Hide).unwrap();
}

/// Show or turn on the cursor so it is in the normal visible state.
///
/// We could use an ANSI sequence for this (ESC[?25h), but the sequence is
/// private and not necessarily supported everywhere.  The crossterm crate
/// makes this simple and hides any system differences.
pub fn show_cursor() {
    let mut stdout = stdout();
    stdout.execute(cursor::Show).unwrap();
}

/// Retrieve the current cursor position in the console window.
///
/// This uses an ANSI string sequence to query the cursor position as the
/// crossterm crate does not provide the ability to get the cursor position.
///
/// # Returns
/// Returns a tuple (x, y) containing the x and y position of the cursor
/// position in the console window, relative to the upper left corner of the
/// window.  Position starts at 1,1.
pub fn get_cursor_position() -> (u16, u16) {
    let mut position_x = 0;
    let mut position_y = 0;

    terminal::enable_raw_mode().unwrap();
    print!("\x1b[6n"); // Make sure we don't add a newline
    let mut stdout = stdout();
    stdout.flush().unwrap(); // No newline so we must flush to activate the sequence.
    let mut stdin = stdin();
    let mut buffer = vec!(0u8;16);

    if let Ok(n) = stdin.read(&mut buffer) {
        // Expecting ESC [ <r> ; <c> R (no spaces)
        if n > 2 && buffer[0] == 0x1b && buffer[1] == b'[' {
            if let Ok(s) = String::from_utf8(buffer) {
                let items : Vec<&str> = s.split(&['[', ';', 'R']).collect();
                if items.len() >= 3 {
                    let row_as_string = items[1];
                    let col_as_string = items[2];
                    position_y = row_as_string.parse().unwrap();
                    position_x = col_as_string.parse().unwrap();
                }
            }
        }
    }
    terminal::disable_raw_mode().unwrap();

    (position_x, position_y)
}

/// Move the text cursor to the specified screen coordinates in the console
/// window.
///
/// # Parameters
/// - position_x
///
///   Column index from left, starting at 1, in characters.
/// - position_y
///
///   Row index from top, starting at 1, in characters
pub fn set_cursor_position(position_x: u16, position_y: u16) {
    print!("\x1b[{position_y};{position_x}H");
    let mut stdout = stdout();
    stdout.flush().unwrap();
}