console-0.8.0/.gitignore010064400007650000024000000000221353006351700133650ustar0000000000000000target Cargo.lock console-0.8.0/.travis.yml010064400007650000024000000001311353006351700135070ustar0000000000000000sudo: false language: rust rust: - stable - beta - nightly script: - cargo test console-0.8.0/appveyor.yml010064400007650000024000000012311353006351700137700ustar0000000000000000cache: - 'target -> appveyor.yml' - '%USERPROFILE%\.cargo' environment: TARGET: i686-pc-windows-msvc install: - set PATH=C:\Program Files\Git\mingw64\bin;%PATH% # Install rust, x86_64-pc-windows-msvc host # FIXME: switch back to win.rustup.rs - curl -sSf -o rustup-init.exe https://dev-static.rust-lang.org/rustup/dist/i686-pc-windows-gnu/rustup-init.exe - rustup-init.exe -y - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - rustup default stable-%TARGET% # let's see what we got - where rustc cargo - rustc -vV - cargo -vV - set CARGO_TARGET_DIR=%CD%\target build: false test_script: - cargo test --target i686-pc-windows-msvc console-0.8.0/Cargo.toml.orig010064400007650000024000000012401353006774300142740ustar0000000000000000[package] name = "console" description = "A terminal and console abstraction for Rust" version = "0.8.0" keywords = ["cli", "terminal", "colors", "console", "ansi"] authors = ["Armin Ronacher "] license = "MIT" homepage = "https://github.com/mitsuhiko/console" documentation = "https://docs.rs/console" readme = "README.md" [dependencies] clicolors-control = "1.0.0" lazy_static = "1" libc = "0.2" regex = "1" unicode-width = "0.1" [target.'cfg(unix)'.dependencies] termios = "0.3" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winbase","winuser","consoleapi","processenv","wincon"] } encode_unicode = "0.3" console-0.8.0/Cargo.toml0000644000000025040000000000000105410ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "console" version = "0.8.0" authors = ["Armin Ronacher "] description = "A terminal and console abstraction for Rust" homepage = "https://github.com/mitsuhiko/console" documentation = "https://docs.rs/console" readme = "README.md" keywords = ["cli", "terminal", "colors", "console", "ansi"] license = "MIT" [dependencies.clicolors-control] version = "1.0.0" [dependencies.lazy_static] version = "1" [dependencies.libc] version = "0.2" [dependencies.regex] version = "1" [dependencies.unicode-width] version = "0.1" [target."cfg(unix)".dependencies.termios] version = "0.3" [target."cfg(windows)".dependencies.encode_unicode] version = "0.3" [target."cfg(windows)".dependencies.winapi] version = "0.3" features = ["winbase", "winuser", "consoleapi", "processenv", "wincon"] console-0.8.0/Cargo.toml.orig0000644000000025050000000000000115010ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "console" version = "0.8.0" authors = ["Armin Ronacher "] description = "A terminal and console abstraction for Rust" homepage = "https://github.com/mitsuhiko/console" documentation = "https://docs.rs/console" readme = "README.md" keywords = ["cli", "terminal", "colors", "console", "ansi"] license = "MIT" [dependencies.clicolors-control] version = "1.0.0" [dependencies.lazy_static] version = "1" [dependencies.libc] version = "0.2" [dependencies.regex] version = "1" [dependencies.unicode-width] version = "0.1" [target."cfg(unix)".dependencies.termios] version = "0.3" [target."cfg(windows)".dependencies.encode_unicode] version = "0.3" [target."cfg(windows)".dependencies.winapi] version = "0.3" features = ["winbase", "winuser", "consoleapi", "processenv", "wincon"] console-0.8.0/LICENSE010064400007650000024000000021301353006351700124040ustar0000000000000000The MIT License (MIT) Copyright (c) 2017 Armin Ronacher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. console-0.8.0/README.md010064400007650000024000000006241353006351700126640ustar0000000000000000# console [![Build Status](https://travis-ci.org/mitsuhiko/console.svg?branch=master)](https://travis-ci.org/mitsuhiko/console) [![Latest Version](https://img.shields.io/crates/v/console.svg)](https://crates.io/crates/console) [![docs](https://docs.rs/console/badge.svg)](https://docs.rs/console) A console and terminal abstraction for Rust. Adds colors and other useful things to your terminal life. console-0.8.0/src/common_term.rs010064400007650000024000000014441353006604400150600ustar0000000000000000use std::io; use term::Term; pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> { if n > 0 { out.write_str(&format!("\x1b[{}B", n)) } else { Ok(()) } } pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> { if n > 0 { out.write_str(&format!("\x1b[{}A", n)) } else { Ok(()) } } pub fn clear_line(out: &Term) -> io::Result<()> { out.write_str("\r\x1b[2K") } pub fn clear_screen(out: &Term) -> io::Result<()> { out.write_str("\r\x1b[2J\r\x1b[H") } pub fn show_cursor(out: &Term) -> io::Result<()> { let esc = "\u{001B}"; out.write_str(&format!("{}[0H{}[0J{}[?25h", esc, esc, esc)) } pub fn hide_cursor(out: &Term) -> io::Result<()> { let esc = "\u{001B}"; out.write_str(&format!("{}[?251", esc)) } console-0.8.0/src/kb.rs010064400007650000024000000005171353006604400131350ustar0000000000000000/// Key mapping /// /// This is an incomplete mapping of keys that are supported for reading /// from the keyboard. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Key { Unknown, ArrowLeft, ArrowRight, ArrowUp, ArrowDown, Enter, Escape, Backspace, Char(char), #[doc(hidden)] __More, } console-0.8.0/src/lib.rs010064400007650000024000000051221353006617600133120ustar0000000000000000//! console is a library for Rust that provides access to various terminal //! features so you can build nicer looking command line interfaces. It //! comes with various tools and utilities for working with Terminals and //! formatting text. //! //! Best paired with other libraries in the family: //! //! * [dialoguer](https://docs.rs/dialoguer) //! * [indicatif](https://docs.rs/indicatif) //! //! # Terminal Access //! //! The terminal is abstracted through the `console::Term` type. It can //! either directly provide access to the connected terminal or by buffering //! up commands. A buffered terminal will however not be completely buffered //! on windows where cursor movements are currently directly passed through. //! //! Example usage: //! //! ``` //! # fn test() -> Result<(), Box> { //! use std::thread; //! use std::time::Duration; //! //! use console::Term; //! //! let term = Term::stdout(); //! term.write_line("Hello World!")?; //! thread::sleep(Duration::from_millis(2000)); //! term.clear_line()?; //! # Ok(()) } fn main() { test().unwrap(); } //! ``` //! //! # Colors and Styles //! //! `console` uses `clicolors-control` to control colors. It also //! provides higher level wrappers for styling text and other things //! that can be displayed with the `style` function and utility types. //! //! Example usage: //! //! ``` //! use console::style; //! //! println!("This is {} neat", style("quite").cyan()); //! ``` //! //! You can also store styles and apply them to text later: //! //! ``` //! use console::Style; //! //! let cyan = Style::new().cyan(); //! println!("This is {} neat", cyan.apply_to("quite")); //! ``` //! //! # Working with ANSI Codes //! //! The crate provids the function `strip_ansi_codes` to remove ANSI codes //! from a string as well as `measure_text_width` to calculate the width of a //! string as it would be displayed by the terminal. Both of those together //! are useful for more complex formatting. #[cfg(windows)] extern crate encode_unicode; extern crate libc; #[cfg(unix)] extern crate termios; #[cfg(windows)] extern crate winapi; #[macro_use] extern crate lazy_static; extern crate clicolors_control; extern crate regex; extern crate unicode_width; pub use kb::Key; pub use term::{user_attended, Term, TermFamily, TermFeatures, TermTarget}; pub use utils::{ colors_enabled, measure_text_width, pad_str, set_colors_enabled, strip_ansi_codes, style, truncate_str, Alignment, AnsiCodeIterator, Attribute, Color, Emoji, Style, StyledObject, }; mod common_term; mod kb; mod term; #[cfg(unix)] mod unix_term; mod utils; #[cfg(windows)] mod windows_term; console-0.8.0/src/term.rs010064400007650000024000000260071353006604400135120ustar0000000000000000use std::fmt::Display; use std::io; use std::io::Write; use std::sync::{Arc, Mutex}; #[cfg(unix)] use std::os::unix::io::{AsRawFd, RawFd}; #[cfg(windows)] use std::os::windows::io::{AsRawHandle, RawHandle}; use kb::Key; use clicolors_control; /// Where the term is writing. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TermTarget { Stdout, Stderr, } #[derive(Debug)] pub struct TermInner { target: TermTarget, buffer: Option>>, } /// The family of the terminal. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TermFamily { /// Redirected to a file or file like thing. File, /// A standard unix terminal. UnixTerm, /// A cmd.exe like windows console. WindowsConsole, } /// Gives access to the terminal features. #[derive(Debug, Clone)] pub struct TermFeatures<'a>(&'a Term); impl<'a> TermFeatures<'a> { /// Checks if this is a real user attended terminal (`isatty`) #[inline] pub fn is_attended(&self) -> bool { is_a_terminal(self.0) } /// Checks if colors are supported by this terminal. /// /// This does not check if colors are enabled. Currently all terminals /// are considered to support colors #[inline] pub fn colors_supported(&self) -> bool { clicolors_control::terminfo::supports_colors() } /// Checks if this terminal is an msys terminal. /// /// This is sometimes useful to disable features that are known to not /// work on msys terminals or require special handling. #[inline] pub fn is_msys_tty(&self) -> bool { #[cfg(windows)] { msys_tty_on(&self.0) } #[cfg(unix)] { false } } /// Checks if this terminal wants emojis. #[inline] pub fn wants_emoji(&self) -> bool { self.is_attended() && wants_emoji() } /// Returns the family of the terminal. #[inline] pub fn family(&self) -> TermFamily { if !self.is_attended() { return TermFamily::File; } #[cfg(windows)] { TermFamily::WindowsConsole } #[cfg(unix)] { TermFamily::UnixTerm } } } /// Abstraction around a terminal. /// /// A terminal can be cloned. If a buffer is used it's shared across all /// clones which means it largely acts as a handle. #[derive(Clone, Debug)] pub struct Term { inner: Arc, } impl Term { fn with_inner(inner: TermInner) -> Term { Term { inner: Arc::new(inner), } } /// Return a new unbuffered terminal #[inline] pub fn stdout() -> Term { Term::with_inner(TermInner { target: TermTarget::Stdout, buffer: None, }) } /// Return a new unbuffered terminal to stderr #[inline] pub fn stderr() -> Term { Term::with_inner(TermInner { target: TermTarget::Stderr, buffer: None, }) } /// Return a new buffered terminal pub fn buffered_stdout() -> Term { Term::with_inner(TermInner { target: TermTarget::Stdout, buffer: Some(Mutex::new(vec![])), }) } /// Return a new buffered terminal to stderr pub fn buffered_stderr() -> Term { Term::with_inner(TermInner { target: TermTarget::Stderr, buffer: Some(Mutex::new(vec![])), }) } /// Returns the targert pub fn target(&self) -> TermTarget { self.inner.target } #[doc(hidden)] pub fn write_str(&self, s: &str) -> io::Result<()> { match self.inner.buffer { Some(ref buffer) => buffer.lock().unwrap().write_all(s.as_bytes()), None => self.write_through(s.as_bytes()), } } /// Writes a string to the terminal and adds a newline. pub fn write_line(&self, s: &str) -> io::Result<()> { match self.inner.buffer { Some(ref mutex) => { let mut buffer = mutex.lock().unwrap(); buffer.extend_from_slice(s.as_bytes()); buffer.push(b'\n'); Ok(()) } None => self.write_through(format!("{}\n", s).as_bytes()), } } /// Read a single character from the terminal /// /// This does not echo the character and blocks until a single character /// is entered. pub fn read_char(&self) -> io::Result { loop { match self.read_key()? { Key::Char(c) => { return Ok(c); } Key::Enter => { return Ok('\n'); } _ => {} } } } /// Read a single key form the terminal. /// /// This does not echo anything. If the terminal is not user attended /// the return value will always be the unknown key. pub fn read_key(&self) -> io::Result { if !self.is_term() { Ok(Key::Unknown) } else { read_single_key() } } /// Read one line of input. /// /// This does not include the trailing newline. If the terminal is not /// user attended the return value will always be an empty string. pub fn read_line(&self) -> io::Result { if !self.is_term() { return Ok("".into()); } let mut rv = String::new(); io::stdin().read_line(&mut rv)?; let len = rv.trim_end_matches(&['\r', '\n'][..]).len(); rv.truncate(len); Ok(rv) } /// Read securely a line of input. /// /// This is similar to `read_line` but will not echo the output. This /// also switches the terminal into a different mode where not all /// characters might be accepted. pub fn read_secure_line(&self) -> io::Result { if !self.is_term() { return Ok("".into()); } match read_secure() { Ok(rv) => { self.write_line("")?; Ok(rv) } Err(err) => Err(err), } } /// Flushes internal buffers. /// /// This forces the contents of the internal buffer to be written to /// the terminal. This is unnecessary for unbuffered terminals which /// will automatically flush. pub fn flush(&self) -> io::Result<()> { if let Some(ref buffer) = self.inner.buffer { let mut buffer = buffer.lock().unwrap(); if !buffer.is_empty() { self.write_through(&buffer[..])?; buffer.clear(); } } Ok(()) } /// Checks if the terminal is indeed a terminal. /// /// This is a shortcut for `features().is_attended()`. pub fn is_term(&self) -> bool { self.features().is_attended() } /// Checks for common terminal features. #[inline] pub fn features(&self) -> TermFeatures<'_> { TermFeatures(self) } /// Checks if this terminal wants emoji output. #[deprecated(note = "Use features().wants_emoji() instead", since = "0.8.0")] pub fn want_emoji(&self) -> bool { self.features().wants_emoji() } /// Returns the terminal size or gets sensible defaults. #[inline] pub fn size(&self) -> (u16, u16) { self.size_checked().unwrap_or((24, DEFAULT_WIDTH)) } /// Returns the terminal size in rows and columns. /// /// If the size cannot be reliably determined None is returned. #[inline] pub fn size_checked(&self) -> Option<(u16, u16)> { terminal_size() } /// Moves the cursor up `n` lines pub fn move_cursor_up(&self, n: usize) -> io::Result<()> { move_cursor_up(self, n) } /// Moves the cursor down `n` lines pub fn move_cursor_down(&self, n: usize) -> io::Result<()> { move_cursor_down(self, n) } /// Clears the current line. /// /// The positions the cursor at the beginning of the line again. pub fn clear_line(&self) -> io::Result<()> { clear_line(self) } /// Clear the last `n` lines. /// /// This positions the cursor at the beginning of the first line /// that was cleared. pub fn clear_last_lines(&self, n: usize) -> io::Result<()> { self.move_cursor_up(n)?; for _ in 0..n { self.clear_line()?; self.move_cursor_down(1)?; } self.move_cursor_up(n)?; Ok(()) } /// Clears the entire screen. pub fn clear_screen(&self) -> io::Result<()> { clear_screen(self) } /// Set the terminal title pub fn set_title(&self, title: T) { if !self.is_term() { return; } set_title(title); } /// Makes cursor visible again pub fn show_cursor(&self) -> io::Result<()> { show_cursor(self) } /// Hides cursor pub fn hide_cursor(&self) -> io::Result<()> { hide_cursor(self) } // helpers fn write_through(&self, bytes: &[u8]) -> io::Result<()> { match self.inner.target { TermTarget::Stdout => { io::stdout().write_all(bytes)?; io::stdout().flush()?; } TermTarget::Stderr => { io::stderr().write_all(bytes)?; io::stderr().flush()?; } } Ok(()) } } /// A fast way to check if the application has a user attended. /// /// This means that stdout is connected to a terminal instead of a /// file or redirected by other means. This is a shortcut for /// checking the `is_attended` flag on the stdout terminal. pub fn user_attended() -> bool { Term::stdout().features().is_attended() } #[cfg(unix)] impl AsRawFd for Term { fn as_raw_fd(&self) -> RawFd { match self.inner.target { TermTarget::Stdout => libc::STDOUT_FILENO, TermTarget::Stderr => libc::STDERR_FILENO, } } } #[cfg(windows)] impl AsRawHandle for Term { fn as_raw_handle(&self) -> RawHandle { use winapi::um::processenv::GetStdHandle; use winapi::um::winbase::{STD_ERROR_HANDLE, STD_OUTPUT_HANDLE}; unsafe { GetStdHandle(match self.inner.target { TermTarget::Stdout => STD_OUTPUT_HANDLE, TermTarget::Stderr => STD_ERROR_HANDLE, }) as RawHandle } } } impl io::Write for Term { fn write(&mut self, buf: &[u8]) -> io::Result { self.write_through(buf)?; Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl<'a> io::Write for &'a Term { fn write(&mut self, buf: &[u8]) -> io::Result { self.write_through(buf)?; Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl io::Read for Term { fn read(&mut self, buf: &mut [u8]) -> io::Result { io::stdin().read(buf) } } impl<'a> io::Read for &'a Term { fn read(&mut self, buf: &mut [u8]) -> io::Result { io::stdin().read(buf) } } #[cfg(unix)] pub use unix_term::*; #[cfg(windows)] pub use windows_term::*; console-0.8.0/src/unix_term.rs010064400007650000024000000073051353006617600145630ustar0000000000000000use std::fmt::Display; use std::fs; use std::io; use std::io::{BufRead, BufReader}; use std::mem; use std::os::unix::io::AsRawFd; use std::str; use libc; use termios; use kb::Key; use term::Term; pub use common_term::*; pub const DEFAULT_WIDTH: u16 = 80; #[inline] pub fn is_a_terminal(out: &Term) -> bool { unsafe { libc::isatty(out.as_raw_fd()) != 0 } } pub fn terminal_size() -> Option<(u16, u16)> { unsafe { if libc::isatty(libc::STDOUT_FILENO) != 1 { return None; } let mut winsize: libc::winsize = mem::zeroed(); // FIXME: ".into()" used as a temporary fix for a libc bug // https://github.com/rust-lang/libc/pull/704 libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ.into(), &mut winsize); if winsize.ws_row > 0 && winsize.ws_col > 0 { Some((winsize.ws_row as u16, winsize.ws_col as u16)) } else { None } } } pub fn read_secure() -> io::Result { let f_tty; let fd = unsafe { if libc::isatty(libc::STDIN_FILENO) == 1 { f_tty = None; libc::STDIN_FILENO } else { let f = fs::File::open("/dev/tty")?; let fd = f.as_raw_fd(); f_tty = Some(BufReader::new(f)); fd } }; let mut termios = termios::Termios::from_fd(fd)?; let original = termios; termios.c_lflag &= !termios::ECHO; termios::tcsetattr(fd, termios::TCSAFLUSH, &termios)?; let mut rv = String::new(); let read_rv = if let Some(mut f) = f_tty { f.read_line(&mut rv) } else { io::stdin().read_line(&mut rv) }; termios::tcsetattr(fd, termios::TCSAFLUSH, &original)?; read_rv.map(|_| { let len = rv.trim_end_matches(&['\r', '\n'][..]).len(); rv.truncate(len); rv }) } pub fn read_single_key() -> io::Result { let tty_f; let fd = unsafe { if libc::isatty(libc::STDIN_FILENO) == 1 { libc::STDIN_FILENO } else { tty_f = fs::File::open("/dev/tty")?; tty_f.as_raw_fd() } }; let mut buf = [0u8; 20]; let mut termios = termios::Termios::from_fd(fd)?; let original = termios; termios::cfmakeraw(&mut termios); termios::tcsetattr(fd, termios::TCSADRAIN, &termios)?; let rv = unsafe { let read = libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, 20); if read < 0 { Err(io::Error::last_os_error()) } else if buf[0] == b'\x03' { Err(io::Error::new( io::ErrorKind::Interrupted, "read interrupted", )) } else { Ok(key_from_escape_codes(&buf[..read as usize])) } }; termios::tcsetattr(fd, termios::TCSADRAIN, &original)?; // if the user hit ^C we want to signal SIGINT to outselves. if let Err(ref err) = rv { if err.kind() == io::ErrorKind::Interrupted { unsafe { libc::raise(libc::SIGINT); } } } rv } pub fn key_from_escape_codes(buf: &[u8]) -> Key { match buf { b"\x1b[D" => Key::ArrowLeft, b"\x1b[C" => Key::ArrowRight, b"\x1b[A" => Key::ArrowUp, b"\x1b[B" => Key::ArrowDown, b"\n" | b"\r" => Key::Enter, b"\x1b" => Key::Escape, b"\x7f" => Key::Backspace, buf => { if let Ok(s) = str::from_utf8(buf) { if let Some(c) = s.chars().next() { return Key::Char(c); } } Key::Unknown } } } pub fn wants_emoji() -> bool { cfg!(target_os = "macos") } pub fn set_title(title: T) { print!("\x1b]0;{}\x07", title); } console-0.8.0/src/utils.rs010064400007650000024000000532251353006621200137020ustar0000000000000000use std::borrow::Cow; use std::collections::BTreeSet; use std::fmt; use clicolors_control; use regex::{Matches, Regex}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use term::wants_emoji; lazy_static! { static ref STRIP_ANSI_RE: Regex = Regex::new(r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]") .unwrap(); } /// Returns `true` if colors should be enabled. /// /// This honors the [clicolors spec](http://bixense.com/clicolors/). /// /// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped. /// * `CLICOLOR == 0`: Don't output ANSI color escape codes. /// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what. /// /// This internally uses `clicolors-control`. #[inline] pub fn colors_enabled() -> bool { clicolors_control::colors_enabled() } /// Forces colorization on or off. /// /// This overrides the default for the current process and changes the return value of the /// `colors_enabled` function. /// /// This internally uses `clicolors-control`. #[inline] pub fn set_colors_enabled(val: bool) { clicolors_control::set_colors_enabled(val) } /// Helper function to strip ansi codes. pub fn strip_ansi_codes(s: &str) -> Cow { STRIP_ANSI_RE.replace_all(s, "") } /// Measure the width of a string in terminal characters. pub fn measure_text_width(s: &str) -> usize { strip_ansi_codes(s).width() } /// A terminal color. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Color { Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, } impl Color { #[inline] fn ansi_num(self) -> usize { match self { Color::Black => 0, Color::Red => 1, Color::Green => 2, Color::Yellow => 3, Color::Blue => 4, Color::Magenta => 5, Color::Cyan => 6, Color::White => 7, } } } /// A terminal style attribute. #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)] pub enum Attribute { Bold, Dim, Italic, Underlined, Blink, Reverse, Hidden, } impl Attribute { #[inline] fn ansi_num(self) -> usize { match self { Attribute::Bold => 1, Attribute::Dim => 2, Attribute::Italic => 3, Attribute::Underlined => 4, Attribute::Blink => 5, Attribute::Reverse => 7, Attribute::Hidden => 8, } } } /// Defines the alignment for padding operations. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Alignment { Left, Center, Right, } /// A stored style that can be applied. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Style { fg: Option, bg: Option, attrs: BTreeSet, force: Option, } impl Default for Style { fn default() -> Style { Style::new() } } impl Style { /// Returns an empty default style. pub fn new() -> Style { Style { fg: None, bg: None, attrs: BTreeSet::new(), force: None, } } /// Creates a style from a dotted string. /// /// Effectively the string is split at each dot and then the /// terms in between are applied. For instance `red.on_blue` will /// create a string that is red on blue background. Unknown terms /// are ignored. pub fn from_dotted_str(s: &str) -> Style { let mut rv = Style::new(); for part in s.split('.') { rv = match part { "black" => rv.black(), "red" => rv.red(), "green" => rv.green(), "yellow" => rv.yellow(), "blue" => rv.blue(), "magenta" => rv.magenta(), "cyan" => rv.cyan(), "white" => rv.white(), "on_black" => rv.on_black(), "on_red" => rv.on_red(), "on_green" => rv.on_green(), "on_yellow" => rv.on_yellow(), "on_blue" => rv.on_blue(), "on_magenta" => rv.on_magenta(), "on_cyan" => rv.on_cyan(), "on_white" => rv.on_white(), "bold" => rv.bold(), "dim" => rv.dim(), "underlined" => rv.underlined(), "blink" => rv.blink(), "reverse" => rv.reverse(), "hidden" => rv.hidden(), _ => { continue; } }; } rv } /// Apply the style to something that can be displayed. pub fn apply_to(&self, val: D) -> StyledObject { StyledObject { style: self.clone(), val, } } /// Forces styling on or off. /// /// This overrides the detection from `clicolors-control`. #[inline] pub fn force_styling(mut self, value: bool) -> Style { self.force = Some(value); self } /// Sets a foreground color. #[inline] pub fn fg(mut self, color: Color) -> Style { self.fg = Some(color); self } /// Sets a background color. #[inline] pub fn bg(mut self, color: Color) -> Style { self.bg = Some(color); self } /// Adds a attr. #[inline] pub fn attr(mut self, attr: Attribute) -> Style { self.attrs.insert(attr); self } #[inline] pub fn black(self) -> Style { self.fg(Color::Black) } #[inline] pub fn red(self) -> Style { self.fg(Color::Red) } #[inline] pub fn green(self) -> Style { self.fg(Color::Green) } #[inline] pub fn yellow(self) -> Style { self.fg(Color::Yellow) } #[inline] pub fn blue(self) -> Style { self.fg(Color::Blue) } #[inline] pub fn magenta(self) -> Style { self.fg(Color::Magenta) } #[inline] pub fn cyan(self) -> Style { self.fg(Color::Cyan) } #[inline] pub fn white(self) -> Style { self.fg(Color::White) } #[inline] pub fn on_black(self) -> Style { self.bg(Color::Black) } #[inline] pub fn on_red(self) -> Style { self.bg(Color::Red) } #[inline] pub fn on_green(self) -> Style { self.bg(Color::Green) } #[inline] pub fn on_yellow(self) -> Style { self.bg(Color::Yellow) } #[inline] pub fn on_blue(self) -> Style { self.bg(Color::Blue) } #[inline] pub fn on_magenta(self) -> Style { self.bg(Color::Magenta) } #[inline] pub fn on_cyan(self) -> Style { self.bg(Color::Cyan) } #[inline] pub fn on_white(self) -> Style { self.bg(Color::White) } #[inline] pub fn bold(self) -> Style { self.attr(Attribute::Bold) } #[inline] pub fn dim(self) -> Style { self.attr(Attribute::Dim) } #[inline] pub fn italic(self) -> Style { self.attr(Attribute::Italic) } #[inline] pub fn underlined(self) -> Style { self.attr(Attribute::Underlined) } #[inline] pub fn blink(self) -> Style { self.attr(Attribute::Blink) } #[inline] pub fn reverse(self) -> Style { self.attr(Attribute::Reverse) } #[inline] pub fn hidden(self) -> Style { self.attr(Attribute::Hidden) } } /// Wraps an object for formatting for styling. /// /// Example: /// /// ```rust,no_run /// # use console::style; /// format!("Hello {}", style("World").cyan()); /// ``` /// /// This is a shortcut for making a new style and applying it /// to a value: /// /// ```rust,no_run /// # use console::Style; /// format!("Hello {}", Style::new().cyan().apply_to("World")); /// ``` pub fn style(val: D) -> StyledObject { Style::new().apply_to(val) } /// A formatting wrapper that can be styled for a terminal. #[derive(Clone)] pub struct StyledObject { style: Style, val: D, } impl StyledObject { /// Forces styling on or off. /// /// This overrides the detection from `clicolors-control`. #[inline] pub fn force_styling(mut self, value: bool) -> StyledObject { self.style = self.style.force_styling(value); self } /// Sets a foreground color. #[inline] pub fn fg(mut self, color: Color) -> StyledObject { self.style = self.style.fg(color); self } /// Sets a background color. #[inline] pub fn bg(mut self, color: Color) -> StyledObject { self.style = self.style.bg(color); self } /// Adds a attr. #[inline] pub fn attr(mut self, attr: Attribute) -> StyledObject { self.style = self.style.attr(attr); self } #[inline] pub fn black(self) -> StyledObject { self.fg(Color::Black) } #[inline] pub fn red(self) -> StyledObject { self.fg(Color::Red) } #[inline] pub fn green(self) -> StyledObject { self.fg(Color::Green) } #[inline] pub fn yellow(self) -> StyledObject { self.fg(Color::Yellow) } #[inline] pub fn blue(self) -> StyledObject { self.fg(Color::Blue) } #[inline] pub fn magenta(self) -> StyledObject { self.fg(Color::Magenta) } #[inline] pub fn cyan(self) -> StyledObject { self.fg(Color::Cyan) } #[inline] pub fn white(self) -> StyledObject { self.fg(Color::White) } #[inline] pub fn on_black(self) -> StyledObject { self.bg(Color::Black) } #[inline] pub fn on_red(self) -> StyledObject { self.bg(Color::Red) } #[inline] pub fn on_green(self) -> StyledObject { self.bg(Color::Green) } #[inline] pub fn on_yellow(self) -> StyledObject { self.bg(Color::Yellow) } #[inline] pub fn on_blue(self) -> StyledObject { self.bg(Color::Blue) } #[inline] pub fn on_magenta(self) -> StyledObject { self.bg(Color::Magenta) } #[inline] pub fn on_cyan(self) -> StyledObject { self.bg(Color::Cyan) } #[inline] pub fn on_white(self) -> StyledObject { self.bg(Color::White) } #[inline] pub fn bold(self) -> StyledObject { self.attr(Attribute::Bold) } #[inline] pub fn dim(self) -> StyledObject { self.attr(Attribute::Dim) } #[inline] pub fn italic(self) -> StyledObject { self.attr(Attribute::Italic) } #[inline] pub fn underlined(self) -> StyledObject { self.attr(Attribute::Underlined) } #[inline] pub fn blink(self) -> StyledObject { self.attr(Attribute::Blink) } #[inline] pub fn reverse(self) -> StyledObject { self.attr(Attribute::Reverse) } #[inline] pub fn hidden(self) -> StyledObject { self.attr(Attribute::Hidden) } } macro_rules! impl_fmt { ($name:ident) => { impl fmt::$name for StyledObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut reset = false; if self.style.force.unwrap_or_else(colors_enabled) { if let Some(fg) = self.style.fg { write!(f, "\x1b[{}m", fg.ansi_num() + 30)?; reset = true; } if let Some(bg) = self.style.bg { write!(f, "\x1b[{}m", bg.ansi_num() + 40)?; reset = true; } for attr in &self.style.attrs { write!(f, "\x1b[{}m", attr.ansi_num())?; reset = true; } } fmt::$name::fmt(&self.val, f)?; if reset { write!(f, "\x1b[0m")?; } Ok(()) } } }; } impl_fmt!(Binary); impl_fmt!(Debug); impl_fmt!(Display); impl_fmt!(LowerExp); impl_fmt!(LowerHex); impl_fmt!(Octal); impl_fmt!(Pointer); impl_fmt!(UpperExp); impl_fmt!(UpperHex); /// "Intelligent" emoji formatter. /// /// This struct intelligently wraps an emoji so that it is rendered /// only on systems that want emojis and renders a fallback on others. /// /// Example: /// /// ```rust /// use console::Emoji; /// println!("[3/4] {}Downloading ...", Emoji("🚚 ", "")); /// println!("[4/4] {} Done!", Emoji("✨", ":-)")); /// ``` #[derive(Copy, Clone)] pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str); impl<'a, 'b> Emoji<'a, 'b> { pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> { Emoji(emoji, fallback) } } impl<'a, 'b> fmt::Display for Emoji<'a, 'b> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if wants_emoji() { write!(f, "{}", self.0) } else { write!(f, "{}", self.1) } } } /// An iterator over ansi codes in a string. /// /// This type can be used to scan over ansi codes in a string. /// It yields tuples in the form `(s, is_ansi)` where `s` is a slice of /// the original string and `is_ansi` indicates if the slice contains /// ansi codes or string values. pub struct AnsiCodeIterator<'a> { s: &'a str, pending_item: Option<(&'a str, bool)>, last_idx: usize, cur_idx: usize, iter: Matches<'static, 'a>, } impl<'a> AnsiCodeIterator<'a> { /// Creates a new ansi code iterator. pub fn new(s: &'a str) -> AnsiCodeIterator<'a> { AnsiCodeIterator { s, pending_item: None, last_idx: 0, cur_idx: 0, iter: STRIP_ANSI_RE.find_iter(s), } } /// Returns the string slice up to the current match. pub fn current_slice(&self) -> &str { &self.s[..self.cur_idx] } /// Returns the string slice from the current match to the end. pub fn rest_slice(&self) -> &str { &self.s[self.cur_idx..] } } impl<'a> Iterator for AnsiCodeIterator<'a> { type Item = (&'a str, bool); fn next(&mut self) -> Option<(&'a str, bool)> { if let Some(pending_item) = self.pending_item.take() { self.cur_idx += pending_item.0.len(); Some(pending_item) } else if let Some(m) = self.iter.next() { let s = &self.s[self.last_idx..m.start()]; self.last_idx = m.end(); if s.is_empty() { self.cur_idx = m.end(); Some((m.as_str(), true)) } else { self.cur_idx = m.start(); self.pending_item = Some((m.as_str(), true)); Some((s, false)) } } else if self.last_idx < self.s.len() { let rv = &self.s[self.last_idx..]; self.cur_idx = self.s.len(); self.last_idx = self.s.len(); Some((rv, false)) } else { None } } } /// Truncates a string to a certain number of characters. /// /// This ensures that escape codes are not screwed up in the process. /// If the maximum length is hit the string will be truncated but /// escapes code will still be honored. If truncation takes place /// the tail string will be appended. pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> { let mut iter = AnsiCodeIterator::new(s); let mut length = 0; let mut rv = None; while let Some(item) = iter.next() { match item { (s, false) => { if rv.is_none() { if s.width() + length > width - tail.width() { let ts = iter.current_slice(); let mut s_byte = 0; let mut s_width = 0; let rest_width = width - tail.width() - length; for c in s.chars() { s_byte += c.len_utf8(); s_width += c.width().unwrap_or(0); if s_width == rest_width { break; } else if s_width > rest_width { s_byte -= c.len_utf8(); break; } } let idx = ts.len() - s.len() + s_byte; let mut buf = ts[..idx].to_string(); buf.push_str(tail); rv = Some(buf); } length += s.width(); } } (s, true) => { if rv.is_some() { rv.as_mut().unwrap().push_str(s); } } } } if let Some(buf) = rv { Cow::Owned(buf) } else { Cow::Borrowed(s) } } /// Pads a string to fill a certain number of characters. /// /// This will honor ansi codes correctly and allows you to align a string /// on the left, right or centered. Additionally truncation can be enabled /// by setting `truncate` to a string that should be used as a truncation /// marker. pub fn pad_str<'a>( s: &'a str, width: usize, align: Alignment, truncate: Option<&str>, ) -> Cow<'a, str> { pad_str_with(s, width, align, truncate, ' ') } /// Pads a string with specific padding to fill a certain number of characters. /// /// This will honor ansi codes correctly and allows you to align a string /// on the left, right or centered. Additionally truncation can be enabled /// by setting `truncate` to a string that should be used as a truncation /// marker. pub fn pad_str_with<'a>( s: &'a str, width: usize, align: Alignment, truncate: Option<&str>, pad: char, ) -> Cow<'a, str> { let cols = measure_text_width(s); if cols >= width { return match truncate { None => Cow::Borrowed(s), Some(tail) => truncate_str(s, width, tail), }; } let diff = width - cols; let (left_pad, right_pad) = match align { Alignment::Left => (0, diff), Alignment::Right => (diff, 0), Alignment::Center => (diff / 2, diff - diff / 2), }; let mut rv = String::new(); for _ in 0..left_pad { rv.push(pad); } rv.push_str(s); for _ in 0..right_pad { rv.push(pad); } Cow::Owned(rv) } #[test] fn test_text_width() { let s = style("foo") .red() .on_black() .bold() .force_styling(true) .to_string(); assert_eq!(measure_text_width(&s), 3); } #[test] fn test_truncate_str() { let s = format!("foo {}", style("bar").red().force_styling(true)); assert_eq!( &truncate_str(&s, 5, ""), &format!("foo {}", style("b").red().force_styling(true)) ); let s = format!("foo {}", style("bar").red().force_styling(true)); assert_eq!( &truncate_str(&s, 5, "!"), &format!("foo {}", style("!").red().force_styling(true)) ); let s = format!("foo {} baz", style("bar").red().force_styling(true)); assert_eq!( &truncate_str(&s, 10, "..."), &format!("foo {}...", style("bar").red().force_styling(true)) ); let s = format!("foo {}", style("バー").red().force_styling(true)); assert_eq!( &truncate_str(&s, 5, ""), &format!("foo {}", style("").red().force_styling(true)) ); let s = format!("foo {}", style("バー").red().force_styling(true)); assert_eq!( &truncate_str(&s, 6, ""), &format!("foo {}", style("バ").red().force_styling(true)) ); } #[test] fn test_pad_str() { assert_eq!(pad_str("foo", 7, Alignment::Center, None), " foo "); assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo "); assert_eq!(pad_str("foo", 7, Alignment::Right, None), " foo"); assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo"); assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar"); assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo"); assert_eq!( pad_str("foobarbaz", 6, Alignment::Left, Some("...")), "foo..." ); } #[test] fn test_pad_str_with() { assert_eq!( pad_str_with("foo", 7, Alignment::Center, None, '#'), "##foo##" ); assert_eq!( pad_str_with("foo", 7, Alignment::Left, None, '#'), "foo####" ); assert_eq!( pad_str_with("foo", 7, Alignment::Right, None, '#'), "####foo" ); assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo"); assert_eq!( pad_str_with("foobar", 3, Alignment::Left, None, '#'), "foobar" ); assert_eq!( pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'), "foo" ); assert_eq!( pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'), "foo..." ); } #[test] fn test_ansi_iter_re() { let s = format!("Hello {}!", style("World").red().force_styling(true)); let mut iter = AnsiCodeIterator::new(&s); assert_eq!(iter.next(), Some(("Hello ", false))); assert_eq!(iter.current_slice(), "Hello "); assert_eq!(iter.rest_slice(), "\x1b[31mWorld\x1b[0m!"); assert_eq!(iter.next(), Some(("\x1b[31m", true))); assert_eq!(iter.current_slice(), "Hello \x1b[31m"); assert_eq!(iter.rest_slice(), "World\x1b[0m!"); assert_eq!(iter.next(), Some(("World", false))); assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld"); assert_eq!(iter.rest_slice(), "\x1b[0m!"); assert_eq!(iter.next(), Some(("\x1b[0m", true))); assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m"); assert_eq!(iter.rest_slice(), "!"); assert_eq!(iter.next(), Some(("!", false))); assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m!"); assert_eq!(iter.rest_slice(), ""); assert_eq!(iter.next(), None); } console-0.8.0/src/windows_term.rs010064400007650000024000000310021353006617600152610ustar0000000000000000use std::char; use std::ffi::OsStr; use std::fmt::Display; use std::io; use std::iter::once; use std::mem; use std::os::windows::ffi::OsStrExt; use std::os::windows::io::AsRawHandle; use std::slice; use encode_unicode::error::InvalidUtf16Tuple; use encode_unicode::CharExt; use winapi; use winapi::ctypes::c_void; use winapi::shared::minwindef::DWORD; use winapi::shared::minwindef::MAX_PATH; use winapi::um::consoleapi::GetConsoleMode; use winapi::um::consoleapi::{GetNumberOfConsoleInputEvents, ReadConsoleInputW}; use winapi::um::fileapi::FILE_NAME_INFO; use winapi::um::handleapi::INVALID_HANDLE_VALUE; use winapi::um::minwinbase::FileNameInfo; use winapi::um::processenv::GetStdHandle; use winapi::um::winbase::GetFileInformationByHandleEx; use winapi::um::winbase::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; use winapi::um::wincon::{ FillConsoleOutputCharacterA, GetConsoleScreenBufferInfo, SetConsoleCursorPosition, SetConsoleTitleW, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, KEY_EVENT, KEY_EVENT_RECORD, }; use winapi::um::winnt::{CHAR, HANDLE, INT, WCHAR}; use common_term; use kb::Key; use term::{Term, TermTarget}; pub const DEFAULT_WIDTH: u16 = 79; pub fn as_handle(term: &Term) -> HANDLE { // convert between winapi::um::winnt::HANDLE and std::os::windows::raw::HANDLE // which are both c_void. would be nice to find a better way to do this unsafe { ::std::mem::transmute(term.as_raw_handle()) } } pub fn is_a_terminal(out: &Term) -> bool { let (fd, others) = match out.target() { TermTarget::Stdout => (STD_OUTPUT_HANDLE, [STD_INPUT_HANDLE, STD_ERROR_HANDLE]), TermTarget::Stderr => (STD_ERROR_HANDLE, [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE]), }; if unsafe { console_on_any(&[fd]) } { // False positives aren't possible. If we got a console then // we definitely have a tty on stdin. return true; } // At this point, we *could* have a false negative. We can determine that // this is true negative if we can detect the presence of a console on // any of the other streams. If another stream has a console, then we know // we're in a Windows console and can therefore trust the negative. if unsafe { console_on_any(&others) } { return false; } msys_tty_on(out) } unsafe fn console_on_any(fds: &[DWORD]) -> bool { for &fd in fds { let mut out = 0; let handle = GetStdHandle(fd); if GetConsoleMode(handle, &mut out) != 0 { return true; } } false } pub fn terminal_size() -> Option<(u16, u16)> { let hand = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) }; if let Some((_, csbi)) = get_console_screen_buffer_info(hand) { Some(( (csbi.srWindow.Bottom - csbi.srWindow.Top) as u16, (csbi.srWindow.Right - csbi.srWindow.Left) as u16, )) } else { None } } pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> { if msys_tty_on(out) { return common_term::move_cursor_up(out, n); } if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { unsafe { SetConsoleCursorPosition( hand, COORD { X: 0, Y: csbi.dwCursorPosition.Y - n as i16, }, ); } } Ok(()) } pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> { if msys_tty_on(out) { return common_term::move_cursor_down(out, n); } if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { unsafe { SetConsoleCursorPosition( hand, COORD { X: 0, Y: csbi.dwCursorPosition.Y + n as i16, }, ); } } Ok(()) } pub fn clear_line(out: &Term) -> io::Result<()> { if msys_tty_on(out) { return common_term::clear_line(out); } if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { unsafe { let width = csbi.srWindow.Right - csbi.srWindow.Left; let pos = COORD { X: 0, Y: csbi.dwCursorPosition.Y, }; let mut written = 0; FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written); SetConsoleCursorPosition(hand, pos); } } Ok(()) } pub fn clear_screen(out: &Term) -> io::Result<()> { if msys_tty_on(out) { return common_term::clear_screen(out); } if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { unsafe { let cells = csbi.dwSize.X as DWORD * csbi.dwSize.Y as DWORD; // as DWORD, or else this causes stack overflows. let pos = COORD { X: 0, Y: 0 }; let mut written = 0; FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as DWORD no longer needed. SetConsoleCursorPosition(hand, pos); } } Ok(()) } fn get_console_screen_buffer_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_SCREEN_BUFFER_INFO)> { let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() }; match unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } { 0 => None, _ => Some((hand, csbi)), } } pub fn key_from_key_code(code: INT) -> Key { match code { winapi::um::winuser::VK_LEFT => Key::ArrowLeft, winapi::um::winuser::VK_RIGHT => Key::ArrowRight, winapi::um::winuser::VK_UP => Key::ArrowUp, winapi::um::winuser::VK_DOWN => Key::ArrowDown, winapi::um::winuser::VK_RETURN => Key::Enter, winapi::um::winuser::VK_ESCAPE => Key::Escape, winapi::um::winuser::VK_BACK => Key::Char('\x08'), winapi::um::winuser::VK_TAB => Key::Char('\x09'), _ => Key::Unknown, } } pub fn read_secure() -> io::Result { let mut rv = String::new(); loop { match read_single_key()? { Key::Enter => { break; } Key::Char('\x08') => { if rv.len() > 0 { let new_len = rv.len() - 1; rv.truncate(new_len); } } Key::Char(c) => { rv.push(c); } _ => {} } } Ok(rv) } pub fn read_single_key() -> io::Result { let key_event = read_key_event()?; let unicode_char = unsafe { *key_event.uChar.UnicodeChar() }; if unicode_char == 0 { return Ok(key_from_key_code(key_event.wVirtualKeyCode as INT)); } else { // This is a unicode character, in utf-16. Try to decode it by itself. match char::from_utf16_tuple((unicode_char, None)) { Ok(c) => { // Maintain backward compatibility. The previous implementation (_getwch()) would return // a special keycode for `Enter`, while ReadConsoleInputW() prefers to use '\r'. if c == '\r' { Ok(Key::Enter) } else { Ok(Key::Char(c)) } } // This is part of a surrogate pair. Try to read the second half. Err(InvalidUtf16Tuple::MissingSecond) => { // Confirm that there is a next character to read. if get_key_event_count()? == 0 { let message = format!( "Read invlid utf16 {}: {}", unicode_char, InvalidUtf16Tuple::MissingSecond ); return Err(io::Error::new(io::ErrorKind::InvalidData, message)); } // Read the next character. let next_event = read_key_event()?; let next_surrogate = unsafe { *next_event.uChar.UnicodeChar() }; // Attempt to decode it. match char::from_utf16_tuple((unicode_char, Some(next_surrogate))) { Ok(c) => Ok(Key::Char(c)), // Return an InvalidData error. This is the recommended value for UTF-related I/O errors. // (This error is given when reading a non-UTF8 file into a String, for example.) Err(e) => { let message = format!( "Read invalid surrogate pair ({}, {}): {}", unicode_char, next_surrogate, e ); Err(io::Error::new(io::ErrorKind::InvalidData, message)) } } } // Return an InvalidData error. This is the recommended value for UTF-related I/O errors. // (This error is given when reading a non-UTF8 file into a String, for example.) Err(e) => { let message = format!("Read invalid utf16 {}: {}", unicode_char, e); Err(io::Error::new(io::ErrorKind::InvalidData, message)) } } } } fn get_stdin_handle() -> io::Result { let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) }; if handle == INVALID_HANDLE_VALUE { Err(io::Error::last_os_error()) } else { Ok(handle) } } /// Get the number of pending events in the ReadConsoleInput queue. Note that while /// these aren't necessarily key events, the only way that multiple events can be /// put into the queue simultaneously is if a unicode character spanning multiple u16's /// is read. /// /// Therefore, this is accurate as long as at least one KEY_EVENT has already been read. fn get_key_event_count() -> io::Result { let handle = get_stdin_handle()?; let mut event_count: DWORD = unsafe { mem::zeroed() }; let success = unsafe { GetNumberOfConsoleInputEvents(handle, &mut event_count) }; if success == 0 { Err(io::Error::last_os_error()) } else { Ok(event_count) } } fn read_key_event() -> io::Result { let handle = get_stdin_handle()?; let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() }; let mut events_read: DWORD = unsafe { mem::zeroed() }; let mut key_event: KEY_EVENT_RECORD; loop { let success = unsafe { ReadConsoleInputW(handle, &mut buffer, 1, &mut events_read) }; if success == 0 { return Err(io::Error::last_os_error()); } if events_read == 0 { return Err(io::Error::new( io::ErrorKind::Other, "ReadConsoleInput returned no events, instead of waiting for an event", )); } if events_read == 1 && buffer.EventType != KEY_EVENT { // This isn't a key event; ignore it. continue; } key_event = unsafe { mem::transmute(buffer.Event) }; if key_event.bKeyDown == 0 { // This is a key being released; ignore it. continue; } return Ok(key_event); } } pub fn wants_emoji() -> bool { false } /// Returns true if there is an MSYS tty on the given handle. pub fn msys_tty_on(term: &Term) -> bool { let handle = term.as_raw_handle(); unsafe { let size = mem::size_of::(); let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::()]; let res = GetFileInformationByHandleEx( handle as *mut _, FileNameInfo, &mut *name_info_bytes as *mut _ as *mut c_void, name_info_bytes.len() as u32, ); if res == 0 { return false; } let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO); let s = slice::from_raw_parts( name_info.FileName.as_ptr(), name_info.FileNameLength as usize / 2, ); let name = String::from_utf16_lossy(s); // This checks whether 'pty' exists in the file name, which indicates that // a pseudo-terminal is attached. To mitigate against false positives // (e.g., an actual file name that contains 'pty'), we also require that // either the strings 'msys-' or 'cygwin-' are in the file name as well.) let is_msys = name.contains("msys-") || name.contains("cygwin-"); let is_pty = name.contains("-pty"); is_msys && is_pty } } pub fn set_title(title: T) { let buffer: Vec = OsStr::new(&format!("{}", title)) .encode_wide() .chain(once(0)) .collect(); unsafe { SetConsoleTitleW(buffer.as_ptr()); } } pub use common_term::{hide_cursor, show_cursor}; console-0.8.0/.cargo_vcs_info.json0000644000000001120000000000000125340ustar00{ "git": { "sha1": "f7660dbe32842a1a7b5e766b7e8588a5000c0aeb" } } console-0.8.0/Cargo.lock0000644000000140350000000000000105200ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "aho-corasick" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "atty" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "clicolors-control" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "console" version = "0.8.0" dependencies = [ "clicolors-control 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "encode_unicode 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lazy_static" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memchr" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "regex" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "termios" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "thread_local" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-width" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum clicolors-control 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90082ee5dcdd64dc4e9e0d37fbf3ee325419e39c0092191e0393df65518f741e" "checksum encode_unicode 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88c3d9193984285d544df4a30c23a4e62ead42edf70a4452ceb76dac1ce05c26" "checksum regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b143cceb2ca5e56d5671988ef8b15615733e7ee16cd348e064333b251b89343f" "checksum termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"