Commit 5301d862 authored by Matthias Devlamynck's avatar Matthias Devlamynck

Merge remote-tracking branch 'origin/master'

parents c26d2f0a c04fd9dc
image: "rust:latest"
stages:
- build
- test
before_script:
- rustup toolchain add $toolchain
cache:
paths:
- target/
build:stable:
stage: build
variables:
toolchain: stable
script:
- cargo +stable build --verbose
- cargo +stable build --release --verbose
test:stable:
stage: test
variables:
toolchain: stable
dependencies:
- build:stable
script:
- script -q -c "cargo +stable test --verbose"
- script -q -c "cargo +stable test --release --verbose"
build:beta:
stage: build
variables:
toolchain: beta
script:
- cargo +beta build --verbose
- cargo +beta build --release --verbose
test:beta:
stage: test
variables:
toolchain: beta
dependencies:
- build:beta
script:
- script -q -c "cargo +beta test --verbose"
- script -q -c "cargo +beta test --release --verbose"
build:nightly:
stage: build
variables:
toolchain: nightly
script:
- cargo +nightly build --verbose
- cargo +nightly build --release --verbose
test:nightly:
stage: test
variables:
toolchain: nightly
dependencies:
- build:nightly
script:
- script -q -c "cargo +nightly test --verbose"
- script -q -c "cargo +nightly test --release --verbose"
\ No newline at end of file
[package]
name = "termion"
version = "1.4.0"
version = "1.5.1"
authors = ["ticki <Ticki@users.noreply.github.com>", "gycos <alexandre.bury@gmail.com>", "IGI-111 <igi-111@protonmail.com>"]
description = "A bindless library for manipulating terminals."
repository = "https://github.com/ticki/termion"
repository = "https://gitlab.redox-os.org/redox-os/termion"
documentation = "https://docs.rs/termion"
license = "MIT"
keywords = ["tty", "color", "terminal", "password", "tui"]
exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"]
[dependencies]
numtoa = { version = "0.1.0", features = ["std"]}
[target.'cfg(not(target_os = "redox"))'.dependencies]
libc = "0.2.8"
[target.'cfg(target_os = "redox")'.dependencies]
redox_syscall = "0.1"
redox_termios = "0.1"
<p align="center">
<img alt="Termion logo" src="https://rawgit.com/ticki/termion/master/logo.svg" />
<img alt="Termion logo" src="https://rawgit.com/redox-os/termion/master/logo.svg" />
</p>
[![Build Status](https://travis-ci.org/ticki/termion.svg?branch=master)](https://travis-ci.org/ticki/termion) [![Latest Version](https://img.shields.io/crates/v/termion.svg)](https://crates.io/crates/termion) | [Documentation](https://docs.rs/termion) | [Examples](https://github.com/Ticki/termion/tree/master/examples) | [Changelog](https://github.com/Ticki/termion/tree/master/CHANGELOG.md) | [Tutorial](http://ticki.github.io/blog/making-terminal-applications-in-rust-with-termion/)
[![Build Status](https://travis-ci.org/redox-os/termion.svg?branch=master)](https://travis-ci.org/redox-os/termion) [![Latest Version](https://img.shields.io/crates/v/termion.svg)](https://crates.io/crates/termion) | [Documentation](https://docs.rs/termion) | [Examples](https://github.com/redox-os/termion/tree/master/examples) | [Changelog](https://github.com/redox-os/termion/tree/master/CHANGELOG.md) | [Tutorial](http://ticki.github.io/blog/making-terminal-applications-in-rust-with-termion/)
|----|----|----|----|----
......
......@@ -6,5 +6,5 @@ fn main() {
println!("{}Red", color::Fg(color::Red));
println!("{}Blue", color::Fg(color::Blue));
println!("{}Blue'n'Bold{}", style::Bold, style::Reset);
println!("{}Just plain italic", style::Italic);
println!("{}Just plain italic{}", style::Italic, style::Reset);
}
......@@ -2,7 +2,30 @@ use std::io::{self, Read};
use std::sync::mpsc;
use std::thread;
use tty;
use sys::tty::get_tty;
/// Construct an asynchronous handle to the TTY standard input, with a delimiter byte.
///
/// This has the same advantages as async_stdin(), but also allows specifying a delimiter byte. The
/// reader will stop reading after consuming the delimiter byte.
pub fn async_stdin_until(delimiter: u8) -> AsyncReader {
let (send, recv) = mpsc::channel();
thread::spawn(move || for i in get_tty().unwrap().bytes() {
match i {
Ok(byte) => {
let end_of_stream = &byte == &delimiter;
let send_error = send.send(Ok(byte)).is_err();
if end_of_stream || send_error { return; }
},
Err(_) => { return; }
}
});
AsyncReader { recv: recv }
}
/// Construct an asynchronous handle to the TTY standard input.
///
......@@ -17,7 +40,7 @@ use tty;
pub fn async_stdin() -> AsyncReader {
let (send, recv) = mpsc::channel();
thread::spawn(move || for i in tty::get_tty().unwrap().bytes() {
thread::spawn(move || for i in get_tty().unwrap().bytes() {
if send.send(i).is_err() {
return;
}
......
......@@ -18,6 +18,7 @@ use std::io::{self, Write, Read};
use std::time::{SystemTime, Duration};
use async::async_stdin;
use std::env;
use numtoa::NumToA;
/// A terminal color.
pub trait Color {
......@@ -36,14 +37,24 @@ macro_rules! derive_color {
impl Color for $name {
#[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("38;5;", $value, "m"))
f.write_str(self.fg_str())
}
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("48;5;", $value, "m"))
f.write_str(self.bg_str())
}
}
impl $name {
#[inline]
/// Returns the ANSI escape sequence as a string.
pub fn fg_str(&self) -> &'static str { csi!("38;5;", $value, "m") }
#[inline]
/// Returns the ANSI escape sequences as a string.
pub fn bg_str(&self) -> &'static str { csi!("48;5;", $value, "m") }
}
};
}
......@@ -110,15 +121,31 @@ impl AnsiValue {
}
}
impl AnsiValue {
/// Returns the ANSI sequence as a string.
pub fn fg_string(self) -> String {
let mut x = [0u8; 20];
let x = self.0.numtoa_str(10, &mut x);
[csi!("38;5;"), x, "m"].concat()
}
/// Returns the ANSI sequence as a string.
pub fn bg_string(self) -> String {
let mut x = [0u8; 20];
let x = self.0.numtoa_str(10, &mut x);
[csi!("48;5;"), x, "m"].concat()
}
}
impl Color for AnsiValue {
#[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("38;5;{}m"), self.0)
f.write_str(&self.fg_string())
}
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("48;5;{}m"), self.0)
f.write_str(&self.bg_string())
}
}
......@@ -126,15 +153,41 @@ impl Color for AnsiValue {
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rgb(pub u8, pub u8, pub u8);
impl Rgb {
/// Returns the ANSI sequence as a string.
pub fn fg_string(self) -> String {
let (mut x, mut y, mut z) = ([0u8; 20], [0u8; 20], [0u8; 20]);
let (x, y, z) = (
self.0.numtoa_str(10, &mut x),
self.1.numtoa_str(10, &mut y),
self.2.numtoa_str(10, &mut z),
);
[csi!("38;2;"), x, ";", y, ";", z, "m"].concat()
}
/// Returns the ANSI sequence as a string.
pub fn bg_string(self) -> String {
let (mut x, mut y, mut z) = ([0u8; 20], [0u8; 20], [0u8; 20]);
let (x, y, z) = (
self.0.numtoa_str(10, &mut x),
self.1.numtoa_str(10, &mut y),
self.2.numtoa_str(10, &mut z),
);
[csi!("48;2;"), x, ";", y, ";", z, "m"].concat()
}
}
impl Color for Rgb {
#[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("38;2;{};{};{}m"), self.0, self.1, self.2)
f.write_str(&self.fg_string())
}
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("48;2;{};{};{}m"), self.0, self.1, self.2)
f.write_str(&self.bg_string())
}
}
......@@ -142,15 +195,25 @@ impl Color for Rgb {
#[derive(Debug, Clone, Copy)]
pub struct Reset;
const RESET_FG: &str = csi!("39m");
const RESET_BG: &str = csi!("49m");
impl Reset {
/// Returns the ANSI sequence as a string.
pub fn fg_str(self) -> &'static str { RESET_FG }
/// Returns the ANSI sequence as a string.
pub fn bg_str(self) -> &'static str { RESET_BG }
}
impl Color for Reset {
#[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("39m"))
f.write_str(RESET_FG)
}
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("49m"))
f.write_str(RESET_BG)
}
}
......
......@@ -3,13 +3,17 @@
use std::fmt;
use std::ops;
use std::io::{self, Write, Error, ErrorKind, Read};
use async::async_stdin;
use async::async_stdin_until;
use std::time::{SystemTime, Duration};
use raw::CONTROL_SEQUENCE_TIMEOUT;
use numtoa::NumToA;
derive_csi_sequence!("Hide the cursor.", Hide, "?25l");
derive_csi_sequence!("Show the cursor.", Show, "?25h");
derive_csi_sequence!("Restore the cursor.", Restore, "u");
derive_csi_sequence!("Save the cursor.", Save, "s");
/// Goto some position ((1,1)-based).
///
/// # Why one-based?
......@@ -30,6 +34,13 @@ derive_csi_sequence!("Show the cursor.", Show, "?25h");
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Goto(pub u16, pub u16);
impl From<Goto> for String {
fn from(this: Goto) -> String {
let (mut x, mut y) = ([0u8; 20], [0u8; 20]);
["\x1B[", this.1.numtoa_str(10, &mut x), ";", this.0.numtoa_str(10, &mut y), "H"].concat()
}
}
impl Default for Goto {
fn default() -> Goto {
Goto(1, 1)
......@@ -39,8 +50,7 @@ impl Default for Goto {
impl fmt::Display for Goto {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
debug_assert!(self != &Goto(0, 0), "Goto is one-based.");
write!(f, csi!("{};{}H"), self.1, self.0)
f.write_str(&String::from(*self))
}
}
......@@ -48,9 +58,16 @@ impl fmt::Display for Goto {
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Left(pub u16);
impl From<Left> for String {
fn from(this: Left) -> String {
let mut buf = [0u8; 20];
["\x1B[", this.0.numtoa_str(10, &mut buf), "D"].concat()
}
}
impl fmt::Display for Left {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("{}D"), self.0)
f.write_str(&String::from(*self))
}
}
......@@ -58,9 +75,16 @@ impl fmt::Display for Left {
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Right(pub u16);
impl From<Right> for String {
fn from(this: Right) -> String {
let mut buf = [0u8; 20];
["\x1B[", this.0.numtoa_str(10, &mut buf), "C"].concat()
}
}
impl fmt::Display for Right {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("{}C"), self.0)
f.write_str(&String::from(*self))
}
}
......@@ -68,9 +92,16 @@ impl fmt::Display for Right {
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Up(pub u16);
impl From<Up> for String {
fn from(this: Up) -> String {
let mut buf = [0u8; 20];
["\x1B[", this.0.numtoa_str(10, &mut buf), "A"].concat()
}
}
impl fmt::Display for Up {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("{}A"), self.0)
f.write_str(&String::from(*self))
}
}
......@@ -78,9 +109,16 @@ impl fmt::Display for Up {
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Down(pub u16);
impl From<Down> for String {
fn from(this: Down) -> String {
let mut buf = [0u8; 20];
["\x1B[", this.0.numtoa_str(10, &mut buf), "B"].concat()
}
}
impl fmt::Display for Down {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("{}B"), self.0)
f.write_str(&String::from(*self))
}
}
......@@ -92,7 +130,8 @@ pub trait DetectCursorPos {
impl<W: Write> DetectCursorPos for W {
fn cursor_pos(&mut self) -> io::Result<(u16, u16)> {
let mut stdin = async_stdin();
let delimiter = b'R';
let mut stdin = async_stdin_until(delimiter);
// Where is the cursor?
// Use `ESC [ 6 n`.
......@@ -106,13 +145,13 @@ impl<W: Write> DetectCursorPos for W {
let now = SystemTime::now();
// Either consume all data up to R or wait for a timeout.
while buf[0] != b'R' && now.elapsed().unwrap() < timeout {
while buf[0] != delimiter && now.elapsed().unwrap() < timeout {
if stdin.read(&mut buf)? > 0 {
read_chars.push(buf[0]);
}
}
if read_chars.len() == 0 {
if read_chars.is_empty() {
return Err(Error::new(ErrorKind::Other, "Cursor position detection timed out."));
}
......
//! Mouse and key events.
use std::io::{Error, ErrorKind};
use std::ascii::AsciiExt;
use std::str;
/// An event reported by the terminal.
......
......@@ -27,16 +27,29 @@ impl<R: Read> Iterator for Keys<R> {
}
/// An iterator over input events.
pub struct Events<R> {
source: R,
leftover: Option<u8>,
pub struct Events<R> {
inner: EventsAndRaw<R>
}
impl<R: Read> Iterator for Events<R> {
type Item = Result<Event, io::Error>;
fn next(&mut self) -> Option<Result<Event, io::Error>> {
let mut source = &mut self.source;
self.inner.next().map(|tuple| tuple.map(|(event, _raw)| event))
}
}
/// An iterator over input events and the bytes that define them.
pub struct EventsAndRaw<R> {
source: R,
leftover: Option<u8>,
}
impl<R: Read> Iterator for EventsAndRaw<R> {
type Item = Result<(Event, Vec<u8>), io::Error>;
fn next(&mut self) -> Option<Result<(Event, Vec<u8>), io::Error>> {
let source = &mut self.source;
if let Some(c) = self.leftover {
// we have a leftover byte, use it
......@@ -53,7 +66,7 @@ impl<R: Read> Iterator for Events<R> {
Ok(0) => return None,
Ok(1) => {
match buf[0] {
b'\x1B' => Ok(Event::Key(Key::Esc)),
b'\x1B' => Ok((Event::Key(Key::Esc), vec![b'\x1B'])),
c => parse_event(c, &mut source.bytes()),
}
}
......@@ -75,7 +88,7 @@ impl<R: Read> Iterator for Events<R> {
}
}
fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, io::Error>
fn parse_event<I>(item: u8, iter: &mut I) -> Result<(Event, Vec<u8>), io::Error>
where I: Iterator<Item = Result<u8, io::Error>>
{
let mut buf = vec![item];
......@@ -85,7 +98,7 @@ fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, io::Error>
});
event::parse_event(item, &mut iter)
};
result.or(Ok(Event::Unsupported(buf)))
result.or(Ok(Event::Unsupported(buf.clone()))).map(|e| (e, buf))
}
......@@ -113,11 +126,11 @@ pub trait TermRead {
}
}
impl<R: Read> TermRead for R {
impl<R: Read + TermReadEventsAndRaw> TermRead for R {
fn events(self) -> Events<Self> {
Events {
source: self,
leftover: None,
inner: self.events_and_raw()
}
}
fn keys(self) -> Keys<Self> {
......@@ -145,6 +158,21 @@ impl<R: Read> TermRead for R {
}
}
/// Extension to `TermRead` trait. A separate trait in order to maintain backwards compatibility.
pub trait TermReadEventsAndRaw {
/// An iterator over input events and the bytes that define them.
fn events_and_raw(self) -> EventsAndRaw<Self> where Self: Sized;
}
impl<R: Read> TermReadEventsAndRaw for R {
fn events_and_raw(self) -> EventsAndRaw<Self> {
EventsAndRaw {
source: self,
leftover: None,
}
}
}
/// A sequence of escape codes to enable terminal mouse support.
const ENTER_MOUSE_SEQUENCE: &'static str = csi!("?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h");
......@@ -241,6 +269,38 @@ mod test {
assert!(i.next().is_none());
}
#[test]
fn test_events_and_raw() {
let input = b"\x1B[\x00bc\x7F\x1B[D\
\x1B[M\x00\x22\x24\x1B[<0;2;4;M\x1B[32;2;4M\x1B[<0;2;4;m\x1B[35;2;4Mb";
let mut output = Vec::<u8>::new();
{
let mut i = input.events_and_raw().map(|res| res.unwrap())
.inspect(|&(_, ref raw)| { output.extend(raw); }).map(|(event, _)| event);
assert_eq!(i.next().unwrap(),
Event::Unsupported(vec![0x1B, b'[', 0x00]));
assert_eq!(i.next().unwrap(), Event::Key(Key::Char('b')));
assert_eq!(i.next().unwrap(), Event::Key(Key::Char('c')));
assert_eq!(i.next().unwrap(), Event::Key(Key::Backspace));
assert_eq!(i.next().unwrap(), Event::Key(Key::Left));
assert_eq!(i.next().unwrap(),
Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 2, 4)));
assert_eq!(i.next().unwrap(),
Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4)));
assert_eq!(i.next().unwrap(),
Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4)));
assert_eq!(i.next().unwrap(),
Event::Mouse(MouseEvent::Release(2, 4)));
assert_eq!(i.next().unwrap(),
Event::Mouse(MouseEvent::Release(2, 4)));
assert_eq!(i.next().unwrap(), Event::Key(Key::Char('b')));
assert!(i.next().is_none());
}
assert_eq!(input.iter().map(|b| *b).collect::<Vec<u8>>(), output)
}
#[test]
fn test_function_keys() {
let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys();
......
......@@ -8,23 +8,24 @@
//!
//! Supports Redox, Mac OS X, and Linux (or, in general, ANSI terminals).
//!
//! For more information refer to the [README](https://github.com/ticki/termion).
//! For more information refer to the [README](https://github.com/redox-os/termion).
#![warn(missing_docs)]
#[cfg(not(target_os = "redox"))]
extern crate libc;
extern crate numtoa;
#[cfg(not(target_os = "redox"))]
mod termios;
#[cfg(target_os = "redox")]
#[path="sys/redox/mod.rs"]
mod sys;
mod async;
pub use async::{AsyncReader, async_stdin};
#[cfg(unix)]
#[path="sys/unix/mod.rs"]
mod sys;
mod size;
pub use size::terminal_size;
pub use sys::size::terminal_size;
pub use sys::tty::{is_tty, get_tty};
mod tty;
pub use tty::{is_tty, get_tty};
mod async;
pub use async::{AsyncReader, async_stdin};
#[macro_use]
mod macros;
......@@ -37,3 +38,26 @@ pub mod raw;
pub mod screen;
pub mod scroll;
pub mod style;
#[cfg(test)]
mod test {
use super::sys;
#[test]
fn test_get_terminal_attr() {
sys::attr::get_terminal_attr().unwrap();
sys::attr::get_terminal_attr().unwrap();
sys::attr::get_terminal_attr().unwrap();
}
#[test]
fn test_set_terminal_attr() {
let ios = sys::attr::get_terminal_attr().unwrap();
sys::attr::set_terminal_attr(&ios).unwrap();
}
#[test]
fn test_size() {
sys::size::terminal_size().unwrap();
}
}
......@@ -15,5 +15,13 @@ macro_rules! derive_csi_sequence {
write!(f, csi!($value))
}
}
impl AsRef<[u8]> for $name {
fn as_ref(&self) -> &'static [u8] { csi!($value).as_bytes() }
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &'static str { csi!($value) }
}
};
}
......@@ -25,6 +25,9 @@
use std::io::{self, Write};
use std::ops;
use sys::Termios;
use sys::attr::{get_terminal_attr, raw_terminal_attr, set_terminal_attr};
/// The timeout of an escape code control sequence, in milliseconds.
pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100;
......@@ -32,34 +35,14 @@ pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100;
/// dropped.
///
/// Restoring will entirely bring back the old TTY state.
#[cfg(target_os = "redox")]
pub struct RawTerminal<W: Write> {
output: W,
}
#[cfg(target_os = "redox")]
impl<W: Write> Drop for RawTerminal<W> {
fn drop(&mut self) {
let _ = write!(self, csi!("?82l"));
let _ = self.flush();
}
}
#[cfg(not(target_os = "redox"))]
use termios::Termios;
/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
/// dropped.
#[cfg(not(target_os = "redox"))]
pub struct RawTerminal<W: Write> {
prev_ios: Termios,
output: W,
}
#[cfg(not(target_os = "redox"))]
impl<W: Write> Drop for RawTerminal<W> {
fn drop(&mut self) {
use termios::set_terminal_attr;
set_terminal_attr(&mut self.prev_ios as *mut _);
set_terminal_attr(&self.prev_ios).unwrap();
}
}
......@@ -103,36 +86,32 @@ pub trait IntoRawMode: Write + Sized {
}
impl<W: Write> IntoRawMode for W {
#[cfg(not(target_os = "redox"))]
fn into_raw_mode(self) -> io::Result<RawTerminal<W>> {
use termios::{cfmakeraw, get_terminal_attr, set_terminal_attr};
let (mut ios, exit) = get_terminal_attr();
let mut ios = get_terminal_attr()?;
let prev_ios = ios;
if exit != 0 {
return Err(io::Error::new(io::ErrorKind::Other, "Unable to get Termios attribute."));
}
unsafe {
cfmakeraw(&mut ios);
}
if set_terminal_attr(&mut ios as *mut _) != 0 {
Err(io::Error::new(io::ErrorKind::Other, "Unable to set Termios attribute."))
} else {
let res = RawTerminal {
prev_ios: prev_ios,
output: self,
};
Ok(res)
}
raw_terminal_attr(&mut ios);
set_terminal_attr(&ios)?;
Ok(RawTerminal {
prev_ios: prev_ios,
output: self,
})
}