...
 
Commits (87)
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
language: rust
cache: cargo
rust:
- stable
- beta
- nightly
os:
- linux
- osx
script:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then FAKETTY="script -q /dev/null"; fi
- $FAKETTY cargo build --verbose
- $FAKETTY cargo test --verbose
- $FAKETTY cargo test --release --verbose
[package]
name = "termion"
version = "1.1.2"
authors = ["ticki <Ticki@users.noreply.github.com>"]
version = "1.5.2"
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"
# Termion
<p align="center">
<img alt="Termion logo" src="https://rawgit.com/redox-os/termion/master/logo.svg" />
</p>
[![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/)
|----|----|----|----|----
[Documentation](https://docs.rs/termion) | [Examples](https://github.com/Ticki/termion/tree/master/examples) | [Changelog](https://github.com/Ticki/termion/tree/master/CHANGELOG.md)
|----|----|----
**Termion** is a pure Rust, bindless library for low-level handling, manipulating
and reading information about terminals. This provides a full-featured
......@@ -11,9 +14,9 @@ Termion aims to be simple and yet expressive. It is bindless, meaning that it
is not a front-end to some other library (e.g., ncurses or termbox), but a
standalone library directly talking to the TTY.
Termion is quite convinient, due to its complete coverage of essential TTY
Termion is quite convenient, due to its complete coverage of essential TTY
features, providing one consistent API. Termion is rather low-level containing
only abstraction aligned with what actually happens behind the scenes, for
only abstraction aligned with what actually happens behind the scenes. For
something more high-level, refer to inquirer-rs, which uses Termion as backend.
Termion generates escapes and API calls for the user. This makes it a whole lot
......@@ -29,7 +32,7 @@ This crate is stable.
```toml
[dependencies]
termion = "1.0"
termion = "*"
```
## 0.1.0 to 1.0.0 guide
......@@ -151,16 +154,16 @@ fn main() {
let stdin = stdin();
let mut stdin = stdin.lock();
stdout.write(b"password: ").unwrap();
stdout.write_all(b"password: ").unwrap();
stdout.flush().unwrap();
let pass = stdin.read_passwd(&mut stdout);
if let Ok(Some(pass)) = pass {
stdout.write(pass.as_bytes()).unwrap();
stdout.write(b"\n").unwrap();
stdout.write_all(pass.as_bytes()).unwrap();
stdout.write_all(b"\n").unwrap();
} else {
stdout.write(b"Error\n").unwrap();
stdout.write_all(b"Error\n").unwrap();
}
}
```
......
extern crate termion;
use termion::screen::*;
use std::io::{Write, stdout};
use std::{time, thread};
fn main() {
{
let mut screen = AlternateScreen::from(stdout());
write!(screen, "Welcome to the alternate screen.\n\nPlease wait patiently until we arrive back at the main screen in a about three seconds.").unwrap();
screen.flush().unwrap();
thread::sleep(time::Duration::from_secs(3));
}
println!("Phew! We are back.");
}
extern crate termion;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::screen::*;
use std::io::{Write, stdout, stdin};
fn write_alt_screen_msg<W: Write>(screen: &mut W) {
write!(screen, "{}{}Welcome to the alternate screen.{}Press '1' to switch to the main screen or '2' to switch to the alternate screen.{}Press 'q' to exit (and switch back to the main screen).",
termion::clear::All,
termion::cursor::Goto(1, 1),
termion::cursor::Goto(1, 3),
termion::cursor::Goto(1, 4)).unwrap();
}
fn main() {
let stdin = stdin();
let mut screen = AlternateScreen::from(stdout().into_raw_mode().unwrap());
write!(screen, "{}", termion::cursor::Hide).unwrap();
write_alt_screen_msg(&mut screen);
screen.flush().unwrap();
for c in stdin.keys() {
match c.unwrap() {
Key::Char('q') => break,
Key::Char('1') => {
write!(screen, "{}", ToMainScreen).unwrap();
}
Key::Char('2') => {
write!(screen, "{}", ToAlternateScreen).unwrap();
write_alt_screen_msg(&mut screen);
}
_ => {}
}
screen.flush().unwrap();
}
write!(screen, "{}", termion::cursor::Show).unwrap();
}
......@@ -11,7 +11,11 @@ fn main() {
let mut stdout = stdout.lock().into_raw_mode().unwrap();
let mut stdin = async_stdin().bytes();
write!(stdout, "{}{}", termion::clear::All, termion::cursor::Goto(1, 1)).unwrap();
write!(stdout,
"{}{}",
termion::clear::All,
termion::cursor::Goto(1, 1))
.unwrap();
loop {
write!(stdout, "{}", termion::clear::CurrentLine).unwrap();
......@@ -25,10 +29,10 @@ fn main() {
stdout.flush().unwrap();
thread::sleep(Duration::from_millis(50));
stdout.write(b"# ").unwrap();
stdout.write_all(b"# ").unwrap();
stdout.flush().unwrap();
thread::sleep(Duration::from_millis(50));
stdout.write(b"\r #").unwrap();
stdout.write_all(b"\r #").unwrap();
write!(stdout, "{}", termion::cursor::Goto(1, 1)).unwrap();
stdout.flush().unwrap();
}
......
......@@ -9,7 +9,11 @@ fn main() {
let stdin = stdin();
let mut stdout = MouseTerminal::from(stdout().into_raw_mode().unwrap());
write!(stdout, "{}{}q to exit. Click, click, click!", termion::clear::All, termion::cursor::Goto(1, 1)).unwrap();
write!(stdout,
"{}{}q to exit. Click, click, click!",
termion::clear::All,
termion::cursor::Goto(1, 1))
.unwrap();
stdout.flush().unwrap();
for c in stdin.events() {
......@@ -20,7 +24,7 @@ fn main() {
match me {
MouseEvent::Press(_, x, y) => {
write!(stdout, "{}x", termion::cursor::Goto(x, y)).unwrap();
},
}
_ => (),
}
}
......
......@@ -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);
}
extern crate termion;
use termion::{clear, color, cursor};
use std::{time, thread};
const COMMUNISM: &'static str = r#"
!######### #
!########! ##!
!########! ###
!########## ####
######### ##### ######
!###! !####! ######
! ##### ######!
!####! #######
##### #######
!####! #######!
####!########
## ##########
,######! !#############
,#### ########################!####!
,####' ##################!' #####
,####' ####### !####!
####' #####
~## ##~
"#;
fn main() {
let mut state = 0;
println!("\n{}{}{}{}{}{}",
cursor::Hide,
clear::All,
cursor::Goto(1, 1),
color::Fg(color::Black),
color::Bg(color::Red),
COMMUNISM);
loop {
println!("{}{} ☭ GAY ☭ SPACE ☭ COMMUNISM ☭ ",
cursor::Goto(1, 1),
color::Bg(color::AnsiValue(state)));
println!("{}{} WILL PREVAIL, COMRADES! ",
cursor::Goto(1, 20),
color::Bg(color::AnsiValue(state)));
state += 1;
state %= 8;
thread::sleep(time::Duration::from_millis(90));
}
}
extern crate termion;
use termion::color::{DetectColors, AnsiValue, Bg};
use termion::raw::IntoRawMode;
use std::io::stdout;
fn main() {
let count;
{
let mut term = stdout().into_raw_mode().unwrap();
count = term.available_colors().unwrap();
}
println!("This terminal supports {} colors.", count);
for i in 0..count {
print!("{} {}", Bg(AnsiValue(i as u8)), Bg(AnsiValue(0)));
}
println!();
}
......@@ -9,14 +9,20 @@ fn main() {
let stdin = stdin();
let mut stdout = stdout().into_raw_mode().unwrap();
write!(stdout, "{}{}q to exit. Type stuff, use alt, and so on.{}",
write!(stdout,
"{}{}q to exit. Type stuff, use alt, and so on.{}",
termion::clear::All,
termion::cursor::Goto(1, 1),
termion::cursor::Hide).unwrap();
termion::cursor::Hide)
.unwrap();
stdout.flush().unwrap();
for c in stdin.keys() {
write!(stdout, "{}{}", termion::cursor::Goto(1, 1), termion::clear::CurrentLine).unwrap();
write!(stdout,
"{}{}",
termion::cursor::Goto(1, 1),
termion::clear::CurrentLine)
.unwrap();
match c.unwrap() {
Key::Char('q') => break,
......@@ -29,7 +35,7 @@ fn main() {
Key::Up => println!("↑"),
Key::Down => println!("↓"),
Key::Backspace => println!("×"),
_ => {},
_ => {}
}
stdout.flush().unwrap();
}
......
extern crate termion;
use termion::event::*;
use termion::cursor;
use termion::cursor::{self, DetectCursorPos};
use termion::input::{TermRead, MouseTerminal};
use termion::raw::IntoRawMode;
use std::io::{self, Write};
......@@ -14,7 +14,7 @@ fn main() {
"{}{}q to exit. Type stuff, use alt, click around...",
termion::clear::All,
termion::cursor::Goto(1, 1))
.unwrap();
.unwrap();
for c in stdin.events() {
let evt = c.unwrap();
......@@ -26,6 +26,15 @@ fn main() {
MouseEvent::Release(a, b) |
MouseEvent::Hold(a, b) => {
write!(stdout, "{}", cursor::Goto(a, b)).unwrap();
let (x, y) = stdout.cursor_pos().unwrap();
write!(stdout,
"{}{}Cursor is at: ({},{}){}",
cursor::Goto(5, 5),
termion::clear::UntilNewline,
x,
y,
cursor::Goto(a, b))
.unwrap();
}
}
}
......
#![feature(step_by)]
extern crate termion;
use termion::event::Key;
......@@ -8,11 +6,20 @@ use termion::raw::IntoRawMode;
use std::io::{Write, stdout, stdin};
fn rainbow<W: Write>(stdout: &mut W, blue: u8) {
write!(stdout, "{}{}", termion::cursor::Goto(1, 1), termion::clear::All).unwrap();
for red in (0..255).step_by(8 as u8) {
for green in (0..255).step_by(4) {
write!(stdout, "{} ", termion::color::Bg(termion::color::Rgb(red, green, blue))).unwrap();
write!(stdout,
"{}{}",
termion::cursor::Goto(1, 1),
termion::clear::All)
.unwrap();
for red in 0..32 {
let red = red * 8;
for green in 0..64 {
let green = green * 4;
write!(stdout,
"{} ",
termion::color::Bg(termion::color::Rgb(red, green, blue)))
.unwrap();
}
write!(stdout, "\n\r").unwrap();
}
......@@ -24,10 +31,12 @@ fn main() {
let stdin = stdin();
let mut stdout = stdout().into_raw_mode().unwrap();
writeln!(stdout, "{}{}{}Use the arrow keys to change the blue in the rainbow.",
termion::clear::All,
termion::cursor::Goto(1, 1),
termion::cursor::Hide).unwrap();
writeln!(stdout,
"{}{}{}Use the up/down arrow keys to change the blue in the rainbow.",
termion::clear::All,
termion::cursor::Goto(1, 1),
termion::cursor::Hide)
.unwrap();
let mut blue = 172u8;
......@@ -36,13 +45,13 @@ fn main() {
Key::Up => {
blue = blue.saturating_add(4);
rainbow(&mut stdout, blue);
},
}
Key::Down => {
blue = blue.saturating_sub(4);
rainbow(&mut stdout, blue);
},
}
Key::Char('q') => break,
_ => {},
_ => {}
}
stdout.flush().unwrap();
}
......
......@@ -9,15 +9,15 @@ fn main() {
let stdin = stdin();
let mut stdin = stdin.lock();
stdout.write(b"password: ").unwrap();
stdout.write_all(b"password: ").unwrap();
stdout.flush().unwrap();
let pass = stdin.read_passwd(&mut stdout);
if let Ok(Some(pass)) = pass {
stdout.write(pass.as_bytes()).unwrap();
stdout.write(b"\n").unwrap();
stdout.write_all(pass.as_bytes()).unwrap();
stdout.write_all(b"\n").unwrap();
} else {
stdout.write(b"Error\n").unwrap();
stdout.write_all(b"Error\n").unwrap();
}
}
......@@ -12,13 +12,13 @@ fn main() {
{line_num_fg}{line_num_bg}84 {reset} }};\n\
{line_num_fg}{line_num_bg}85 {reset} }}\n\
{line_num_fg}{line_num_bg}{info_line}{reset} {red}^{reset} {error_fg}borrow from first closure ends here",
lighgreen=color::Fg(color::LightGreen),
red=color::Fg(color::Red),
bold=style::Bold,
reset=style::Reset,
magenta=color::Fg(color::Magenta),
line_num_bg=color::Bg(color::AnsiValue::grayscale(3)),
line_num_fg=color::Fg(color::AnsiValue::grayscale(18)),
info_line="| ",
error_fg=color::Fg(color::AnsiValue::grayscale(17)))
lighgreen = color::Fg(color::LightGreen),
red = color::Fg(color::Red),
bold = style::Bold,
reset = style::Reset,
magenta = color::Fg(color::Magenta),
line_num_bg = color::Bg(color::AnsiValue::grayscale(3)),
line_num_fg = color::Fg(color::AnsiValue::grayscale(18)),
info_line = "| ",
error_fg = color::Fg(color::AnsiValue::grayscale(17)))
}
......@@ -11,8 +11,14 @@ fn main() {
let stdin = stdin();
let stdin = stdin.lock();
write!(stdout, "{}{}{}yo, 'q' will exit.{}{}", termion::clear::All, termion::cursor::Goto(5, 5),
termion::style::Bold, termion::style::Reset, termion::cursor::Goto(20, 10)).unwrap();
write!(stdout,
"{}{}{}yo, 'q' will exit.{}{}",
termion::clear::All,
termion::cursor::Goto(5, 5),
termion::style::Bold,
termion::style::Reset,
termion::cursor::Goto(20, 10))
.unwrap();
stdout.flush().unwrap();
let mut bytes = stdin.bytes();
......@@ -20,15 +26,16 @@ fn main() {
let b = bytes.next().unwrap().unwrap();
match b {
// Quit
b'q' => return,
// Clear the screen
b'c' => write!(stdout, "{}", termion::clear::All),
// Set red color
b'r' => write!(stdout, "{}", color::Fg(color::Rgb(5, 0, 0))),
// Write it to stdout.
a => write!(stdout, "{}", a),
}.unwrap();
// Quit
b'q' => return,
// Clear the screen
b'c' => write!(stdout, "{}", termion::clear::All),
// Set red color
b'r' => write!(stdout, "{}", color::Fg(color::Rgb(5, 0, 0))),
// Write it to stdout.
a => write!(stdout, "{}", a),
}
.unwrap();
stdout.flush().unwrap();
}
......
<svg xmlns="http://www.w3.org/2000/svg" width="60.099598mm" height="18.291185mm" viewBox="0 0 212.95 64.81">
<style>
.blink { animation: blinker 3s linear infinite; } @keyframes blinker { 50% { opacity: 0; } }
</style>
<path d="M0 0h212.95v64.82H0z" opacity=".71"/>
<path fill="#f9f9f9" d="M12.24 17.8H34.5v3.33h-9.13v25.84H21.4V21.13h-9.16V17.8zm27 0h17.3v3.33H43.2v8.63h12.77v3.32h-12.8v10.57H56.9v3.32H39.24V17.8zM74.3 33.2q1.5.4 2.6 1.48 1.06 1.08 2.66 4.32l3.97 7.97H79.3l-3.5-7.37q-1.5-3.14-2.7-4.04-1.2-.92-3.13-.92H66.2v12.33h-3.96V17.8h8.13q4.8 0 7.36 2.17 2.56 2.17 2.56 6.27 0 2.9-1.6 4.73-1.6 1.82-4.4 2.23zm-8.1-12.15V31.4h4.32q2.83 0 4.22-1.27 1.4-1.27 1.4-3.9 0-2.5-1.5-3.83-1.46-1.35-4.27-1.35H66.2zm19-3.25h5.26l5.04 14.85 5.08-14.84h5.3V47h-3.66V21.2l-5.2 15.38h-2.98L88.82 21.2v25.77H85.2V17.8zm26.3 0h16.2v3.33h-6.12v22.52h6.1v3.32H111.5v-3.32h6.1V21.13h-6.1V17.8zm37.8 14.62q0-6.43-1.32-9.18-1.3-2.76-4.3-2.76t-4.33 2.76q-1.3 2.75-1.3 9.18 0 6.4 1.3 9.16 1.33 2.75 4.32 2.75 3 0 4.3-2.73 1.34-2.76 1.34-9.18zm4.13 0q0 7.6-2.42 11.36-2.4 3.75-7.3 3.75t-7.3-3.73q-2.4-3.73-2.4-11.38 0-7.64 2.4-11.4 2.4-3.74 7.35-3.74t7.34 3.75q2.42 3.75 2.42 11.4zm4.97-14.62h5l9.86 24v-24h3.8v29.17h-5l-9.84-24v24h-3.8V17.8z"/>
<path fill="#f9f9f9" d="M192.7 8.66v47.5h-3.93V8.66h3.94z" class="blink"/>
</svg>
......@@ -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,17 +40,13 @@ use tty;
pub fn async_stdin() -> AsyncReader {
let (send, recv) = mpsc::channel();
thread::spawn(move || {
for i in tty::get_tty().unwrap().bytes() {
if send.send(i).is_err() {
return;
}
}
});
thread::spawn(move || for i in get_tty().unwrap().bytes() {
if send.send(i).is_err() {
return;
}
});
AsyncReader {
recv: recv,
}
AsyncReader { recv: recv }
}
/// An asynchronous reader.
......@@ -59,7 +78,7 @@ impl Read for AsyncReader {
Ok(Ok(b)) => {
buf[total] = b;
total += 1;
},
}
Ok(Err(e)) => return Err(e),
Err(_) => break,
}
......
......@@ -3,9 +3,7 @@
//! # Example
//!
//! ```rust
//! use termion::{color, style};
//!
//! use std::io;
//! use termion::color;
//!
//! fn main() {
//! println!("{}Red", color::Fg(color::Red));
......@@ -15,9 +13,16 @@
//! ```
use std::fmt;
use raw::CONTROL_SEQUENCE_TIMEOUT;
use std::io::{self, Write, Read};
use std::time::{SystemTime, Duration};
use async::async_stdin;
use std::env;
use std::fmt::Debug;
use numtoa::NumToA;
/// A terminal color.
pub trait Color {
pub trait Color: Debug {
/// Write the foreground version of this color.
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result;
/// Write the background version of this color.
......@@ -33,14 +38,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") }
}
};
}
......@@ -61,6 +76,18 @@ derive_color!("High-intensity light magenta.", LightMagenta, "13");
derive_color!("High-intensity light cyan.", LightCyan, "14");
derive_color!("High-intensity light white.", LightWhite, "15");
impl<'a> Color for &'a Color {
#[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
(*self).write_fg(f)
}
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
(*self).write_bg(f)
}
}
/// An arbitrary ANSI color value.
#[derive(Clone, Copy, Debug)]
pub struct AnsiValue(pub u8);
......@@ -68,9 +95,15 @@ pub struct AnsiValue(pub u8);
impl AnsiValue {
/// 216-color (r, g, b ≤ 5) RGB.
pub fn rgb(r: u8, g: u8, b: u8) -> AnsiValue {
debug_assert!(r <= 5, "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.", r);
debug_assert!(g <= 5, "Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.", g);
debug_assert!(b <= 5, "Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.", b);
debug_assert!(r <= 5,
"Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.",
r);
debug_assert!(g <= 5,
"Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.",
g);
debug_assert!(b <= 5,
"Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.",
b);
AnsiValue(16 + 36 * r + 6 * g + b)
}
......@@ -80,22 +113,40 @@ impl AnsiValue {
/// There are 24 shades of gray.
pub fn grayscale(shade: u8) -> AnsiValue {
// Unfortunately, there are a little less than fifty shades.
debug_assert!(shade < 24, "Grayscale out of bound (shade = {}). There are only 24 shades of \
gray.", shade);
debug_assert!(shade < 24,
"Grayscale out of bound (shade = {}). There are only 24 shades of \
gray.",
shade);
AnsiValue(0xE8 + shade)
}
}
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())
}
}
......@@ -103,15 +154,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())
}
}
......@@ -119,15 +196,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)
}
}
......@@ -150,3 +237,70 @@ impl<C: Color> fmt::Display for Bg<C> {
self.0.write_bg(f)
}
}
/// Types that allow detection of the colors they support.
pub trait DetectColors {
/// How many ANSI colors are supported (from 8 to 256)?
///
/// Beware: the information given isn't authoritative, it's infered through escape codes or the
/// value of `TERM`, more colors may be available.
fn available_colors(&mut self) -> io::Result<u16>;
}
impl<W: Write> DetectColors for W {
fn available_colors(&mut self) -> io::Result<u16> {
let mut stdin = async_stdin();
if detect_color(self, &mut stdin, 0)? {
// OSC 4 is supported, detect how many colors there are.
// Do a binary search of the last supported color.
let mut min = 8;
let mut max = 256;
let mut i;
while min + 1 < max {
i = (min + max) / 2;
if detect_color(self, &mut stdin, i)? {
min = i
} else {
max = i
}
}
Ok(max)
} else {
// OSC 4 is not supported, trust TERM contents.
Ok(match env::var_os("TERM") {
Some(val) => {
if val.to_str().unwrap_or("").contains("256color") {
256
} else {
8
}
}
None => 8,
})
}
}
}
/// Detect a color using OSC 4.
fn detect_color(stdout: &mut Write, stdin: &mut Read, color: u16) -> io::Result<bool> {
// Is the color available?
// Use `ESC ] 4 ; color ; ? BEL`.
write!(stdout, "\x1B]4;{};?\x07", color)?;
stdout.flush()?;
let mut buf: [u8; 1] = [0];
let mut total_read = 0;
let timeout = Duration::from_millis(CONTROL_SEQUENCE_TIMEOUT);
let now = SystemTime::now();
let bell = 7u8;
// Either consume all data up to bell or wait for a timeout.
while buf[0] != bell && now.elapsed().unwrap() < timeout {
total_read += stdin.read(&mut buf)?;
}
// If there was a response, the color is supported.
Ok(total_read > 0)
}
//! Cursor movement.
use std::fmt;
use std::ops;
use std::io::{self, Write, Error, ErrorKind, Read};
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?
......@@ -25,15 +34,23 @@ 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) }
fn default() -> Goto {
Goto(1, 1)
}
}
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))
}
}
......@@ -41,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))
}
}
......@@ -51,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))
}
}
......@@ -61,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))
}
}
......@@ -71,8 +109,114 @@ 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))
}
}
/// Types that allow detection of the cursor position.
pub trait DetectCursorPos {
/// Get the (1,1)-based cursor position from the terminal.
fn cursor_pos(&mut self) -> io::Result<(u16, u16)>;
}
impl<W: Write> DetectCursorPos for W {
fn cursor_pos(&mut self) -> io::Result<(u16, u16)> {
let delimiter = b'R';
let mut stdin = async_stdin_until(delimiter);
// Where is the cursor?
// Use `ESC [ 6 n`.
write!(self, "\x1B[6n")?;
self.flush()?;
let mut buf: [u8; 1] = [0];
let mut read_chars = Vec::new();
let timeout = Duration::from_millis(CONTROL_SEQUENCE_TIMEOUT);
let now = SystemTime::now();
// Either consume all data up to R or wait for a timeout.
while buf[0] != delimiter && now.elapsed().unwrap() < timeout {
if stdin.read(&mut buf)? > 0 {
read_chars.push(buf[0]);
}
}
if read_chars.is_empty() {
return Err(Error::new(ErrorKind::Other, "Cursor position detection timed out."));
}
// The answer will look like `ESC [ Cy ; Cx R`.
read_chars.pop(); // remove trailing R.
let read_str = String::from_utf8(read_chars).unwrap();
let beg = read_str.rfind('[').unwrap();
let coords: String = read_str.chars().skip(beg + 1).collect();
let mut nums = coords.split(';');
let cy = nums.next()
.unwrap()
.parse::<u16>()
.unwrap();
let cx = nums.next()
.unwrap()
.parse::<u16>()
.unwrap();
Ok((cx, cy))
}
}
/// Hide the cursor for the lifetime of this struct.
/// It will hide the cursor on creation with from() and show it back on drop().
pub struct HideCursor<W: Write> {
/// The output target.
output: W,
}
impl<W: Write> HideCursor<W> {
/// Create a hide cursor wrapper struct for the provided output and hides the cursor.
pub fn from(mut output: W) -> Self {
write!(output, "{}", Hide).expect("hide the cursor");
HideCursor { output: output }
}
}
impl<W: Write> Drop for HideCursor<W> {
fn drop(&mut self) {
write!(self, "{}", Show).expect("show the cursor");
}
}
impl<W: Write> ops::Deref for HideCursor<W> {
type Target = W;
fn deref(&self) -> &W {
&self.output
}
}
impl<W: Write> ops::DerefMut for HideCursor<W> {
fn deref_mut(&mut self) -> &mut W {
&mut self.output
}
}
impl<W: Write> Write for HideCursor<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.output.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.output.flush()
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -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(all(unix, not(target_os = "redox")))]
#[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;
......@@ -34,5 +35,29 @@ pub mod cursor;
pub mod event;
pub mod input;
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))
}