termion/src/input.rs

237 lines
7.3 KiB
Rust
Raw Normal View History

2016-03-15 19:32:25 +00:00
use std::io::{self, Read, Write};
use IntoRawMode;
/// A key.
2016-06-14 13:24:07 +01:00
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Key {
/// Backspace.
Backspace,
/// Left arrow.
Left,
/// Right arrow.
Right,
/// Up arrow.
Up,
/// Down arrow.
Down,
/// Home key.
Home,
/// End key.
End,
/// Page Up key.
PageUp,
/// Page Down key.
PageDown,
/// Delete key.
Delete,
/// Insert key.
Insert,
/// Function keys.
F(u8),
/// Normal character.
Char(char),
2016-03-10 14:12:59 +00:00
/// Alt modified character.
Alt(char),
/// Ctrl modified character.
///
/// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals.
Ctrl(char),
/// Invalid character code.
Invalid,
2016-03-15 17:01:33 +00:00
/// Null byte.
Null,
2016-03-13 10:55:24 +00:00
2016-03-13 10:55:24 +00:00
#[allow(missing_docs)]
#[doc(hidden)]
__IsNotComplete
}
/// An iterator over input keys.
#[cfg(feature = "nightly")]
2016-03-09 10:38:43 +00:00
pub struct Keys<I> {
chars: I,
}
#[cfg(feature = "nightly")]
2016-06-14 13:24:07 +01:00
impl<I: Iterator<Item = Result<char, io::CharsError>>> Iterator for Keys<I> {
type Item = Result<Key, io::CharsError>;
2016-06-14 13:24:07 +01:00
fn next(&mut self) -> Option<Result<Key, io::CharsError>> {
Some(match self.chars.next() {
Some(Ok('\x1B')) => Ok(match self.chars.next() {
Some(Ok('O')) => match self.chars.next() {
Some(Ok('P')) => Key::F(1),
Some(Ok('Q')) => Key::F(2),
Some(Ok('R')) => Key::F(3),
Some(Ok('S')) => Key::F(4),
_ => Key::Invalid,
},
Some(Ok('[')) => match self.chars.next() {
Some(Ok('D')) => Key::Left,
Some(Ok('C')) => Key::Right,
Some(Ok('A')) => Key::Up,
Some(Ok('B')) => Key::Down,
Some(Ok('H')) => Key::Home,
Some(Ok('F')) => Key::End,
Some(Ok(c @ '1' ... '6')) => match self.chars.next() {
Some(Ok('~')) => match c {
2016-07-15 11:19:02 +01:00
'1' | '7' => Key::Home,
'2' | '8' => Key::Insert,
'3' => Key::Delete,
'4' => Key::End,
'5' => Key::PageUp,
'6' => Key::PageDown,
_ => Key::Invalid,
},
Some(Ok(k @ '0' ... '9')) => match self.chars.next() {
Some(Ok('~')) => match (c, k) {
('1', '1') => Key::F(1),
('1', '2') => Key::F(2),
('1', '3') => Key::F(3),
('1', '4') => Key::F(4),
('1', '5') => Key::F(5),
('1', '7') => Key::F(6),
('1', '8') => Key::F(7),
('1', '9') => Key::F(8),
('2', '0') => Key::F(9),
('2', '1') => Key::F(10),
('2', '3') => Key::F(11),
('2', '4') => Key::F(12),
_ => Key::Invalid,
},
_ => Key::Invalid,
},
_ => Key::Invalid,
},
_ => Key::Invalid,
},
Some(Ok(c)) => Key::Alt(c),
Some(Err(_)) | None => Key::Invalid,
}),
2016-06-14 13:24:07 +01:00
Some(Ok('\n')) | Some(Ok('\r')) => Ok(Key::Char('\n')),
Some(Ok('\t')) => Ok(Key::Char('\t')),
Some(Ok('\x7F')) => Ok(Key::Backspace),
Some(Ok(c @ '\x01' ... '\x1A')) => Ok(Key::Ctrl((c as u8 - 0x1 + b'a') as char)),
Some(Ok(c @ '\x1C' ... '\x1F')) => Ok(Key::Ctrl((c as u8 - 0x1C + b'4') as char)),
Some(Ok('\0')) => Ok(Key::Null),
Some(Ok(c)) => Ok(Key::Char(c)),
Some(Err(e)) => Err(e),
None => return None,
})
}
}
/// Extension to `Read` trait.
2016-03-08 20:39:24 +00:00
pub trait TermRead {
/// An iterator over key inputs.
#[cfg(feature = "nightly")]
2016-06-14 13:24:07 +01:00
fn keys(self) -> Keys<io::Chars<Self>> where Self: Sized;
/// Read a line.
///
/// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
/// complete the input.
fn read_line(&mut self) -> io::Result<Option<String>>;
/// Read a password.
2016-03-08 09:08:50 +00:00
///
/// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
/// complete the input.
fn read_passwd<W: Write>(&mut self, writer: &mut W) -> io::Result<Option<String>> {
let _raw = try!(writer.into_raw_mode());
self.read_line()
}
}
2016-03-08 20:39:24 +00:00
impl<R: Read> TermRead for R {
#[cfg(feature = "nightly")]
2016-06-14 13:24:07 +01:00
fn keys(self) -> Keys<io::Chars<R>> {
Keys {
chars: self.chars(),
}
}
fn read_line(&mut self) -> io::Result<Option<String>> {
let mut buf = Vec::with_capacity(30);
for c in self.bytes() {
2016-03-08 07:51:34 +00:00
match c {
2016-03-13 10:55:24 +00:00
Err(e) => return Err(e),
Ok(0) | Ok(3) | Ok(4) => return Ok(None),
2016-04-02 18:20:47 +01:00
Ok(0x7f) => { buf.pop(); },
Ok(b'\n') | Ok(b'\r') => break,
Ok(c) => buf.push(c),
}
}
let string = try!(String::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)));
Ok(Some(string))
}
}
2016-03-09 16:18:31 +00:00
#[cfg(test)]
mod test {
2016-04-02 17:05:31 +01:00
use super::*;
use std::io;
2016-03-09 16:18:31 +00:00
#[cfg(feature = "nightly")]
#[test]
fn test_keys() {
let mut i = b"\x1Bayo\x7F\x1B[D".keys();
2016-06-14 13:24:07 +01:00
assert_eq!(i.next().unwrap().unwrap(), Key::Alt('a'));
assert_eq!(i.next().unwrap().unwrap(), Key::Char('y'));
assert_eq!(i.next().unwrap().unwrap(), Key::Char('o'));
assert_eq!(i.next().unwrap().unwrap(), Key::Backspace);
assert_eq!(i.next().unwrap().unwrap(), Key::Left);
assert!(i.next().is_none());
2016-03-09 16:18:31 +00:00
}
2016-04-02 17:05:31 +01:00
2016-04-02 18:20:47 +01:00
fn line_match(a: &str, b: Option<&str>) {
let mut sink = io::sink();
let line = a.as_bytes().read_line().unwrap();
let pass = a.as_bytes().read_passwd(&mut sink).unwrap();
// godammit rustc
assert_eq!(line, pass);
if let Some(l) = line {
assert_eq!(Some(l.as_str()), b);
} else {
assert!(b.is_none());
}
}
2016-04-02 17:05:31 +01:00
#[test]
2016-04-02 18:20:47 +01:00
fn test_read() {
2016-04-02 17:05:31 +01:00
let test1 = "this is the first test";
let test2 = "this is the second test";
2016-04-02 18:20:47 +01:00
line_match(test1, Some(test1));
line_match(test2, Some(test2));
2016-04-02 17:05:31 +01:00
}
#[test]
2016-04-02 18:20:47 +01:00
fn test_backspace() {
line_match("this is the\x7f first\x7f\x7f test", Some("this is th fir test"));
line_match("this is the seco\x7fnd test\x7f", Some("this is the secnd tes"));
}
2016-04-02 17:05:31 +01:00
2016-04-02 18:20:47 +01:00
#[test]
fn test_end() {
line_match("abc\nhttps://www.youtube.com/watch?v=dQw4w9WgXcQ", Some("abc"));
line_match("hello\rhttps://www.youtube.com/watch?v=yPYZpwSpKmA", Some("hello"));
}
#[test]
fn test_abort() {
line_match("abc\x03https://www.youtube.com/watch?v=dQw4w9WgXcQ", None);
line_match("hello\x04https://www.youtube.com/watch?v=yPYZpwSpKmA", None);
2016-04-02 17:05:31 +01:00
}
2016-03-09 16:18:31 +00:00
}