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
187
188
189
190
191
192
193
194
195
196
197
198
199
//! Provides a low-level terminal interface

use std::io;
use std::time::Duration;

use sys;

/// Terminal cursor mode
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CursorMode {
    /// Normal mode
    Normal,
    /// Overwrite mode
    Overwrite,
}

/// Signal caught by a `Terminal`
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Signal {
    /// Break signal (`SIGBREAK`); Windows only
    Break,
    /// Interrupt signal (`SIGINT`)
    Interrupt,
    /// Suspend signal (`SIGTSTP`); Unix only
    Suspend,
    /// Quit signal (`SIGQUIT`); Unix only
    Quit,
}

/// Contains a set of signals
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SignalSet(u32);

impl SignalSet {
    /// Returns an empty `SignalSet`.
    pub fn new() -> SignalSet {
        SignalSet(0)
    }

    /// Returns whether the given `Signal` is contained in the set.
    pub fn contains(&self, signal: Signal) -> bool {
        self.0 & signal.as_bit() != 0
    }

    /// Inserts the given `Signal` into the set.
    pub fn insert(&mut self, signal: Signal) {
        self.0 |= signal.as_bit();
    }

    /// Removes the given `Signal` from the set.
    pub fn remove(&mut self, signal: Signal) {
        self.0 &= !signal.as_bit();
    }

    /// Returns the intersection of the two sets.
    pub fn intersection(&self, other: &SignalSet) -> SignalSet {
        SignalSet(self.0 & other.0)
    }

    /// Returns the union of the two sets.
    pub fn union(&self, other: &SignalSet) -> SignalSet {
        SignalSet(self.0 | other.0)
    }
}

impl Default for SignalSet {
    fn default() -> SignalSet {
        SignalSet::new()
    }
}

impl Signal {
    fn as_bit(&self) -> u32 {
        1 << (*self as u32)
    }
}

/// Represents the size of a terminal window
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Size {
    /// Number of lines in the terminal
    pub lines: usize,
    /// Number of columns in the terminal
    pub columns: usize,
}

/// Type alias for the platform-dependent default `Terminal` interface
pub type DefaultTerminal = sys::Terminal;

/// Defines a low-level interface to the terminal
pub trait Terminal: Sized {
    /// Returned by `prepare` and `read_signals`.
    /// When dropped, the prior terminal state will be restored.
    type PrepareGuard;

    /// Initialize the terminal interface
    fn new() -> io::Result<Self>;

    /// Returns the character that indicates end-of-file
    fn eof_char(&self) -> char;
    /// Returns the character that indicates literal quoting sequence
    fn literal_char(&self) -> char;
    /// Returns the character that indicates backward character erase
    fn erase_char(&self) -> char;
    /// Returns the character that indicates backward word erase
    fn word_erase_char(&self) -> char;
    /// Returns the character that indicates backward kill line
    fn kill_char(&self) -> char;

    /// Returns the key sequence that indicates forward delete character
    fn delete_seq(&self) -> &str;
    /// Returns the key sequence that indicates switching to insert mode
    fn insert_seq(&self) -> &str;

    /// Returns the name of the terminal, if one has been supplied
    fn name(&self) -> Option<&str>;

    /// Returns the size of the terminal window
    fn size(&self) -> io::Result<Size>;

    /// Presents a clear terminal screen, with cursor at first row, first column.
    ///
    /// If the terminal possesses a scrolling window over a buffer, this shall
    /// have the effect of moving the visible window down such that it shows
    /// an empty view of the buffer, preserving some or all of existing buffer
    /// contents, where possible.
    fn clear_screen(&self) -> io::Result<()>;

    /// Clears characters on the line occupied by the cursor, beginning with the
    /// cursor and ending at the end of the line. Also clears all characters on
    /// all lines after the cursor.
    fn clear_to_screen_end(&self) -> io::Result<()>;

    /// Moves the cursor up `n` cells; `n` may be zero.
    fn move_up(&self, n: usize) -> io::Result<()>;
    /// Moves the cursor down `n` cells; `n` may be zero.
    fn move_down(&self, n: usize) -> io::Result<()>;
    /// Moves the cursor left `n` cells; `n` may be zero.
    fn move_left(&self, n: usize) -> io::Result<()>;
    /// Moves the cursor right `n` cells; `n` may be zero.
    fn move_right(&self, n: usize) -> io::Result<()>;

    /// Moves the cursor to the first column of the current line
    fn move_to_first_col(&self) -> io::Result<()>;

    /// Set the current cursor mode
    fn set_cursor_mode(&self, mode: CursorMode) -> io::Result<()>;

    /// Waits `timeout` for user input. If `timeout` is `None`, waits indefinitely.
    ///
    /// Returns `Ok(true)` if input becomes available within the given timeout
    /// or if a signal is received.
    ///
    /// Returns `Ok(false)` if the timeout expires before input becomes available.
    fn wait_for_input(&self, timeout: Option<Duration>) -> io::Result<bool>;

    /// Prepares the terminal for line reading and editing operations.
    ///
    /// When the returned value is dropped, the terminal will be restored to its
    /// state prior to calling `prepare`.
    ///
    /// If `catch_signals` is `true`, signal handlers will be registered.
    /// These are also restored when the guard value is dropped.
    ///
    /// The set of signals caught should include those contained in
    /// `report_signals`.
    fn prepare(&self, catch_signals: bool, report_signals: SignalSet)
        -> io::Result<Self::PrepareGuard>;

    /// If the process received a signal since the last call to `take_signal`,
    /// return it. Otherwise, return `None`.
    fn get_signal(&self) -> Option<Signal>;

    /// If the process received a signal since the last call to `take_signal`,
    /// consume and return it. Otherwise, return `None`.
    fn take_signal(&self) -> Option<Signal>;

    /// Configures the terminal to interpret signal-inducing characters
    /// as input without raising a signal.
    ///
    /// When the returned value is dropped, the terminal will be restored to its
    /// state prior to calling `read_signals`.
    fn read_signals(&self) -> io::Result<Self::PrepareGuard>;

    /// Reads some input from the terminal and appends it to the given buffer.
    ///
    /// Returns the number of bytes read. `Ok(0)` may be returned to indicate
    /// that no bytes can be read at this time.
    fn read(&self, buf: &mut Vec<u8>) -> io::Result<usize>;

    /// Writes output to the terminal and immediately flushes it to the device.
    ///
    /// For each newline `'\n'` written to the terminal, the cursor should
    /// be moved to the first column of the following line.
    ///
    /// The terminal interface shall not automatically move the cursor to the next
    /// line when `write` causes a character to be written to the final column.
    fn write(&self, s: &str) -> io::Result<()>;
}