Merge branch 'master' of github.com:Ticki/libterm

This commit is contained in:
ticki 2016-10-26 11:56:00 +02:00
commit 936c4336ce
6 changed files with 77 additions and 20 deletions

View File

@ -3,7 +3,7 @@ extern crate termion;
use std::fs;
fn main() {
if termion::is_tty(fs::File::create("/dev/stdout").unwrap()) {
if termion::is_tty(&fs::File::create("/dev/stdout").unwrap()) {
println!("This is a TTY!");
} else {
println!("This is not a TTY :(");

View File

@ -23,6 +23,7 @@ fn main() {
Key::Char(c) => println!("{}", c),
Key::Alt(c) => println!("^{}", c),
Key::Ctrl(c) => println!("*{}", c),
Key::Esc => println!("ESC"),
Key::Left => println!(""),
Key::Right => println!(""),
Key::Up => println!(""),

View File

@ -27,7 +27,7 @@ pub trait Color {
macro_rules! derive_color {
($doc:expr, $name:ident, $value:expr) => {
#[doc = $doc]
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub struct $name;
impl Color for $name {
@ -62,7 +62,7 @@ derive_color!("High-intensity light cyan.", LightCyan, "14");
derive_color!("High-intensity light white.", LightWhite, "15");
/// An arbitrary ANSI color value.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub struct AnsiValue(pub u8);
impl AnsiValue {
@ -100,6 +100,7 @@ impl Color for AnsiValue {
}
/// A truecolor RGB.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rgb(pub u8, pub u8, pub u8);
impl Color for Rgb {
@ -115,6 +116,7 @@ impl Color for Rgb {
}
/// Reset colors to defaults.
#[derive(Debug, Clone, Copy)]
pub struct Reset;
impl Color for Reset {
@ -130,6 +132,7 @@ impl Color for Reset {
}
/// A foreground color.
#[derive(Debug, Clone, Copy)]
pub struct Fg<C: Color>(pub C);
impl<C: Color> fmt::Display for Fg<C> {
@ -139,6 +142,7 @@ impl<C: Color> fmt::Display for Fg<C> {
}
/// A background color.
#[derive(Debug, Clone, Copy)]
pub struct Bg<C: Color>(pub C);
impl<C: Color> fmt::Display for Bg<C> {

View File

@ -90,6 +90,8 @@ pub enum Key {
Ctrl(char),
/// Null byte.
Null,
/// Esc key.
Esc,
#[allow(missing_docs)]
#[doc(hidden)]

View File

@ -7,11 +7,11 @@ use event::{parse_event, Event, Key};
use raw::IntoRawMode;
/// An iterator over input keys.
pub struct Keys<I> {
iter: Events<I>,
pub struct Keys<R> {
iter: Events<R>,
}
impl<I: Iterator<Item = Result<u8, io::Error>>> Iterator for Keys<I> {
impl<R: Read> Iterator for Keys<R> {
type Item = Result<Key, io::Error>;
fn next(&mut self) -> Option<Result<Key, io::Error>> {
@ -27,29 +27,59 @@ impl<I: Iterator<Item = Result<u8, io::Error>>> Iterator for Keys<I> {
}
/// An iterator over input events.
pub struct Events<I> {
bytes: I,
pub struct Events<R> {
source: R,
leftover: Option<u8>,
}
impl<I: Iterator<Item = Result<u8, io::Error>>> Iterator for Events<I> {
impl<R: Read> Iterator for Events<R> {
type Item = Result<Event, io::Error>;
fn next(&mut self) -> Option<Result<Event, io::Error>> {
let iter = &mut self.bytes;
match iter.next() {
Some(item) => Some(parse_event(item, iter).or(Ok(Event::Unsupported))),
None => None,
let mut source = &mut self.source;
if let Some(c) = self.leftover {
// we have a leftover byte, use it
self.leftover = None;
return Some(parse_event(Ok(c), &mut source.bytes()).or(Ok(Event::Unsupported)));
}
// Here we read two bytes at a time. We need to distinguish between single ESC key presses,
// and escape sequences (which start with ESC or a x1B byte). The idea is that if this is
// an escape sequence, we will read multiple bytes (the first byte being ESC) but if this
// is a single ESC keypress, we will only read a single byte.
let mut buf = [0u8; 2];
let res = match source.read(&mut buf) {
Ok(0) => return None,
Ok(1) => match buf[0] {
b'\x1B' => Ok(Event::Key(Key::Esc)),
c => parse_event(Ok(c), &mut source.bytes()),
},
Ok(2) => {
if buf[0] != b'\x1B' {
// this is not an escape sequence, but we read two bytes, save the second byte
// for later
self.leftover = Some(buf[1]);
}
let mut iter = buf[1..2].iter().map(|c| Ok(*c)).chain(source.bytes());
parse_event(Ok(buf[0]), &mut iter)
}
Ok(_) => unreachable!(),
Err(e) => Err(e),
};
Some(res.or(Ok(Event::Unsupported)))
}
}
/// Extension to `Read` trait.
pub trait TermRead {
/// An iterator over input events.
fn events(self) -> Events<io::Bytes<Self>> where Self: Sized;
fn events(self) -> Events<Self> where Self: Sized;
/// An iterator over key inputs.
fn keys(self) -> Keys<io::Bytes<Self>> where Self: Sized;
fn keys(self) -> Keys<Self> where Self: Sized;
/// Read a line.
///
@ -68,12 +98,13 @@ pub trait TermRead {
}
impl<R: Read> TermRead for R {
fn events(self) -> Events<io::Bytes<R>> {
fn events(self) -> Events<Self> {
Events {
bytes: self.bytes(),
source: self,
leftover: None,
}
}
fn keys(self) -> Keys<io::Bytes<R>> {
fn keys(self) -> Keys<Self> {
Keys {
iter: self.events(),
}
@ -213,6 +244,13 @@ mod test {
assert!(st.next().is_none());
}
#[test]
fn test_esc_key() {
let mut st = b"\x1B".keys();
assert_eq!(st.next().unwrap().unwrap(), Key::Esc);
assert!(st.next().is_none());
}
fn line_match(a: &str, b: Option<&str>) {
let mut sink = io::sink();

View File

@ -15,17 +15,25 @@ struct TermSize {
// Since attributes on non-item statements is not stable yet, we use a function.
#[cfg(not(target_os = "redox"))]
#[cfg(target_pointer_width = "64")]
#[cfg(not(target_env = "musl"))]
fn tiocgwinsz() -> u64 {
use termios::TIOCGWINSZ;
TIOCGWINSZ as u64
}
#[cfg(not(target_os = "redox"))]
#[cfg(target_pointer_width = "32")]
#[cfg(not(target_env = "musl"))]
fn tiocgwinsz() -> u32 {
use termios::TIOCGWINSZ;
TIOCGWINSZ as u32
}
#[cfg(target_env = "musl")]
fn tiocgwinsz() -> i32 {
use termios::TIOCGWINSZ;
TIOCGWINSZ as i32
}
/// Get the size of the terminal.
#[cfg(not(target_os = "redox"))]
@ -50,8 +58,12 @@ pub fn terminal_size() -> io::Result<(u16, u16)> {
pub fn terminal_size() -> io::Result<(u16, u16)> {
use std::env;
let width = try!(env::var("COLUMNS").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))).parse().unwrap_or(0);
let height = try!(env::var("LINES").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))).parse().unwrap_or(0);
let width = try!(env::var("COLUMNS").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x)))
.parse()
.unwrap_or(0);
let height = try!(env::var("LINES").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x)))
.parse()
.unwrap_or(0);
Ok((width, height))
}