From 6f1621d2d099520e83235843cda4cf70fb382812 Mon Sep 17 00:00:00 2001
From: Ticki <Ticki@users.noreply.github.com>
Date: Wed, 9 Mar 2016 09:39:22 +0100
Subject: [PATCH] Introduce 'Key', which can decode special key input

---
 Cargo.toml       |  3 +++
 examples/keys.rs | 35 ++++++++++++++++++++++++
 src/color.rs     |  9 ++++++-
 src/input.rs     | 69 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/lib.rs       | 16 +++++++++--
 src/raw.rs       |  1 +
 6 files changed, 130 insertions(+), 3 deletions(-)
 create mode 100644 examples/keys.rs

diff --git a/Cargo.toml b/Cargo.toml
index 19101a30..3b8c618b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,3 +5,6 @@ authors = ["Ticki <Ticki@users.noreply.github.com>"]
 
 [dependencies]
 libc = "0.2.8"
+
+[features]
+nightly = []
diff --git a/examples/keys.rs b/examples/keys.rs
new file mode 100644
index 00000000..5e365d71
--- /dev/null
+++ b/examples/keys.rs
@@ -0,0 +1,35 @@
+extern crate libterm;
+
+use libterm::{TermRead, TermWrite, IntoRawMode, Color, Style, Key};
+use std::io::{Read, Write, stdout, stdin};
+
+fn main() {
+    let stdin = stdin();
+    let mut stdout = stdout().into_raw_mode().unwrap();
+
+    stdout.clear();
+    stdout.goto(0, 0);
+    stdout.write(b"q to exit. Type stuff, use alt, and so on.");
+    stdout.hide_cursor();
+    stdout.flush();
+
+    for c in stdin.keys() {
+        stdout.goto(5, 5);
+        stdout.clear_line();
+        match c {
+            Key::Char('q') => break,
+            Key::Char(c) => println!("{}", c),
+            Key::Alt(c) => println!("^{}", c),
+            Key::Left => println!("←"),
+            Key::Right => println!("→"),
+            Key::Up => println!("∆"),
+            Key::Down => println!("∇"),
+            Key::Backspace => println!("×"),
+            Key::Invalid => println!("???"),
+            Key::Error => println!("ERROR"),
+        }
+        stdout.flush();
+    }
+
+    stdout.show_cursor();
+}
diff --git a/src/color.rs b/src/color.rs
index c927b9cf..20824f37 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -27,7 +27,7 @@ pub enum Color {
     HiYellow,
     /// High-intensity blue.
     HiBlue,
-    /// High-intensity megenta.
+    /// High-intensity magenta.
     HiMagenta,
     /// High-intensity cyan.
     HiCyan,
@@ -42,6 +42,13 @@ pub enum Color {
 use Color::*;
 
 impl Color {
+    /// Get the corresponding ANSI value.
+    ///
+    /// Panics
+    /// ======
+    ///
+    /// This method will panic in debug mode, if `self` is invalid (that is, the values are out of
+    /// bound).
     pub fn to_ansi_val(self) -> u8 {
         self.debug_check();
 
diff --git a/src/input.rs b/src/input.rs
index 44ad69f3..0017efa8 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -1,8 +1,70 @@
 use std::io::{Read, Write};
 use {IntoRawMode, TerminalError};
 
+#[cfg(feature = "nightly")]
+use std::io::Chars;
+
+/// A key.
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+pub enum Key {
+    /// Backspace.
+    Backspace,
+    /// Left arrow.
+    Left,
+    /// Right arrow.
+    Right,
+    /// Up arrow.
+    Up,
+    /// Down arrow.
+    Down,
+    /// Alt modified character.
+    Alt(char),
+    /// Normal character.
+    Char(char),
+    /// Invalid character code.
+    Invalid,
+    // TODO handle errors better?
+    /// IO error.
+    Error,
+}
+
+#[cfg(feature = "nightly")]
+/// An iterator over input keys.
+pub struct Keys<R> {
+    chars: Chars<R>,
+}
+
+#[cfg(feature = "nightly")]
+impl<R: Read> Iterator for Keys<R> {
+    type Item = Key;
+
+    fn next(&mut self) -> Option<Key> {
+        match self.chars.next() {
+            Some(Ok('\x1B')) => Some(match self.chars.next() {
+                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,
+                    _ => Key::Invalid,
+                },
+                Some(Ok(c)) => Key::Alt(c),
+                Some(Err(_)) | None => Key::Invalid,
+            }),
+            Some(Ok('\x7F')) => Some(Key::Backspace),
+            Some(Ok(c)) => Some(Key::Char(c)),
+            None => None,
+            Some(Err(_)) => Some(Key::Error),
+        }
+    }
+}
+
 /// Extension to `Read` trait.
 pub trait TermRead {
+    /// An iterator over key inputs.
+    #[cfg(feature = "nightly")]
+    fn keys(self) -> Keys<Self> where Self: Sized;
+
     /// Read a password.
     ///
     /// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
@@ -11,6 +73,13 @@ pub trait TermRead {
 }
 
 impl<R: Read> TermRead for R {
+    #[cfg(feature = "nightly")]
+    fn keys(self) -> Keys<R> {
+        Keys {
+            chars: self.chars(),
+        }
+    }
+
     fn read_passwd<W: Write>(&mut self, writer: &mut W) -> Result<Option<String>, TerminalError> {
         let _raw = try!(writer.into_raw_mode());
         let mut passbuf = Vec::with_capacity(30);
diff --git a/src/lib.rs b/src/lib.rs
index 99240fe1..3ad3d47d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,14 @@
-#[warn(missing_docs)]
+//! Libterm is a pure Rust library for reading, manipulating, and handling terminals.
+//!
+//! This crate is not stable, yet. However, if you do want stability, you should specify the
+//! revision (commit hash) in your `Cargo.toml`, this way builds are complete reproducible, and won't
+//! break.
+
+#![cfg_attr(feature = "nightly",
+            feature(io))]
+
+#![warn(missing_docs)]
+
 
 #[cfg(not(target_os = "redox"))]
 extern crate libc;
@@ -10,7 +20,9 @@ mod control;
 pub use control::TermWrite;
 
 mod input;
-pub use input::TermRead;
+pub use input::{TermRead, Key};
+#[cfg(feature = "nightly")]
+pub use input::Keys;
 
 mod error;
 pub use error::TerminalError;
diff --git a/src/raw.rs b/src/raw.rs
index 448d1613..7b1df442 100644
--- a/src/raw.rs
+++ b/src/raw.rs
@@ -49,6 +49,7 @@ impl<W> DerefMut for TerminalRestorer<W> {
     }
 }
 
+/// Types which can be converted into "raw mode".
 pub trait IntoRawMode: Sized {
     /// Switch to raw mode.
     ///
-- 
GitLab