Introduce 'Key', which can decode special key input

This commit is contained in:
Ticki 2016-03-09 09:39:22 +01:00
parent d7a3e607ba
commit 6f1621d2d0
6 changed files with 130 additions and 3 deletions

View File

@ -5,3 +5,6 @@ authors = ["Ticki <Ticki@users.noreply.github.com>"]
[dependencies] [dependencies]
libc = "0.2.8" libc = "0.2.8"
[features]
nightly = []

35
examples/keys.rs Normal file
View File

@ -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();
}

View File

@ -27,7 +27,7 @@ pub enum Color {
HiYellow, HiYellow,
/// High-intensity blue. /// High-intensity blue.
HiBlue, HiBlue,
/// High-intensity megenta. /// High-intensity magenta.
HiMagenta, HiMagenta,
/// High-intensity cyan. /// High-intensity cyan.
HiCyan, HiCyan,
@ -42,6 +42,13 @@ pub enum Color {
use Color::*; use Color::*;
impl 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 { pub fn to_ansi_val(self) -> u8 {
self.debug_check(); self.debug_check();

View File

@ -1,8 +1,70 @@
use std::io::{Read, Write}; use std::io::{Read, Write};
use {IntoRawMode, TerminalError}; 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. /// Extension to `Read` trait.
pub trait TermRead { pub trait TermRead {
/// An iterator over key inputs.
#[cfg(feature = "nightly")]
fn keys(self) -> Keys<Self> where Self: Sized;
/// Read a password. /// Read a password.
/// ///
/// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will /// 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 { 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> { fn read_passwd<W: Write>(&mut self, writer: &mut W) -> Result<Option<String>, TerminalError> {
let _raw = try!(writer.into_raw_mode()); let _raw = try!(writer.into_raw_mode());
let mut passbuf = Vec::with_capacity(30); let mut passbuf = Vec::with_capacity(30);

View File

@ -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"))] #[cfg(not(target_os = "redox"))]
extern crate libc; extern crate libc;
@ -10,7 +20,9 @@ mod control;
pub use control::TermWrite; pub use control::TermWrite;
mod input; mod input;
pub use input::TermRead; pub use input::{TermRead, Key};
#[cfg(feature = "nightly")]
pub use input::Keys;
mod error; mod error;
pub use error::TerminalError; pub use error::TerminalError;

View File

@ -49,6 +49,7 @@ impl<W> DerefMut for TerminalRestorer<W> {
} }
} }
/// Types which can be converted into "raw mode".
pub trait IntoRawMode: Sized { pub trait IntoRawMode: Sized {
/// Switch to raw mode. /// Switch to raw mode.
/// ///