Merge branch 'master' of github.com:Ticki/libterm
This commit is contained in:
commit
936c4336ce
|
@ -3,7 +3,7 @@ extern crate termion;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
fn main() {
|
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!");
|
println!("This is a TTY!");
|
||||||
} else {
|
} else {
|
||||||
println!("This is not a TTY :(");
|
println!("This is not a TTY :(");
|
||||||
|
|
|
@ -23,6 +23,7 @@ fn main() {
|
||||||
Key::Char(c) => println!("{}", c),
|
Key::Char(c) => println!("{}", c),
|
||||||
Key::Alt(c) => println!("^{}", c),
|
Key::Alt(c) => println!("^{}", c),
|
||||||
Key::Ctrl(c) => println!("*{}", c),
|
Key::Ctrl(c) => println!("*{}", c),
|
||||||
|
Key::Esc => println!("ESC"),
|
||||||
Key::Left => println!("←"),
|
Key::Left => println!("←"),
|
||||||
Key::Right => println!("→"),
|
Key::Right => println!("→"),
|
||||||
Key::Up => println!("↑"),
|
Key::Up => println!("↑"),
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub trait Color {
|
||||||
macro_rules! derive_color {
|
macro_rules! derive_color {
|
||||||
($doc:expr, $name:ident, $value:expr) => {
|
($doc:expr, $name:ident, $value:expr) => {
|
||||||
#[doc = $doc]
|
#[doc = $doc]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct $name;
|
pub struct $name;
|
||||||
|
|
||||||
impl Color for $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");
|
derive_color!("High-intensity light white.", LightWhite, "15");
|
||||||
|
|
||||||
/// An arbitrary ANSI color value.
|
/// An arbitrary ANSI color value.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct AnsiValue(pub u8);
|
pub struct AnsiValue(pub u8);
|
||||||
|
|
||||||
impl AnsiValue {
|
impl AnsiValue {
|
||||||
|
@ -100,6 +100,7 @@ impl Color for AnsiValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A truecolor RGB.
|
/// A truecolor RGB.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Rgb(pub u8, pub u8, pub u8);
|
pub struct Rgb(pub u8, pub u8, pub u8);
|
||||||
|
|
||||||
impl Color for Rgb {
|
impl Color for Rgb {
|
||||||
|
@ -115,6 +116,7 @@ impl Color for Rgb {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset colors to defaults.
|
/// Reset colors to defaults.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Reset;
|
pub struct Reset;
|
||||||
|
|
||||||
impl Color for Reset {
|
impl Color for Reset {
|
||||||
|
@ -130,6 +132,7 @@ impl Color for Reset {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A foreground color.
|
/// A foreground color.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Fg<C: Color>(pub C);
|
pub struct Fg<C: Color>(pub C);
|
||||||
|
|
||||||
impl<C: Color> fmt::Display for Fg<C> {
|
impl<C: Color> fmt::Display for Fg<C> {
|
||||||
|
@ -139,6 +142,7 @@ impl<C: Color> fmt::Display for Fg<C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A background color.
|
/// A background color.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Bg<C: Color>(pub C);
|
pub struct Bg<C: Color>(pub C);
|
||||||
|
|
||||||
impl<C: Color> fmt::Display for Bg<C> {
|
impl<C: Color> fmt::Display for Bg<C> {
|
||||||
|
|
|
@ -90,6 +90,8 @@ pub enum Key {
|
||||||
Ctrl(char),
|
Ctrl(char),
|
||||||
/// Null byte.
|
/// Null byte.
|
||||||
Null,
|
Null,
|
||||||
|
/// Esc key.
|
||||||
|
Esc,
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
|
68
src/input.rs
68
src/input.rs
|
@ -7,11 +7,11 @@ use event::{parse_event, Event, Key};
|
||||||
use raw::IntoRawMode;
|
use raw::IntoRawMode;
|
||||||
|
|
||||||
/// An iterator over input keys.
|
/// An iterator over input keys.
|
||||||
pub struct Keys<I> {
|
pub struct Keys<R> {
|
||||||
iter: Events<I>,
|
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>;
|
type Item = Result<Key, io::Error>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<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.
|
/// An iterator over input events.
|
||||||
pub struct Events<I> {
|
pub struct Events<R> {
|
||||||
bytes: I,
|
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>;
|
type Item = Result<Event, io::Error>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Result<Event, io::Error>> {
|
fn next(&mut self) -> Option<Result<Event, io::Error>> {
|
||||||
let iter = &mut self.bytes;
|
let mut source = &mut self.source;
|
||||||
match iter.next() {
|
|
||||||
Some(item) => Some(parse_event(item, iter).or(Ok(Event::Unsupported))),
|
if let Some(c) = self.leftover {
|
||||||
None => None,
|
// 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.
|
/// Extension to `Read` trait.
|
||||||
pub trait TermRead {
|
pub trait TermRead {
|
||||||
/// An iterator over input events.
|
/// 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.
|
/// 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.
|
/// Read a line.
|
||||||
///
|
///
|
||||||
|
@ -68,12 +98,13 @@ pub trait TermRead {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> TermRead for R {
|
impl<R: Read> TermRead for R {
|
||||||
fn events(self) -> Events<io::Bytes<R>> {
|
fn events(self) -> Events<Self> {
|
||||||
Events {
|
Events {
|
||||||
bytes: self.bytes(),
|
source: self,
|
||||||
|
leftover: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn keys(self) -> Keys<io::Bytes<R>> {
|
fn keys(self) -> Keys<Self> {
|
||||||
Keys {
|
Keys {
|
||||||
iter: self.events(),
|
iter: self.events(),
|
||||||
}
|
}
|
||||||
|
@ -213,6 +244,13 @@ mod test {
|
||||||
assert!(st.next().is_none());
|
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>) {
|
fn line_match(a: &str, b: Option<&str>) {
|
||||||
let mut sink = io::sink();
|
let mut sink = io::sink();
|
||||||
|
|
||||||
|
|
16
src/size.rs
16
src/size.rs
|
@ -15,17 +15,25 @@ struct TermSize {
|
||||||
// Since attributes on non-item statements is not stable yet, we use a function.
|
// Since attributes on non-item statements is not stable yet, we use a function.
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(not(target_os = "redox"))]
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
#[cfg(not(target_env = "musl"))]
|
||||||
fn tiocgwinsz() -> u64 {
|
fn tiocgwinsz() -> u64 {
|
||||||
use termios::TIOCGWINSZ;
|
use termios::TIOCGWINSZ;
|
||||||
TIOCGWINSZ as u64
|
TIOCGWINSZ as u64
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(not(target_os = "redox"))]
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
#[cfg(not(target_env = "musl"))]
|
||||||
fn tiocgwinsz() -> u32 {
|
fn tiocgwinsz() -> u32 {
|
||||||
use termios::TIOCGWINSZ;
|
use termios::TIOCGWINSZ;
|
||||||
TIOCGWINSZ as u32
|
TIOCGWINSZ as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_env = "musl")]
|
||||||
|
fn tiocgwinsz() -> i32 {
|
||||||
|
use termios::TIOCGWINSZ;
|
||||||
|
TIOCGWINSZ as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Get the size of the terminal.
|
/// Get the size of the terminal.
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[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)> {
|
pub fn terminal_size() -> io::Result<(u16, u16)> {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
let width = try!(env::var("COLUMNS").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)))
|
||||||
let height = try!(env::var("LINES").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))).parse().unwrap_or(0);
|
.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))
|
Ok((width, height))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue