diff --git a/Cargo.toml b/Cargo.toml index 19101a3..3b8c618 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,6 @@ authors = ["Ticki "] [dependencies] libc = "0.2.8" + +[features] +nightly = [] diff --git a/examples/keys.rs b/examples/keys.rs new file mode 100644 index 0000000..5e365d7 --- /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 c927b9c..20824f3 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 44ad69f..0017efa 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 { + chars: Chars, +} + +#[cfg(feature = "nightly")] +impl Iterator for Keys { + type Item = Key; + + fn next(&mut self) -> Option { + 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 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 TermRead for R { + #[cfg(feature = "nightly")] + fn keys(self) -> Keys { + Keys { + chars: self.chars(), + } + } + fn read_passwd(&mut self, writer: &mut W) -> Result, 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 99240fe..3ad3d47 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 448d161..7b1df44 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -49,6 +49,7 @@ impl DerefMut for TerminalRestorer { } } +/// Types which can be converted into "raw mode". pub trait IntoRawMode: Sized { /// Switch to raw mode. ///