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
//! # Intro
//! `term_cursor` is a crate for handling terminal cursor movement in a platform independent way.
//! The supported platforms are:
//!
//! - `Windows`
//! - `Linux`
//! - `macOS`
//! - `FreeBSD`
//! - `OpenBSD`
//! 
//! Throughout this crate X and Y are used to denote the coordinates of the cursor.
//! X corresponds to columns and Y corresponds to rows.
//! All tuples like `(i32, i32)` are interpreted as `(X, Y)`.
//!
//! # API
//! This crate provides 2 APIs that can be used to achieve the same effects:
//!
//! - A functions based approach that provides very simple functions to directly interact with the terminal (see the functions `set_pos`, `get_pos` and `clear`).
//! - A newtype pattern based approach that provies a bunch of types which all implement `std::fmt::Display` (see the structs section below).
//!   These types call the `set_pos`, `get_pos` and `clear` functions internally, when they get formatted.
//!
//! # Watch out!
//! - Both APIs **always** operate on the "default" terminal that is bound to the process.
//!   In other words, on Windows `GetStdHandle(STD_OUTPUT_HANDLE)` is used, and on *NIX, the ANSI terminal communcation is done through `stdout` / `stdin`.
//! - Drawing outside the boundaries of the buffer / terminal is **undefined behaviour**.

use std::fmt::{Display, Formatter, Result as FmtResult, Error as FmtError};

use std::io;

mod platform;

/// The error generated by operations on the terminal.
#[derive(Debug)]
pub enum Error {
    /// A generic io error.
    Io(io::Error),
    /// An opaque, platform-implementation-specific error.
    PlatformSpecific,
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self {
        Error::Io(err)
    }
}

impl From<Error> for FmtError {
    fn from(_: Error) -> Self {
        FmtError
    }
}

/// A type that when `Display`ed, moves the cursor to the specified coordinates.
#[derive(Clone, Copy)]
pub struct Goto(pub i32, pub i32);

impl Display for Goto {
    fn fmt(&self, _fmt: &mut Formatter) -> FmtResult {
        let Goto(x, y) = *self;
        platform::set_cursor_pos(x, y)?;
        Ok(())
    }
}

/// A type that when `Display`ed, moves the cursor by the specified amounts.
///
/// This moves the cursor to the specified coordinates, relative to it's previous position.
#[derive(Clone, Copy)]
pub struct Relative(pub i32, pub i32);

impl Display for Relative {
    fn fmt(&self, _fmt: &mut Formatter) -> FmtResult {
        let (cur_x, cur_y) = platform::get_cursor_pos()?;
        let Relative(x, y) = *self;
        let (x, y) = (x + cur_x, y + cur_y);
        platform::set_cursor_pos(x, y)?;
        Ok(())
    }
}

/// A type that when `Display`ed, moves the cursor left by the specified amount.
///
/// This is identical to a `Relative(-val, 0)`.
#[derive(Clone, Copy)]
pub struct Left(pub i32);

impl Display for Left {
    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
        Relative(-self.0, 0).fmt(fmt)
    }
}

/// A type that when `Display`ed, moves the cursor right by the specified amount.
///
/// This is identical to a `Relative(val, 0)`.
#[derive(Clone, Copy)]
pub struct Right(pub i32);

impl Display for Right {
    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
        Relative(self.0, 0).fmt(fmt)
    }
}

/// A type that when `Display`ed, moves the cursor up by the specified amount.
///
/// This is identical to a `Relative(0, -val)`.
#[derive(Clone, Copy)]
pub struct Up(pub i32);

impl Display for Up {
    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
        Relative(0, -self.0).fmt(fmt)
    }
}

/// A type that when `Display`ed, moves the cursor down by the specified amount.
///
/// This is identical to a `Relative(0, val)`.
#[derive(Clone, Copy)]
pub struct Down(pub i32);

impl Display for Down {
    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
        Relative(0, self.0).fmt(fmt)
    }
}

/// A type that when `Display`ed, clears the entire terminal screen.
///
/// In effect, this sets every terminal cell to a space `' '`.
#[derive(Clone, Copy)]
pub struct Clear;

impl Display for Clear {
    fn fmt(&self, _fmt: &mut Formatter) -> FmtResult {
        platform::clear()?;
        Ok(())
    }
}

/// Set the cursor position to the specified coordinates.
pub fn set_pos(x: i32, y: i32) -> Result<(), Error> {
    platform::set_cursor_pos(x, y)
}

/// Get the current cursor position.
/// 
/// The tuple returned contains the (x, y) coordinates of the cursor position.
pub fn get_pos() -> Result<(i32, i32), Error> {
    platform::get_cursor_pos()
}

/// Clear the screen, i.e. setting every character in the terminal to a space `' '`.
pub fn clear() -> Result<(), Error> {
    platform::clear()
}