Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • redox-os/termion
  • NateDogg1232/termion
  • lemarcuspoilus/termion
  • nixpulvis/termion
  • Jezza/termion
  • mneumann/termion
  • ParadoxSpiral/termion
  • mdevlamynck/termion
  • jocutajar/termion
  • d-e-s-o/termion
  • karliss/termion
  • scauligi/termion
  • mwilbur/termion
  • curious/termion
  • xPMo/termion
  • lovasoa/termion
  • stillinbeta/termion
  • mjbshaw/termion
  • rabite0/termion
  • akitsu-sanae/termion
  • joshhansen/termion
  • lilydjwg/termion
  • kivimango/termion
  • gintsp/termion
  • 4lDO2/termion
  • denis/termion
  • needle/termion
  • rw_van/termion
  • nfachan2/termion
  • FuchsiaFrog/termion-horizontal-scroll
  • haraguroicha/termion
  • bugnano/termion
  • nolan1299/termion
  • zethra/termion
  • proehlen/termion
  • kankri/termion
  • ridcully/termion
  • TheLostLambda/termion
  • khs26/termion
  • lassipulkkinen/termion
  • amigo-rich/termion
  • nbdd0121/termion
  • jean-airoldie/termion
  • andreasblum/termion
  • luqmana/termion
  • orhun/termion
  • joshka/termion
  • nicholasdower/termion
  • vlad/termion
49 results
Show changes
<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,38 +2,71 @@ use std::io::{self, Read};
use std::sync::mpsc;
use std::thread;
/// Construct an asynchronous handle to the standard input.
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.
///
/// This allows you to read from standard input _without blocking_ the current thread.
/// Specifically, it works by firing up another thread to handle the event stream, which will then
/// be buffered in a mpsc queue, which will eventually be read by the current thread.
///
/// Note that this will acquire the Mutex lock on the standard input, making all future stdin
/// construction hang the program.
/// This will not read the piped standard input, but rather read from the TTY device, since reading
/// asyncronized from piped input would rarely make sense. In other words, if you pipe standard
/// output from another process, it won't be reflected in the stream returned by this function, as
/// this represents the TTY device, and not the piped standard input.
pub fn async_stdin() -> AsyncReader {
let (send, recv) = mpsc::channel();
thread::spawn(move || {
let stdin = io::stdin();
for i in stdin.lock().bytes() {
for i in get_tty().unwrap().bytes() {
if send.send(i).is_err() {
return;
}
}
});
AsyncReader {
recv: recv,
}
AsyncReader { recv: recv }
}
/// An asynchronous reader.
///
/// This acts as any other stream, with the exception that reading from it won't block. Instead,
/// the buffer will only be partially updated based on how much the internal buffer holds.
pub struct AsyncReader {
/// The underlying mpsc receiver.
#[doc(hidden)]
pub recv: mpsc::Receiver<io::Result<u8>>,
recv: mpsc::Receiver<io::Result<u8>>,
}
// FIXME: Allow constructing an async reader from an arbitrary stream.
impl Read for AsyncReader {
/// Read from the byte stream.
///
......@@ -44,15 +77,15 @@ impl Read for AsyncReader {
let mut total = 0;
loop {
if total >= buf.len() {
break;
}
match self.recv.try_recv() {
Ok(Ok(b)) => {
buf[total] = b;
total += 1;
if total == buf.len() {
break;
}
},
}
Ok(Err(e)) => return Err(e),
Err(_) => break,
}
......
//! Clearing the screen.
use std::fmt;
derive_csi_sequence!("Clear the entire screen.", All, "2J");
derive_csi_sequence!("Clear everything after the cursor.", AfterCursor, "J");
derive_csi_sequence!("Clear everything before the cursor.", BeforeCursor, "1J");
derive_csi_sequence!("Clear the current line.", CurrentLine, "2K");
derive_csi_sequence!("Clear from cursor to newline.", UntilNewline, "K");
//! Color managemement.
//!
//! # Example
//!
//! ```rust
//! use termion::color;
//!
//! fn main() {
//! println!("{}Red", color::Fg(color::Red));
//! println!("{}Blue", color::Fg(color::Blue));
//! println!("{}Back again", color::Fg(color::Reset));
//! }
//! ```
use async::async_stdin;
use numtoa::NumToA;
use raw::CONTROL_SEQUENCE_TIMEOUT;
use std::env;
use std::fmt;
use std::fmt::Debug;
use std::io::{self, Read, Write};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Once;
use std::time::{Duration, SystemTime};
static NO_COLOR: AtomicBool = AtomicBool::new(false);
static INITIALIZER: Once = Once::new();
/// Returns true if the `NO_COLOR` environment variable is set.
///
/// See <https://no-color.org> for more information.
fn is_no_color_set() -> bool {
!std::env::var("NO_COLOR")
.unwrap_or("".to_string())
.is_empty()
}
/// Returns true if ANSI colors are disabled.
///
/// This function checks the `NO_COLOR` environment variable
/// and it is memoized.
fn ansi_color_disabled() -> bool {
INITIALIZER.call_once(|| {
NO_COLOR.store(is_no_color_set(), Ordering::SeqCst);
});
NO_COLOR.load(Ordering::SeqCst)
}
/// A terminal color.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Color {
/// Black.
Black,
/// Red.
Red,
/// Green.
Green,
/// Yellow.
Yellow,
/// Blue.
Blue,
/// Megenta.
Magenta,
/// Cyan.
Cyan,
/// White.
White,
/// High-intensity black.
LightBlack,
/// High-intensity red.
LightRed,
/// High-intensity green.
LightGreen,
/// High-intensity yellow.
LightYellow,
/// High-intensity blue.
LightBlue,
/// High-intensity magenta.
LightMagenta,
/// High-intensity cyan.
LightCyan,
/// High-intensity white.
LightWhite,
/// 216-color (r, g, b ≤ 5) RGB.
Rgb(u8, u8, u8),
/// Grayscale (max value: 24)
Grayscale(u8),
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.
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result;
}
use Color::*;
macro_rules! derive_color {
($doc:expr, $name:ident, $value:expr) => {
#[doc = $doc]
#[derive(Copy, Clone, Debug)]
pub struct $name;
impl Color {
/// Get the corresponding ANSI value.
///
/// Panics
/// ======
impl Color for $name {
#[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
if ansi_color_disabled() {
return Ok(());
}
f.write_str(self.fg_str())
}
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
if ansi_color_disabled() {
return Ok(());
}
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")
}
}
};
}
derive_color!("Black.", Black, "0");
derive_color!("Red.", Red, "1");
derive_color!("Green.", Green, "2");
derive_color!("Yellow.", Yellow, "3");
derive_color!("Blue.", Blue, "4");
derive_color!("Magenta.", Magenta, "5");
derive_color!("Cyan.", Cyan, "6");
derive_color!("White.", White, "7");
derive_color!("High-intensity light black.", LightBlack, "8");
derive_color!("High-intensity light red.", LightRed, "9");
derive_color!("High-intensity light green.", LightGreen, "10");
derive_color!("High-intensity light yellow.", LightYellow, "11");
derive_color!("High-intensity light blue.", LightBlue, "12");
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 dyn 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);
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
);
AnsiValue(16 + 36 * r + 6 * g + b)
}
/// Grayscale color.
///
/// 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 {
self.debug_check();
match self {
Black => 0x0,
Red => 0x1,
Green => 0x2,
Yellow => 0x3,
Blue => 0x4,
Magenta => 0x5,
Cyan => 0x6,
White => 0x7,
LightBlack => 0x8,
LightRed => 0x9,
LightGreen => 0xA,
LightYellow => 0xB,
LightBlue => 0xC,
LightMagenta => 0xD,
LightCyan => 0xE,
LightWhite => 0xF,
Rgb(r, g, b) => 16 + 36 * r + 6 * g + b,
Grayscale(shade) => 0xE8 + shade,
/// 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
);
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 {
if ansi_color_disabled() {
return Ok(());
}
f.write_str(&self.fg_string())
}
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
if ansi_color_disabled() {
return Ok(());
}
f.write_str(&self.bg_string())
}
}
/// A truecolor RGB.
#[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()
}
fn debug_check(self) {
match self {
Rgb(r, g, 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);
},
Grayscale(shade) => {
// 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);
},
_ => {},
/// 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 {
if ansi_color_disabled() {
return Ok(());
}
f.write_str(&self.fg_string())
}
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
if ansi_color_disabled() {
return Ok(());
}
f.write_str(&self.bg_string())
}
}
#[cfg(test)]
mod test {
use super::*;
/// Reset colors to defaults.
#[derive(Debug, Clone, Copy)]
pub struct Reset;
const RESET_FG: &str = csi!("39m");
const RESET_BG: &str = csi!("49m");
#[test]
fn test_rgb() {
assert_eq!(Color::Rgb(2, 3, 4).to_ansi_val(), 110);
assert_eq!(Color::Rgb(2, 1, 4).to_ansi_val(), 98);
assert_eq!(Color::Rgb(5, 1, 4).to_ansi_val(), 206);
impl Reset {
/// Returns the ANSI sequence as a string.
pub fn fg_str(self) -> &'static str {
RESET_FG
}
#[test]
fn test_grayscale() {
assert_eq!(Color::Grayscale(2).to_ansi_val(), 234);
assert_eq!(Color::Grayscale(5).to_ansi_val(), 237);
/// Returns the ANSI sequence as a string.
pub fn bg_str(self) -> &'static str {
RESET_BG
}
#[test]
fn test_normal() {
assert_eq!(Color::Black.to_ansi_val(), 0);
assert_eq!(Color::Green.to_ansi_val(), 2);
assert_eq!(Color::White.to_ansi_val(), 7);
}
impl Color for Reset {
#[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(RESET_FG)
}
#[test]
fn test_hi() {
assert_eq!(Color::LightRed.to_ansi_val(), 9);
assert_eq!(Color::LightCyan.to_ansi_val(), 0xE);
assert_eq!(Color::LightWhite.to_ansi_val(), 0xF);
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(RESET_BG)
}
}
#[cfg(debug)]
#[should_panic]
#[test]
fn test_bound_check_rgb() {
Color::Rgb(3, 9, 1).debug_check();
/// A foreground color.
#[derive(Debug, Clone, Copy)]
pub struct Fg<C: Color>(pub C);
impl<C: Color> fmt::Display for Fg<C> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.write_fg(f)
}
}
/// A background color.
#[derive(Debug, Clone, Copy)]
pub struct Bg<C: Color>(pub C);
impl<C: Color> fmt::Display for Bg<C> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.write_bg(f)
}
#[cfg(debug)]
#[should_panic]
#[test]
fn test_bound_check_rgb_2() {
Color::Rgb(3, 6, 1).debug_check();
}
/// 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,
})
}
}
#[cfg(debug)]
#[should_panic]
#[test]
fn test_bound_check_grayscale() {
Color::Grayscale(25).debug_check();
}
/// Detect a color using OSC 4.
fn detect_color(stdout: &mut dyn Write, stdin: &mut dyn 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)
}
use std::io::{self, Write};
use {Color, Style};
/// Extension to the `Write` trait.
///
/// This extension to the `Write` trait is capable of producing the correct ANSI escape sequences
/// for given commands, effectively controlling the terminal.
pub trait TermWrite {
/// Print the CSI (control sequence introducer) followed by a byte string.
fn csi(&mut self, b: &[u8]) -> io::Result<usize>;
/// Print OSC (operating system command) followed by a byte string.
fn osc(&mut self, b: &[u8]) -> io::Result<usize>;
/// Print OSC (device control string) followed by a byte string.
fn dsc(&mut self, b: &[u8]) -> io::Result<usize>;
/// Clear the entire screen.
fn clear(&mut self) -> io::Result<usize> {
self.csi(b"2J")
}
/// Clear everything _after_ the cursor.
fn clear_after(&mut self) -> io::Result<usize> {
self.csi(b"J")
}
/// Clear everything _before_ the cursor.
fn clear_before(&mut self) -> io::Result<usize> {
self.csi(b"1J")
}
/// Clear the current line.
fn clear_line(&mut self) -> io::Result<usize> {
self.csi(b"2K")
}
/// Clear from the cursor until newline.
fn clear_until_newline(&mut self) -> io::Result<usize> {
self.csi(b"K")
}
/// Show the cursor.
fn show_cursor(&mut self) -> io::Result<usize> {
self.csi(b"?25h")
}
/// Hide the cursor.
fn hide_cursor(&mut self) -> io::Result<usize> {
self.csi(b"?25l")
}
// TODO
// fn mode
/// Reset the rendition mode.
///
/// This will reset both the current style and color.
fn reset(&mut self) -> io::Result<usize> {
self.csi(b"m")
}
/// Restore the defaults.
///
/// This will reset color, position, cursor state, and so on. It is recommended that you use
/// this before you exit your program, to avoid messing up the user's terminal.
fn restore(&mut self) -> io::Result<usize> {
Ok(try!(self.reset()) + try!(self.clear()) + try!(self.goto(0, 0)) + try!(self.show_cursor()))
}
/// Go to a given position.
///
/// The position is 0-based.
fn goto(&mut self, mut x: u16, mut y: u16) -> io::Result<usize> {
x += 1;
y += 1;
self.csi(&[
b'0' + (y / 10000) as u8,
b'0' + (y / 1000) as u8 % 10,
b'0' + (y / 100) as u8 % 10,
b'0' + (y / 10) as u8 % 10,
b'0' + y as u8 % 10,
b';',
b'0' + (x / 10000) as u8,
b'0' + (x / 1000) as u8 % 10,
b'0' + (x / 100) as u8 % 10,
b'0' + (x / 10) as u8 % 10,
b'0' + x as u8 % 10,
b'H',
])
}
/// Set graphic rendition.
fn rendition(&mut self, r: u8) -> io::Result<usize> {
self.csi(&[
b'0' + r / 100,
b'0' + r / 10 % 10,
b'0' + r % 10,
b'm',
])
}
/// Set foreground color
fn color(&mut self, color: Color) -> io::Result<usize> {
let ansi = color.to_ansi_val();
self.csi(&[
b'3',
b'8',
b';',
b'5',
b';',
b'0' + ansi / 100,
b'0' + ansi / 10 % 10,
b'0' + ansi % 10,
b'm',
])
}
/// Set background color
fn bg_color(&mut self, color: Color) -> io::Result<usize> {
let ansi = color.to_ansi_val();
self.csi(&[
b'4',
b'8',
b';',
b'5',
b';',
b'0' + ansi / 100,
b'0' + ansi / 10 % 10,
b'0' + ansi % 10,
b'm',
])
}
/// Set rendition mode (SGR).
fn style(&mut self, mode: Style) -> io::Result<usize> {
self.rendition(mode as u8)
}
}
impl<W: Write> TermWrite for W {
fn csi(&mut self, b: &[u8]) -> io::Result<usize> {
Ok(try!(self.write(b"\x1B[")) + try!(self.write(b)))
}
fn osc(&mut self, b: &[u8]) -> io::Result<usize> {
Ok(try!(self.write(b"\x1B]")) + try!(self.write(b)))
}
fn dsc(&mut self, b: &[u8]) -> io::Result<usize> {
Ok(try!(self.write(b"\x1BP")) + try!(self.write(b)))
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Cursor;
#[test]
fn test_csi() {
let mut buf = Cursor::new(Vec::new());
buf.csi(b"bluh").unwrap();
assert_eq!(buf.get_ref(), b"\x1B[bluh");
buf.csi(b"blah").unwrap();
assert_eq!(buf.get_ref(), b"\x1B[bluh\x1B[blah");
}
#[test]
fn test_csi_partial() {
let mut buf = [0; 3];
let mut buf = &mut buf[..];
assert_eq!(buf.csi(b"blu").unwrap(), 3);
assert_eq!(buf.csi(b"").unwrap(), 0);
assert_eq!(buf.csi(b"nooooo").unwrap(), 0);
}
#[test]
fn test_osc() {
let mut buf = Cursor::new(Vec::new());
buf.osc(b"bluh").unwrap();
assert_eq!(buf.get_ref(), b"\x1B]bluh");
buf.osc(b"blah").unwrap();
assert_eq!(buf.get_ref(), b"\x1B]bluh\x1B]blah");
}
#[test]
fn test_osc_partial() {
let mut buf = [0; 3];
let mut buf = &mut buf[..];
assert_eq!(buf.osc(b"blu").unwrap(), 3);
assert_eq!(buf.osc(b"").unwrap(), 0);
assert_eq!(buf.osc(b"nooooo").unwrap(), 0);
}
#[test]
fn test_dsc() {
let mut buf = Cursor::new(Vec::new());
buf.dsc(b"bluh").unwrap();
assert_eq!(buf.get_ref(), b"\x1BPbluh");
buf.dsc(b"blah").unwrap();
assert_eq!(buf.get_ref(), b"\x1BPbluh\x1BPblah");
}
#[test]
fn test_dsc_partial() {
let mut buf = [0; 3];
let mut buf = &mut buf[..];
assert_eq!(buf.dsc(b"blu").unwrap(), 3);
assert_eq!(buf.dsc(b"").unwrap(), 0);
assert_eq!(buf.dsc(b"nooooo").unwrap(), 0);
}
#[test]
fn test_clear() {
let mut buf = Cursor::new(Vec::new());
buf.clear().unwrap();
assert_eq!(buf.get_ref(), b"\x1B[2J");
buf.clear().unwrap();
assert_eq!(buf.get_ref(), b"\x1B[2J\x1B[2J");
}
#[test]
fn test_goto() {
let mut buf = Cursor::new(Vec::new());
buf.goto(34, 43).unwrap();
assert_eq!(buf.get_ref(), b"\x1B[00044;00035H");
buf.goto(24, 45).unwrap();
assert_eq!(buf.get_ref(), b"\x1B[00044;00035H\x1B[00046;00025H");
}
#[test]
fn test_style() {
use Style;
let mut buf = Cursor::new(Vec::new());
buf.style(Style::Bold).unwrap();
assert_eq!(buf.get_ref(), b"\x1B[001m");
buf.style(Style::Italic).unwrap();
assert_eq!(buf.get_ref(), b"\x1B[001m\x1B[003m");
}
}
//! Cursor movement.
use async::async_stdin_until;
use numtoa::NumToA;
use raw::CONTROL_SEQUENCE_TIMEOUT;
use std::fmt;
use std::io::{self, Error, ErrorKind, Read, Write};
use std::ops;
use std::time::{Duration, SystemTime};
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");
derive_csi_sequence!(
"Change the cursor style to blinking block",
BlinkingBlock,
"\x31 q"
);
derive_csi_sequence!(
"Change the cursor style to steady block",
SteadyBlock,
"\x32 q"
);
derive_csi_sequence!(
"Change the cursor style to blinking underline",
BlinkingUnderline,
"\x33 q"
);
derive_csi_sequence!(
"Change the cursor style to steady underline",
SteadyUnderline,
"\x34 q"
);
derive_csi_sequence!(
"Change the cursor style to blinking bar",
BlinkingBar,
"\x35 q"
);
derive_csi_sequence!("Change the cursor style to steady bar", SteadyBar, "\x36 q");
/// Goto some position ((1,1)-based).
///
/// # Why one-based?
///
/// ANSI escapes are very poorly designed, and one of the many odd aspects is being one-based. This
/// can be quite strange at first, but it is not that big of an obstruction once you get used to
/// it.
///
/// # Example
///
/// ```rust
/// extern crate termion;
///
/// fn main() {
/// print!("{}{}Stuff", termion::clear::All, termion::cursor::Goto(5, 3));
/// }
/// ```
#[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)
}
}
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, "\x1B[{};{}H", self.1, self.0)
}
}
/// Move cursor left.
#[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, "\x1B[{}D", self.0)
}
}
/// Move cursor right.
#[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, "\x1B[{}C", self.0)
}
}
/// Move cursor up.
#[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, "\x1B[{}A", self.0)
}
}
/// Move cursor down.
#[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, "\x1B[{}B", self.0)
}
}
/// 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()
}
}
//! Mouse and key events.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::io::{Error, ErrorKind};
use std::str;
/// An event reported by the terminal.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Event {
/// A key press.
Key(Key),
/// A mouse button press, release or wheel use at specific coordinates.
Mouse(MouseEvent),
/// An event that cannot currently be evaluated.
Unsupported(Vec<u8>),
}
/// A mouse related event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum MouseEvent {
/// A mouse button was pressed.
///
/// The coordinates are one-based.
Press(MouseButton, u16, u16),
/// A mouse button was released.
///
/// The coordinates are one-based.
Release(u16, u16),
/// A mouse button is held over the given coordinates.
///
/// The coordinates are one-based.
Hold(u16, u16),
}
/// A mouse button.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum MouseButton {
/// The left mouse button.
Left,
/// The right mouse button.
Right,
/// The middle mouse button.
Middle,
/// Mouse wheel is going up.
///
/// This event is typically only used with Mouse::Press.
WheelUp,
/// Mouse wheel is going down.
///
/// This event is typically only used with Mouse::Press.
WheelDown,
/// Mouse wheel is going left. Only supported in certain terminals.
///
/// This event is typically only used with Mouse::Press.
WheelLeft,
/// Mouse wheel is going right. Only supported in certain terminals.
///
/// This event is typically only used with Mouse::Press.
WheelRight,
}
/// A key.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Key {
/// Backspace.
Backspace,
/// Left arrow.
Left,
/// Shift Left arrow.
ShiftLeft,
/// Alt Left arrow.
AltLeft,
/// Ctrl Left arrow.
CtrlLeft,
/// Right arrow.
Right,
/// Shift Right arrow.
ShiftRight,
/// Alt Right arrow.
AltRight,
/// Ctrl Right arrow.
CtrlRight,
/// Up arrow.
Up,
/// Shift Up arrow.
ShiftUp,
/// Alt Up arrow.
AltUp,
/// Ctrl Up arrow.
CtrlUp,
/// Down arrow.
Down,
/// Shift Down arrow.
ShiftDown,
/// Alt Down arrow.
AltDown,
/// Ctrl Down arrow
CtrlDown,
/// Home key.
Home,
/// Ctrl Home key.
CtrlHome,
/// End key.
End,
/// Ctrl End key.
CtrlEnd,
/// Page Up key.
PageUp,
/// Page Down key.
PageDown,
/// Backward Tab key.
BackTab,
/// Delete key.
Delete,
/// Insert key.
Insert,
/// Function keys.
///
/// Only function keys 1 through 12 are supported.
F(u8),
/// Normal character.
Char(char),
/// Alt modified character.
Alt(char),
/// Ctrl modified character.
///
/// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals.
Ctrl(char),
/// Null byte.
Null,
/// Esc key.
Esc,
#[doc(hidden)]
__IsNotComplete,
}
/// Parse an Event from `item` and possibly subsequent bytes through `iter`.
pub fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, Error>
where
I: Iterator<Item = Result<u8, Error>>,
{
let error = Error::new(ErrorKind::Other, "Could not parse an event");
match item {
b'\x1B' => {
// This is an escape character, leading a control sequence.
Ok(match iter.next() {
Some(Ok(b'O')) => {
match iter.next() {
// F1-F4
Some(Ok(val @ b'P'..=b'S')) => Event::Key(Key::F(1 + val - b'P')),
_ => return Err(error),
}
}
Some(Ok(b'[')) => {
// This is a CSI sequence.
parse_csi(iter).ok_or(error)?
}
Some(Ok(c)) => {
let ch = parse_utf8_char(c, iter)?;
Event::Key(Key::Alt(ch))
}
Some(Err(_)) | None => return Err(error),
})
}
b'\n' | b'\r' => Ok(Event::Key(Key::Char('\n'))),
b'\t' => Ok(Event::Key(Key::Char('\t'))),
b'\x7F' => Ok(Event::Key(Key::Backspace)),
c @ b'\x01'..=b'\x1A' => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1 + b'a') as char))),
c @ b'\x1C'..=b'\x1F' => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1C + b'4') as char))),
b'\0' => Ok(Event::Key(Key::Null)),
c => Ok({
let ch = parse_utf8_char(c, iter)?;
Event::Key(Key::Char(ch))
}),
}
}
/// Parses a CSI sequence, just after reading ^[
///
/// Returns None if an unrecognized sequence is found.
fn parse_csi<I>(iter: &mut I) -> Option<Event>
where
I: Iterator<Item = Result<u8, Error>>,
{
Some(match iter.next() {
Some(Ok(b'[')) => match iter.next() {
Some(Ok(val @ b'A'..=b'E')) => Event::Key(Key::F(1 + val - b'A')),
_ => return None,
},
Some(Ok(b'D')) => Event::Key(Key::Left),
Some(Ok(b'C')) => Event::Key(Key::Right),
Some(Ok(b'A')) => Event::Key(Key::Up),
Some(Ok(b'B')) => Event::Key(Key::Down),
Some(Ok(b'H')) => Event::Key(Key::Home),
Some(Ok(b'F')) => Event::Key(Key::End),
Some(Ok(b'Z')) => Event::Key(Key::BackTab),
Some(Ok(b'M')) => {
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only).
let mut next = || iter.next().unwrap().unwrap();
let cb = next() as i8 - 32;
// (1, 1) are the coords for upper left.
let cx = next().saturating_sub(32) as u16;
let cy = next().saturating_sub(32) as u16;
Event::Mouse(match cb & 0b11 {
0 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelUp, cx, cy)
} else {
MouseEvent::Press(MouseButton::Left, cx, cy)
}
}
1 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelDown, cx, cy)
} else {
MouseEvent::Press(MouseButton::Middle, cx, cy)
}
}
2 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelLeft, cx, cy)
} else {
MouseEvent::Press(MouseButton::Right, cx, cy)
}
}
3 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelRight, cx, cy)
} else {
MouseEvent::Release(cx, cy)
}
}
_ => return None,
})
}
Some(Ok(b'<')) => {
// xterm mouse encoding:
// ESC [ < Cb ; Cx ; Cy (;) (M or m)
let mut buf = Vec::new();
let mut c = iter.next().unwrap().unwrap();
while match c {
b'm' | b'M' => false,
_ => true,
} {
buf.push(c);
c = iter.next().unwrap().unwrap();
}
let str_buf = String::from_utf8(buf).unwrap();
let nums = &mut str_buf.split(';');
let cb = nums.next().unwrap().parse::<u16>().unwrap();
let cx = nums.next().unwrap().parse::<u16>().unwrap();
let cy = nums.next().unwrap().parse::<u16>().unwrap();
let event = match cb {
0..=2 | 64..=67 => {
let button = match cb {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
64 => MouseButton::WheelUp,
65 => MouseButton::WheelDown,
66 => MouseButton::WheelLeft,
67 => MouseButton::WheelRight,
_ => unreachable!(),
};
match c {
b'M' => MouseEvent::Press(button, cx, cy),
b'm' => MouseEvent::Release(cx, cy),
_ => return None,
}
}
32 => MouseEvent::Hold(cx, cy),
3 => MouseEvent::Release(cx, cy),
_ => return None,
};
Event::Mouse(event)
}
Some(Ok(c @ b'0'..=b'9')) => {
// Numbered escape code.
let mut buf = Vec::new();
buf.push(c);
let mut c = iter.next().unwrap().unwrap();
// The final byte of a CSI sequence can be in the range 64-126, so
// let's keep reading anything else.
while c < 64 || c > 126 {
buf.push(c);
c = iter.next().unwrap().unwrap();
}
match c {
// rxvt mouse encoding:
// ESC [ Cb ; Cx ; Cy ; M
b'M' => {
let str_buf = String::from_utf8(buf).unwrap();
let nums: Vec<u16> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
let cb = nums[0];
let cx = nums[1];
let cy = nums[2];
let event = match cb {
32 => MouseEvent::Press(MouseButton::Left, cx, cy),
33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
34 => MouseEvent::Press(MouseButton::Right, cx, cy),
35 => MouseEvent::Release(cx, cy),
64 => MouseEvent::Hold(cx, cy),
96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
_ => return None,
};
Event::Mouse(event)
}
// Special key code.
b'~' => {
let str_buf = String::from_utf8(buf).unwrap();
// This CSI sequence can be a list of semicolon-separated
// numbers.
let nums: Vec<u8> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
if nums.is_empty() {
return None;
}
// TODO: handle multiple values for key modififiers (ex: values
// [3, 2] means Shift+Delete)
if nums.len() > 1 {
return None;
}
match nums[0] {
1 | 7 => Event::Key(Key::Home),
2 => Event::Key(Key::Insert),
3 => Event::Key(Key::Delete),
4 | 8 => Event::Key(Key::End),
5 => Event::Key(Key::PageUp),
6 => Event::Key(Key::PageDown),
v @ 11..=15 => Event::Key(Key::F(v - 10)),
v @ 17..=21 => Event::Key(Key::F(v - 11)),
v @ 23..=24 => Event::Key(Key::F(v - 12)),
_ => return None,
}
}
b'A' | b'B' | b'C' | b'D' | b'F' | b'H' => {
let str_buf = String::from_utf8(buf).unwrap();
// This CSI sequence can be a list of semicolon-separated
// numbers.
let nums: Vec<u8> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
if !(nums.len() == 2 && nums[0] == 1) {
return None;
}
match nums[1] {
2 => {
// Shift Modifier
match c {
b'D' => Event::Key(Key::ShiftLeft),
b'C' => Event::Key(Key::ShiftRight),
b'A' => Event::Key(Key::ShiftUp),
b'B' => Event::Key(Key::ShiftDown),
_ => return None,
}
}
3 => {
// Alt Modifier
match c {
b'D' => Event::Key(Key::AltLeft),
b'C' => Event::Key(Key::AltRight),
b'A' => Event::Key(Key::AltUp),
b'B' => Event::Key(Key::AltDown),
_ => return None,
}
}
5 => {
// Ctrl Modifier
match c {
b'D' => Event::Key(Key::CtrlLeft),
b'C' => Event::Key(Key::CtrlRight),
b'A' => Event::Key(Key::CtrlUp),
b'B' => Event::Key(Key::CtrlDown),
b'H' => Event::Key(Key::CtrlHome),
b'F' => Event::Key(Key::CtrlEnd),
_ => return None,
}
}
_ => return None,
}
}
_ => return None,
}
}
_ => return None,
})
}
/// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char.
fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char, Error>
where
I: Iterator<Item = Result<u8, Error>>,
{
let error = Err(Error::new(
ErrorKind::Other,
"Input character is not valid UTF-8",
));
if c.is_ascii() {
Ok(c as char)
} else {
let bytes = &mut Vec::new();
bytes.push(c);
loop {
match iter.next() {
Some(Ok(next)) => {
bytes.push(next);
if let Ok(st) = str::from_utf8(bytes) {
return Ok(st.chars().next().unwrap());
}
if bytes.len() >= 4 {
return error;
}
}
_ => return error,
}
}
}
}
#[cfg(test)]
#[test]
fn test_parse_utf8() {
let st = "abcéŷ¤£€ù%323";
let ref mut bytes = st.bytes().map(|x| Ok(x));
let chars = st.chars();
for c in chars {
let b = bytes.next().unwrap().unwrap();
assert!(c == parse_utf8_char(b, bytes).unwrap());
}
}
//! User input.
use std::io::{self, Read, Write};
use std::thread;
use std::sync::mpsc;
use {IntoRawMode, AsyncReader};
#[cfg(feature = "nightly")]
use std::io::{Chars, CharsError};
/// A key.
#[derive(Debug)]
pub enum Key {
/// Backspace.
Backspace,
/// Left arrow.
Left,
/// Right arrow.
Right,
/// Up arrow.
Up,
/// Down arrow.
Down,
/// Normal character.
Char(char),
/// Alt modified character.
Alt(char),
/// Ctrl modified character.
///
/// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals.
Ctrl(char),
/// Invalid character code.
Invalid,
/// IO error.
Error(io::Error),
/// Null byte.
Null,
use std::ops;
use std::os::fd::AsFd;
#[allow(missing_docs)]
#[doc(hidden)]
__IsNotComplete
}
use event::{self, Event, Key};
use raw::IntoRawMode;
/// An iterator over input keys.
#[cfg(feature = "nightly")]
pub struct Keys<I> {
chars: I,
}
#[cfg(feature = "nightly")]
impl<I: Iterator<Item = Result<char, CharsError>>> Iterator for Keys<I> {
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('\n')) | Some(Ok('\r')) => Some(Key::Char('\n')),
Some(Ok('\t')) => Some(Key::Char('\t')),
Some(Ok('\x7F')) => Some(Key::Backspace),
Some(Ok(c @ '\x01' ... '\x1A')) => Some(Key::Ctrl((c as u8 - 0x1 + b'a') as char)),
Some(Ok(c @ '\x1C' ... '\x1F')) => Some(Key::Ctrl((c as u8 - 0x1C + b'4') as char)),
None => None,
Some(Ok('\0')) => Some(Key::Null),
Some(Ok(c)) => Some(Key::Char(c)),
Some(Err(e)) => Some(Key::Error(io::Error::new(io::ErrorKind::InvalidData, e))),
pub struct Keys<R> {
iter: Events<R>,
}
impl<R: Read> Iterator for Keys<R> {
type Item = Result<Key, io::Error>;
fn next(&mut self) -> Option<Result<Key, io::Error>> {
loop {
match self.iter.next() {
Some(Ok(Event::Key(k))) => return Some(Ok(k)),
Some(Ok(_)) => continue,
Some(Err(e)) => return Some(Err(e)),
None => return None,
};
}
}
}
/// An iterator over input events.
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>> {
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
self.leftover = None;
return Some(parse_event(c, &mut source.bytes()));
}
// 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), vec![b'\x1B'])),
c => parse_event(c, &mut source.bytes()),
},
Ok(2) => {
let option_iter = &mut Some(buf[1]).into_iter();
let result = {
let mut iter = option_iter.map(|c| Ok(c)).chain(source.bytes());
parse_event(buf[0], &mut iter)
};
// If the option_iter wasn't consumed, keep the byte for later.
self.leftover = option_iter.next();
result
}
Ok(_) => unreachable!(),
Err(e) => Err(e),
};
Some(res)
}
}
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];
let result = {
let mut iter = iter.inspect(|byte| {
if let &Ok(byte) = byte {
buf.push(byte);
}
});
event::parse_event(item, &mut iter)
};
result
.or(Ok(Event::Unsupported(buf.clone())))
.map(|e| (e, buf))
}
/// Extension to `Read` trait.
pub trait TermRead {
/// An iterator over input events.
fn events(self) -> Events<Self>
where
Self: Sized;
/// An iterator over key inputs.
#[cfg(feature = "nightly")]
fn keys(self) -> Keys<Chars<Self>> where Self: Sized;
fn keys(self) -> Keys<Self>
where
Self: Sized;
/// Read a password.
/// Read a line.
///
/// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
/// complete the password input.
fn read_passwd<W: Write>(&mut self, writer: &mut W) -> io::Result<Option<String>>;
/// complete the input.
fn read_line(&mut self) -> io::Result<Option<String>>;
/// Turn the reader into a asynchronous reader.
/// Read a password.
///
/// This will spawn up another thread listening for event, buffering them in a mpsc queue.
fn into_async(self) -> AsyncReader where Self: Send;
/// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
/// complete the input.
fn read_passwd<W: Write + AsFd>(&mut self, writer: &mut W) -> io::Result<Option<String>> {
let _raw = writer.into_raw_mode()?;
self.read_line()
}
}
impl<R: Read> TermRead for R {
#[cfg(feature = "nightly")]
fn keys(self) -> Keys<Chars<R>> {
impl<R: Read + TermReadEventsAndRaw> TermRead for R {
fn events(self) -> Events<Self> {
Events {
inner: self.events_and_raw(),
}
}
fn keys(self) -> Keys<Self> {
Keys {
chars: self.chars(),
iter: self.events(),
}
}
fn read_passwd<W: Write>(&mut self, writer: &mut W) -> io::Result<Option<String>> {
let _raw = try!(writer.into_raw_mode());
let mut passbuf = Vec::with_capacity(30);
fn read_line(&mut self) -> io::Result<Option<String>> {
let mut buf = Vec::with_capacity(30);
for c in self.bytes() {
match c {
Err(e) => return Err(e),
Ok(0) | Ok(3) | Ok(4) => return Ok(None),
Ok(0x7f) => {
buf.pop();
}
Ok(b'\n') | Ok(b'\r') => break,
Ok(c) => passbuf.push(c),
Ok(c) => buf.push(c),
}
}
let passwd = try!(String::from_utf8(passbuf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)));
let string =
String::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(Some(string))
}
}
/// 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;
}
Ok(Some(passwd))
impl<R: Read> TermReadEventsAndRaw for R {
fn events_and_raw(self) -> EventsAndRaw<Self> {
EventsAndRaw {
source: self,
leftover: None,
}
}
}
fn into_async(self) -> AsyncReader where R: Send + 'static {
let (send, recv) = mpsc::channel();
/// A sequence of escape codes to enable terminal mouse support.
const ENTER_MOUSE_SEQUENCE: &'static str = csi!("?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h");
thread::spawn(move || {
let mut reader = self;
loop {
let mut buf = [0];
if send.send(if let Err(k) = reader.read(&mut buf) {
Err(k)
} else {
Ok(buf[0])
}).is_err() {
return;
};
}
});
/// A sequence of escape codes to disable terminal mouse support.
const EXIT_MOUSE_SEQUENCE: &'static str = csi!("?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l");
/// A terminal with added mouse support.
///
/// This can be obtained through the `From` implementations.
pub struct MouseTerminal<W: Write> {
term: W,
}
AsyncReader {
recv: recv,
impl<W: Write> From<W> for MouseTerminal<W> {
fn from(mut from: W) -> MouseTerminal<W> {
from.write_all(ENTER_MOUSE_SEQUENCE.as_bytes()).unwrap();
MouseTerminal { term: from }
}
}
impl<W: Write> Drop for MouseTerminal<W> {
fn drop(&mut self) {
self.term.write_all(EXIT_MOUSE_SEQUENCE.as_bytes()).unwrap();
}
}
impl<W: Write> ops::Deref for MouseTerminal<W> {
type Target = W;
fn deref(&self) -> &W {
&self.term
}
}
impl<W: Write> ops::DerefMut for MouseTerminal<W> {
fn deref_mut(&mut self) -> &mut W {
&mut self.term
}
}
impl<W: Write> Write for MouseTerminal<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.term.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.term.flush()
}
}
#[cfg(unix)]
mod unix_impl {
use super::*;
use std::os::unix::io::{AsRawFd, RawFd};
impl<W: Write + AsRawFd> AsRawFd for MouseTerminal<W> {
fn as_raw_fd(&self) -> RawFd {
self.term.as_raw_fd()
}
}
}
#[cfg(test)]
mod test {
#[cfg(feature = "nightly")]
use super::*;
use event::{Event, Key, MouseButton, MouseEvent};
#[test]
fn test_keys() {
use {TermRead, Key};
let mut i = b"\x1Bayo\x7F\x1B[D".keys();
assert_eq!(i.next(), Some(Key::Alt('a')));
assert_eq!(i.next(), Some(Key::Char('y')));
assert_eq!(i.next(), Some(Key::Char('o')));
assert_eq!(i.next(), Some(Key::Backspace));
assert_eq!(i.next(), Some(Key::Left));
assert_eq!(i.next(), None);
assert_eq!(i.next().unwrap().unwrap(), Key::Alt('a'));
assert_eq!(i.next().unwrap().unwrap(), Key::Char('y'));
assert_eq!(i.next().unwrap().unwrap(), Key::Char('o'));
assert_eq!(i.next().unwrap().unwrap(), Key::Backspace);
assert_eq!(i.next().unwrap().unwrap(), Key::Left);
assert!(i.next().is_none());
}
#[test]
fn test_events() {
let mut i = 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"
.events();
assert_eq!(
i.next().unwrap().unwrap(),
Event::Unsupported(vec![0x1B, b'[', 0x00])
);
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b')));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('c')));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Backspace));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Left));
assert_eq!(
i.next().unwrap().unwrap(),
Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 2, 4))
);
assert_eq!(
i.next().unwrap().unwrap(),
Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4))
);
assert_eq!(
i.next().unwrap().unwrap(),
Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4))
);
assert_eq!(
i.next().unwrap().unwrap(),
Event::Mouse(MouseEvent::Release(2, 4))
);
assert_eq!(
i.next().unwrap().unwrap(),
Event::Mouse(MouseEvent::Release(2, 4))
);
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b')));
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();
for i in 1..5 {
assert_eq!(st.next().unwrap().unwrap(), Key::F(i));
}
let mut st = b"\x1B[11~\x1B[12~\x1B[13~\x1B[14~\x1B[15~\
\x1B[17~\x1B[18~\x1B[19~\x1B[20~\x1B[21~\x1B[23~\x1B[24~"
.keys();
for i in 1..13 {
assert_eq!(st.next().unwrap().unwrap(), Key::F(i));
}
}
#[test]
fn test_special_keys() {
let mut st = b"\x1B[2~\x1B[H\x1B[7~\x1B[5~\x1B[3~\x1B[F\x1B[8~\x1B[6~".keys();
assert_eq!(st.next().unwrap().unwrap(), Key::Insert);
assert_eq!(st.next().unwrap().unwrap(), Key::Home);
assert_eq!(st.next().unwrap().unwrap(), Key::Home);
assert_eq!(st.next().unwrap().unwrap(), Key::PageUp);
assert_eq!(st.next().unwrap().unwrap(), Key::Delete);
assert_eq!(st.next().unwrap().unwrap(), Key::End);
assert_eq!(st.next().unwrap().unwrap(), Key::End);
assert_eq!(st.next().unwrap().unwrap(), Key::PageDown);
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 line = a.as_bytes().read_line().unwrap();
let pass = a.as_bytes().read_passwd(&mut std::io::stdout()).unwrap();
// godammit rustc
assert_eq!(line, pass);
if let Some(l) = line {
assert_eq!(Some(l.as_str()), b);
} else {
assert!(b.is_none());
}
}
#[test]
fn test_read() {
let test1 = "this is the first test";
let test2 = "this is the second test";
line_match(test1, Some(test1));
line_match(test2, Some(test2));
}
#[test]
fn test_backspace() {
line_match(
"this is the\x7f first\x7f\x7f test",
Some("this is th fir test"),
);
line_match(
"this is the seco\x7fnd test\x7f",
Some("this is the secnd tes"),
);
}
#[test]
fn test_end() {
line_match(
"abc\nhttps://www.youtube.com/watch?v=dQw4w9WgXcQ",
Some("abc"),
);
line_match(
"hello\rhttps://www.youtube.com/watch?v=yPYZpwSpKmA",
Some("hello"),
);
}
#[test]
fn test_abort() {
line_match("abc\x03https://www.youtube.com/watch?v=dQw4w9WgXcQ", None);
line_match("hello\x04https://www.youtube.com/watch?v=yPYZpwSpKmA", None);
}
}
//! termion is a pure Rust library for reading, manipulating, and handling terminals.
//! Termion is a pure Rust, bindless library for low-level handling, manipulating
//! and reading information about terminals. This provides a full-featured
//! alternative to Termbox.
//!
//! 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))]
//! 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.
//!
//! Supports Redox, Mac OS X, and Linux (or, in general, ANSI terminals).
//!
//! For more information refer to the [README](https://github.com/redox-os/termion).
#![warn(missing_docs)]
extern crate numtoa;
#[cfg(feature = "serde")]
extern crate serde;
#[cfg(target_os = "redox")]
#[path = "sys/redox/mod.rs"]
mod sys;
#[cfg(not(target_os = "redox"))]
extern crate libc;
#[cfg(all(unix, not(target_os = "redox")))]
#[path = "sys/unix/mod.rs"]
mod sys;
#[cfg(not(target_os = "redox"))]
mod termios;
pub use sys::size::terminal_size;
#[cfg(all(unix, not(target_os = "redox")))]
pub use sys::size::terminal_size_pixels;
pub use sys::tty::{get_tty, is_tty};
mod control;
pub use control::TermWrite;
mod r#async;
pub use r#async::{async_stdin, AsyncReader};
mod async;
pub use async::{AsyncReader, async_stdin};
#[macro_use]
mod macros;
pub mod clear;
pub mod color;
pub mod cursor;
pub mod event;
pub mod input;
pub mod raw;
pub mod screen;
pub mod scroll;
pub mod style;
mod input;
pub use input::{TermRead, Key};
#[cfg(feature = "nightly")]
pub use input::Keys;
#[cfg(test)]
mod test {
use std::os::fd::AsFd;
mod raw;
pub use raw::{IntoRawMode, RawTerminal};
use super::sys;
mod size;
pub use size::terminal_size;
#[test]
fn test_get_terminal_attr() {
let stdout = std::io::stdout();
sys::attr::get_terminal_attr(stdout.as_fd()).unwrap();
sys::attr::get_terminal_attr(stdout.as_fd()).unwrap();
sys::attr::get_terminal_attr(stdout.as_fd()).unwrap();
}
mod color;
pub use color::Color;
#[test]
fn test_set_terminal_attr() {
let stdout = std::io::stdout();
let ios = sys::attr::get_terminal_attr(stdout.as_fd()).unwrap();
sys::attr::set_terminal_attr(stdout.as_fd(), &ios).unwrap();
}
mod style;
pub use style::Style;
#[test]
fn test_size() {
sys::size::terminal_size().unwrap();
}
}
/// Create a CSI-introduced sequence.
macro_rules! csi {
($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) };
}
/// Derive a CSI sequence struct.
macro_rules! derive_csi_sequence {
($doc:expr, $name:ident, $value:expr) => {
#[doc = $doc]
#[derive(Copy, Clone)]
pub struct $name;
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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)
}
}
};
}
use std::io::{Write, Error, ErrorKind, Result as IoResult};
use std::ops::{Deref, DerefMut};
//! Managing raw mode.
//!
//! Raw mode is a particular state a TTY can have. It signifies that:
//!
//! 1. No line buffering (the input is given byte-by-byte).
//! 2. The input is not written out, instead it has to be done manually by the programmer.
//! 3. The output is not canonicalized (for example, `\n` means "go one line down", not "line
//! break").
//!
//! It is essential to design terminal programs.
//!
//! # Example
//!
//! ```rust,no_run
//! use termion::raw::IntoRawMode;
//! use std::io::{Write, stdout};
//!
//! let mut stdout = stdout().into_raw_mode()?;
//! write!(stdout, "Hey there.").unwrap();
//! # std::io::Result::Ok(())
//! ```
use std::{
io::{self, Write},
ops,
os::fd::AsFd,
};
use sys::attr::{get_terminal_attr, raw_terminal_attr, set_terminal_attr};
use sys::Termios;
/// The timeout of an escape code control sequence, in milliseconds.
pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100;
/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
/// dropped.
#[cfg(target_os = "redox")]
pub struct RawTerminal<W> {
output: W,
}
#[cfg(target_os = "redox")]
impl<W: Write> Drop for RawTerminal<W> {
fn drop(&mut self) {
use TermControl;
self.csi(b"R");
}
}
#[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> {
///
/// Restoring will entirely bring back the old TTY state.
pub struct RawTerminal<W: Write + AsFd> {
prev_ios: Termios,
output: W,
}
#[cfg(not(target_os = "redox"))]
impl<W> Drop for RawTerminal<W> {
impl<W: Write + AsFd> Drop for RawTerminal<W> {
fn drop(&mut self) {
use termios::set_terminal_attr;
set_terminal_attr(&mut self.prev_ios as *mut _);
let _ = set_terminal_attr(self.output.as_fd(), &self.prev_ios);
}
}
impl<W> Deref for RawTerminal<W> {
impl<W: Write + AsFd> ops::Deref for RawTerminal<W> {
type Target = W;
fn deref(&self) -> &W {
&self.output
}
}
impl<W> DerefMut for RawTerminal<W> {
impl<W: Write + AsFd> ops::DerefMut for RawTerminal<W> {
fn deref_mut(&mut self) -> &mut W {
&mut self.output
}
}
impl<W: Write> Write for RawTerminal<W> {
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
impl<W: Write + AsFd> Write for RawTerminal<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.output.write(buf)
}
fn flush(&mut self) -> IoResult<()> {
fn flush(&mut self) -> io::Result<()> {
self.output.flush()
}
}
#[cfg(unix)]
mod unix_impl {
use super::*;
use std::os::unix::io::{AsFd, BorrowedFd};
impl<W: Write + AsFd> AsFd for RawTerminal<W> {
fn as_fd(&self) -> BorrowedFd {
self.output.as_fd()
}
}
}
/// Types which can be converted into "raw mode".
pub trait IntoRawMode: Sized {
///
/// # Why is this type defined on writers and not readers?
///
/// TTYs has their state controlled by the writer, not the reader. You use the writer to clear the
/// screen, move the cursor and so on, so naturally you use the writer to change the mode as well.
pub trait IntoRawMode: Write + AsFd + Sized {
/// Switch to raw mode.
///
/// Raw mode means that stdin won't be printed (it will instead have to be written manually by the
/// program). Furthermore, the input isn't canonicalised or buffered (that is, you can read from
/// stdin one byte of a time). The output is neither modified in any way.
fn into_raw_mode(self) -> IoResult<RawTerminal<Self>>;
/// Raw mode means that stdin won't be printed (it will instead have to be written manually by
/// the program). Furthermore, the input isn't canonicalised or buffered (that is, you can
/// read from stdin one byte of a time). The output is neither modified in any way.
fn into_raw_mode(self) -> io::Result<RawTerminal<Self>>;
}
impl<W: Write> IntoRawMode for W {
#[cfg(not(target_os = "redox"))]
fn into_raw_mode(self) -> IoResult<RawTerminal<W>> {
use termios::{cfmakeraw, get_terminal_attr, set_terminal_attr};
impl<W: Write + AsFd> IntoRawMode for W {
fn into_raw_mode(self) -> io::Result<RawTerminal<W>> {
let mut ios = get_terminal_attr(self.as_fd())?;
let prev_ios = ios;
let (mut ios, exit) = get_terminal_attr();
let prev_ios = ios.clone();
if exit != 0 {
return Err(Error::new(ErrorKind::Other, "Unable to get Termios attribute."));
}
raw_terminal_attr(&mut ios);
unsafe {
cfmakeraw(&mut ios);
}
if set_terminal_attr(&mut ios as *mut _) != 0 {
Err(Error::new(ErrorKind::Other, "Unable to set Termios attribute."))
} else {
Ok(RawTerminal {
prev_ios: prev_ios,
output: self,
})
}
}
#[cfg(target_os = "redox")]
fn into_raw_mode(self) -> IoResult<RawTerminal<W>> {
use TermControl;
set_terminal_attr(self.as_fd(), &ios)?;
self.csi("r").map(|_| RawTerminal {
Ok(RawTerminal {
prev_ios,
output: self,
})
}
}
impl<W: Write + AsFd> RawTerminal<W> {
/// Temporarily switch to original mode
pub fn suspend_raw_mode(&self) -> io::Result<()> {
set_terminal_attr(self.as_fd(), &self.prev_ios)?;
Ok(())
}
/// Temporarily switch to raw mode
pub fn activate_raw_mode(&self) -> io::Result<()> {
let mut ios = get_terminal_attr(self.as_fd())?;
raw_terminal_attr(&mut ios);
set_terminal_attr(self.as_fd(), &ios)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::{Write, stdout};
use std::io::{stdout, Write};
#[test]
fn test_into_raw_mode() {
let mut out = stdout().into_raw_mode().unwrap();
out.write(b"this is a test, muahhahahah").unwrap();
out.write_all(b"this is a test, muahhahahah\r\n").unwrap();
drop(out);
}
}
//! Managing switching between main and alternate screen buffers.
//!
//! Note that this implementation uses xterm's new escape sequences for screen switching and thus
//! only works for xterm compatible terminals (which should be most terminals nowadays).
//!
//! # Example
//!
//! ```rust
//! use termion::screen::IntoAlternateScreen;
//! use std::io::{Write, stdout};
//!
//! fn main() {
//! {
//! let mut screen = stdout().into_alternate_screen().unwrap();
//! write!(screen, "Writing to alternate screen!").unwrap();
//! screen.flush().unwrap();
//! }
//! println!("Writing to main screen.");
//! }
//! ```
use std::fmt;
use std::io::{self, Write};
use std::ops;
/// Switch to the main screen buffer of the terminal.
pub struct ToMainScreen;
impl fmt::Display for ToMainScreen {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("?1049l"))
}
}
/// Switch to the alternate screen buffer of the terminal.
pub struct ToAlternateScreen;
impl fmt::Display for ToAlternateScreen {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("?1049h"))
}
}
/// A terminal restorer, which wraps a type implementing Write, and causes all writes to be written
/// to an alternate screen.
///
/// This is achieved by switching the terminal to the alternate screen on creation and
/// automatically switching it back to the original screen on drop.
pub struct AlternateScreen<W: Write> {
/// The output target.
output: W,
}
/// Extension trait for writers, providing the `into_alternate_screen` function.
pub trait IntoAlternateScreen: Write + Sized {
/// Switch the terminal controlled by this writer to use the alternate screen. The terminal will be
/// restored to the main screen when the `AlternateScreen` returned by this function is
/// dropped.
fn into_alternate_screen(mut self) -> io::Result<AlternateScreen<Self>> {
write!(self, "{}", ToAlternateScreen)?;
Ok(AlternateScreen { output: self })
}
}
impl<W: Write> IntoAlternateScreen for W {}
impl<W: Write> Drop for AlternateScreen<W> {
fn drop(&mut self) {
let _ = write!(self, "{}", ToMainScreen);
}
}
impl<W: Write> ops::Deref for AlternateScreen<W> {
type Target = W;
fn deref(&self) -> &W {
&self.output
}
}
impl<W: Write> ops::DerefMut for AlternateScreen<W> {
fn deref_mut(&mut self) -> &mut W {
&mut self.output
}
}
impl<W: Write> Write for AlternateScreen<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.output.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.output.flush()
}
}
//! Scrolling.
use std::fmt;
/// Scroll up.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Up(pub u16);
impl fmt::Display for Up {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("{}S"), self.0)
}
}
/// Scroll down.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Down(pub u16);
impl fmt::Display for Down {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, csi!("{}T"), self.0)
}
}
use std::io;
#[cfg(not(target_os = "redox"))]
use libc::c_ushort;
#[cfg(not(target_os = "redox"))]
#[repr(C)]
struct TermSize {
row: c_ushort,
col: c_ushort,
_x: c_ushort,
_y: c_ushort,
}
// Since attributes on non-item statements is not stable yet, we use a function.
#[cfg(target_pointer_width = "64")]
fn tiocgwinsz() -> u64 {
use termios::TIOCGWINSZ;
TIOCGWINSZ as u64
}
#[cfg(target_pointer_width = "32")]
fn tiocgwinsz() -> u32 {
use termios::TIOCGWINSZ;
TIOCGWINSZ as u32
}
/// Get the size of the terminal.
#[cfg(not(target_os = "redox"))]
pub fn terminal_size() -> io::Result<(usize, usize)> {
use libc::ioctl;
use libc::STDOUT_FILENO;
use std::mem;
unsafe {
let mut size: TermSize = mem::zeroed();
if ioctl(STDOUT_FILENO, tiocgwinsz(), &mut size as *mut _) == 0 {
Ok((size.col as usize, size.row as usize))
} else {
Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal size."))
}
}
}
/// Get the size of the terminal.
#[cfg(target_os = "redox")]
pub fn terminal_size() -> io::Result<(usize, usize)> {
fn get_int(s: &'static str) -> io::Result<usize> {
use std::env;
env::var(s).map_err(|e| match e {
env::VarError::NotPresent => io::Error::new(io::ErrorKind::NotFound, e),
env::VarError::NotUnicode(u) => io::Error::new(io::ErrorKind::InvalidData, u),
}).and_then(|x| {
x.parse().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
})
}
Ok((try!(get_int("COLUMNS")), try!(get_int("LINES"))))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_size() {
assert!(terminal_size().is_ok());
}
}
/// A SGR parameter (rendition mode).
pub enum Style {
/// Reset SGR parameters.
Reset = 0,
/// Bold text.
Bold = 1,
/// Fainted text (not widely supported).
Faint = 2,
/// Italic text.
Italic = 3,
/// Underlined text.
Underline = 4,
/// Blinking text (not widely supported).
Blink = 5,
/// Inverted colors (negative mode).
Invert = 7,
/// Crossed out text (not widely supported).
CrossedOut = 9,
/// Framed text (not widely supported).
Framed = 51,
}
//! Text styling management.
use std::fmt;
derive_csi_sequence!("Reset SGR parameters.", Reset, "m");
derive_csi_sequence!("Bold text.", Bold, "1m");
derive_csi_sequence!("Fainted text (not widely supported).", Faint, "2m");
derive_csi_sequence!("Italic text.", Italic, "3m");
derive_csi_sequence!("Underlined text.", Underline, "4m");
derive_csi_sequence!("Blinking text (not widely supported).", Blink, "5m");
derive_csi_sequence!("Inverted colors (negative mode).", Invert, "7m");
derive_csi_sequence!("Crossed out text (not widely supported).", CrossedOut, "9m");
derive_csi_sequence!("Undo bold text.", NoBold, "21m");
derive_csi_sequence!("Undo fainted text (not widely supported).", NoFaint, "22m");
derive_csi_sequence!("Undo italic text.", NoItalic, "23m");
derive_csi_sequence!("Undo underlined text.", NoUnderline, "24m");
derive_csi_sequence!("Undo blinking text (not widely supported).", NoBlink, "25m");
derive_csi_sequence!("Undo inverted colors (negative mode).", NoInvert, "27m");
derive_csi_sequence!(
"Undo crossed out text (not widely supported).",
NoCrossedOut,
"29m"
);
derive_csi_sequence!("Framed text (not widely supported).", Framed, "51m");
use std::convert::TryInto;
use std::io;
use std::os::fd::{AsRawFd, BorrowedFd};
use super::Termios;
pub fn get_terminal_attr(fd: BorrowedFd) -> io::Result<Termios> {
let mut termios = Termios::default();
let fd: usize = fd
.as_raw_fd()
.try_into()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let fd = libredox::call::dup(fd, b"termios")?;
let res = libredox::call::read(fd, &mut termios);
let _ = libredox::call::close(fd);
if res? == termios.len() {
Ok(termios)
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Unable to get the terminal attributes.",
))
}
}
pub fn set_terminal_attr(fd: BorrowedFd, termios: &Termios) -> io::Result<()> {
let fd: usize = fd
.as_raw_fd()
.try_into()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let fd = libredox::call::dup(fd, b"termios")?;
let res = libredox::call::write(fd, termios);
let _ = libredox::call::close(fd);
if res? == termios.len() {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Unable to set the terminal attributes.",
))
}
}
pub fn raw_terminal_attr(ios: &mut Termios) {
ios.make_raw()
}
extern crate libredox;
extern crate redox_termios;
pub use self::redox_termios::Termios;
pub mod attr;
pub mod size;
pub mod tty;
use std::io;
use super::redox_termios;
/// Get the size of the terminal.
pub fn terminal_size() -> io::Result<(u16, u16)> {
let mut winsize = redox_termios::Winsize::default();
let fd = libredox::call::dup(1, b"winsize")?;
let res = libredox::call::read(fd, &mut winsize);
let _ = libredox::call::close(fd);
if res? == winsize.len() {
Ok((winsize.ws_col, winsize.ws_row))
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Unable to get the terminal size.",
))
}
}
use std::os::unix::io::AsRawFd;
use std::{env, fs, io};
/// Is this stream a TTY?
pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
if let Ok(fd) = libredox::call::dup(stream.as_raw_fd() as _, b"termios") {
let _ = libredox::call::close(fd);
true
} else {
false
}
}
/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
pub fn get_tty() -> io::Result<fs::File> {
let tty = env::var("TTY").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))?;
fs::OpenOptions::new().read(true).write(true).open(tty)
}
use std::{
io, mem,
os::fd::{AsRawFd, BorrowedFd},
};
use super::{cvt, Termios};
pub fn get_terminal_attr(fd: BorrowedFd) -> io::Result<Termios> {
unsafe {
let mut termios = mem::zeroed();
cvt(libc::tcgetattr(fd.as_raw_fd(), &mut termios))?;
Ok(termios)
}
}
pub fn set_terminal_attr(fd: BorrowedFd, termios: &Termios) -> io::Result<()> {
cvt(unsafe { libc::tcsetattr(fd.as_raw_fd(), libc::TCSANOW, termios) }).and(Ok(()))
}
pub fn raw_terminal_attr(termios: &mut Termios) {
unsafe { libc::cfmakeraw(termios) }
}