console-0.13.0/.cargo_vcs_info.json0000644000000001121374303736500126220ustar { "git": { "sha1": "903e94a4eb352c635ee195275ec818ec228c2dbd" } } console-0.13.0/.gitignore010064400007650000024000000000221365010515400133760ustar 00000000000000target Cargo.lock console-0.13.0/.travis.yml010064400007650000024000000002271365010515400135260ustar 00000000000000sudo: false language: rust rust: - stable - beta - nightly script: - make test - if [ "$TRAVIS_RUST_VERSION" = stable ]; then make lint; fi console-0.13.0/CHANGELOG.md010064400007650000024000000007151374302752600132410ustar 00000000000000# Changelog ## 0.13.0 ### Enhancements * Added `user_attended_stderr` for checking if stderr is a terminal * Removed `termios` dependency ### Bug Fixes * Better handling of key recognition on unix * `Term::terminal_size()` on stderr terms correctly returns stderr term info ### Deprecated * Deprecate `Term::is_term()` in favor of `Term::features().is_attended()` ### BREAKING * Remove `Term::want_emoji()` in favor of `Term::features().wants_emoji()` console-0.13.0/Cargo.lock0000644000000051111374303736500106010ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "console" version = "0.13.0" dependencies = [ "encode_unicode", "lazy_static", "libc", "regex", "terminal_size", "unicode-width", "winapi", "winapi-util", ] [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" [[package]] name = "regex" version = "1.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" dependencies = [ "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" [[package]] name = "terminal_size" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a14cd9f8c72704232f0bfc8455c0e861f0ad4eb60cc9ec8a170e231414c1e13" dependencies = [ "libc", "winapi", ] [[package]] name = "unicode-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" console-0.13.0/Cargo.toml0000644000000032261374303736500106310ustar # 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] edition = "2018" name = "console" version = "0.13.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" repository = "https://github.com/mitsuhiko/console" [dependencies.lazy_static] version = "1" [dependencies.libc] version = "0.2" [dependencies.regex] version = "1.3.1" features = ["std"] optional = true default-features = false [dependencies.terminal_size] version = "0.1.13" [dependencies.unicode-width] version = "0.1" optional = true [features] ansi-parsing = ["regex"] default = ["unicode-width", "ansi-parsing", "windows-console-colors"] windows-console-colors = ["ansi-parsing", "winapi-util"] [target."cfg(windows)".dependencies.encode_unicode] version = "0.3" [target."cfg(windows)".dependencies.winapi] version = "0.3" features = ["winbase", "winuser", "consoleapi", "processenv", "wincon"] [target."cfg(windows)".dependencies.winapi-util] version = "0.1.3" optional = true console-0.13.0/Cargo.toml.orig0000644000000017751374303736500115770ustar [package] name = "console" description = "A terminal and console abstraction for Rust" version = "0.13.0" keywords = ["cli", "terminal", "colors", "console", "ansi"] authors = ["Armin Ronacher "] license = "MIT" edition = "2018" homepage = "https://github.com/mitsuhiko/console" repository = "https://github.com/mitsuhiko/console" documentation = "https://docs.rs/console" readme = "README.md" [features] default = ["unicode-width", "ansi-parsing", "windows-console-colors"] windows-console-colors = ["ansi-parsing", "winapi-util"] ansi-parsing = ["regex"] [dependencies] lazy_static = "1" libc = "0.2" terminal_size = "0.1.13" regex = { version = "1.3.1", optional = true, default-features = false, features = ["std"] } unicode-width = { version = "0.1", optional = true } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winbase", "winuser", "consoleapi", "processenv", "wincon"] } winapi-util = { version = "0.1.3", optional = true } encode_unicode = "0.3" console-0.13.0/Cargo.toml.orig010064400007650000024000000017751374303735700143310ustar 00000000000000[package] name = "console" description = "A terminal and console abstraction for Rust" version = "0.13.0" keywords = ["cli", "terminal", "colors", "console", "ansi"] authors = ["Armin Ronacher "] license = "MIT" edition = "2018" homepage = "https://github.com/mitsuhiko/console" repository = "https://github.com/mitsuhiko/console" documentation = "https://docs.rs/console" readme = "README.md" [features] default = ["unicode-width", "ansi-parsing", "windows-console-colors"] windows-console-colors = ["ansi-parsing", "winapi-util"] ansi-parsing = ["regex"] [dependencies] lazy_static = "1" libc = "0.2" terminal_size = "0.1.13" regex = { version = "1.3.1", optional = true, default-features = false, features = ["std"] } unicode-width = { version = "0.1", optional = true } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winbase", "winuser", "consoleapi", "processenv", "wincon"] } winapi-util = { version = "0.1.3", optional = true } encode_unicode = "0.3" console-0.13.0/LICENSE010064400007650000024000000021301365010515400124150ustar 00000000000000The 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.13.0/Makefile010064400007650000024000000011101365010515400130450ustar 00000000000000all: test .PHONY: all format: @rustup component add rustfmt 2> /dev/null @cargo fmt --all .PHONY: format format-check: @rustup component add rustfmt 2> /dev/null @cargo fmt --all -- --check .PHONY: format-check lint: @rustup component add clippy 2> /dev/null @cargo clippy .PHONY: lint update-readme: @cargo readme > README.md .PHONY: update-readme test: @cargo test @cargo test --no-default-features @rustup target add wasm32-unknown-unknown @cargo check --target wasm32-unknown-unknown @rustup target add wasm32-wasi @cargo check --target wasm32-wasi .PHONY: test console-0.13.0/README.md010064400007650000024000000036151365010515400127000ustar 00000000000000# console 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: ```rust 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()?; ``` ## 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: ```rust use console::style; println!("This is {} neat", style("quite").cyan()); ``` You can also store styles and apply them to text later: ```rust 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. ## Unicode Width Support By default this crate depends on the `unicode-width` crate to calculate the width of terminal characters. If you do not need this you can disable the `unicode-width` feature which will cut down on dependencies. License: MIT console-0.13.0/appveyor.yml010064400007650000024000000012311365010515400140010ustar 00000000000000cache: - '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.13.0/src/ansi.rs010064400007650000024000000102361365203174600135140ustar 00000000000000use std::borrow::Cow; use regex::{Matches, Regex}; lazy_static::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(); } /// Helper function to strip ansi codes. pub fn strip_ansi_codes(s: &str) -> Cow { STRIP_ANSI_RE.replace_all(s, "") } /// 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 } } } #[test] fn test_ansi_iter_re() { use crate::style; 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); } #[test] fn test_ansi_iter_re_on_multi() { use crate::style; let s = format!("{}", style("a").red().bold().force_styling(true)); let mut iter = AnsiCodeIterator::new(&s); assert_eq!(iter.next(), Some(("\x1b[31m", true))); assert_eq!(iter.current_slice(), "\x1b[31m"); assert_eq!(iter.rest_slice(), "\x1b[1ma\x1b[0m"); assert_eq!(iter.next(), Some(("\x1b[1m", true))); assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1m"); assert_eq!(iter.rest_slice(), "a\x1b[0m"); assert_eq!(iter.next(), Some(("a", false))); assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma"); assert_eq!(iter.rest_slice(), "\x1b[0m"); assert_eq!(iter.next(), Some(("\x1b[0m", true))); assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma\x1b[0m"); assert_eq!(iter.rest_slice(), ""); assert_eq!(iter.next(), None); } console-0.13.0/src/common_term.rs010064400007650000024000000027341365455440500151110ustar 00000000000000use std::io; use crate::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 move_cursor_left(out: &Term, n: usize) -> io::Result<()> { if n > 0 { out.write_str(&format!("\x1b[{}D", n)) } else { Ok(()) } } pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> { if n > 0 { out.write_str(&format!("\x1b[{}C", n)) } else { Ok(()) } } #[inline] pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> { out.write_str(&format!("\x1B[{};{}H", y + 1, x + 1)) } pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> { if n > 0 { out.write_str(&format!("\x1b[{}D\x1b[0K", n)) } else { Ok(()) } } #[inline] pub fn clear_line(out: &Term) -> io::Result<()> { out.write_str("\r\x1b[2K") } #[inline] pub fn clear_screen(out: &Term) -> io::Result<()> { out.write_str("\r\x1b[2J\r\x1b[H") } #[inline] pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> { out.write_str("\r\x1b[0J") } #[inline] pub fn show_cursor(out: &Term) -> io::Result<()> { out.write_str("\x1b[?25h") } #[inline] pub fn hide_cursor(out: &Term) -> io::Result<()> { out.write_str("\x1b[?25l") } console-0.13.0/src/kb.rs010064400007650000024000000007631374301472500131610ustar 00000000000000/// Key mapping /// /// This is an incomplete mapping of keys that are supported for reading /// from the keyboard. #[non_exhaustive] #[derive(Clone, PartialEq, Eq, Debug)] pub enum Key { Unknown, /// Unrecognized sequence containing Esc and a list of chars UnknownEscSeq(Vec), ArrowLeft, ArrowRight, ArrowUp, ArrowDown, Enter, Escape, Backspace, Home, End, Tab, BackTab, Del, Insert, PageUp, PageDown, Char(char), } console-0.13.0/src/lib.rs010064400007650000024000000062311374302355300133260ustar 00000000000000//! 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(()) } 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. //! //! # Unicode Width Support //! //! By default this crate depends on the `unicode-width` crate to calculate //! the width of terminal characters. If you do not need this you can disable //! the `unicode-width` feature which will cut down on dependencies. //! //! # Features //! //! By default all features are enabled. The following features exist: //! //! * `unicode-width`: adds support for unicode width calculations //! * `ansi-parsing`: adds support for parsing ansi codes (this adds support //! for stripping and taking ansi escape codes into account for length //! calculations). pub use crate::kb::Key; pub use crate::term::{ user_attended, user_attended_stderr, Term, TermFamily, TermFeatures, TermTarget, }; pub use crate::utils::{ colors_enabled, colors_enabled_stderr, measure_text_width, pad_str, set_colors_enabled, set_colors_enabled_stderr, style, truncate_str, Alignment, Attribute, Color, Emoji, Style, StyledObject, }; #[cfg(feature = "ansi-parsing")] pub use crate::ansi::{strip_ansi_codes, AnsiCodeIterator}; mod common_term; mod kb; mod term; #[cfg(unix)] mod unix_term; mod utils; #[cfg(target_arch = "wasm32")] mod wasm_term; #[cfg(windows)] mod windows_term; #[cfg(feature = "ansi-parsing")] mod ansi; console-0.13.0/src/term.rs010064400007650000024000000364541374302631500135400ustar 00000000000000use 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 crate::{kb::Key, utils::Style}; /// 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, /// A dummy terminal (for instance on wasm) Dummy, } /// 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 { is_a_color_terminal(self.0) } /// 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(not(windows))] { 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 } #[cfg(target_arch = "wasm32")] { TermFamily::Dummy } } } /// 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, pub(crate) is_msys_tty: bool, pub(crate) is_tty: bool, } impl Term { fn with_inner(inner: TermInner) -> Term { let mut term = Term { inner: Arc::new(inner), is_msys_tty: false, is_tty: false, }; term.is_msys_tty = term.features().is_msys_tty(); term.is_tty = term.features().is_attended(); term } /// 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 style for the term #[inline] pub fn style(&self) -> Style { match self.target() { TermTarget::Stderr => Style::new().for_stderr(), TermTarget::Stdout => Style::new().for_stdout(), } } /// Returns the targert #[inline] 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'); } Key::Unknown => { return Err(io::Error::new( io::ErrorKind::NotConnected, "Not a terminal", )) } _ => {} } } } /// 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_tty { 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_tty { 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 one line of input with initial text. /// /// 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_initial_text(&self, initial: &str) -> io::Result { if !self.is_tty { return Ok("".into()); } self.write_str(initial)?; let mut chars: Vec = initial.chars().collect(); loop { match self.read_key()? { Key::Backspace => { if chars.pop().is_some() { self.clear_chars(1)?; } self.flush()?; } Key::Char(chr) => { chars.push(chr); let mut bytes_char = [0; 4]; chr.encode_utf8(&mut bytes_char); self.write_str(chr.encode_utf8(&mut bytes_char))?; self.flush()?; } Key::Enter => break, Key::Unknown => { return Err(io::Error::new( io::ErrorKind::NotConnected, "Not a terminal", )) } _ => (), } } Ok(chars.iter().collect::()) } /// 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_tty { 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. #[deprecated(note = "Use features().is_attended() instead", since = "0.13.0")] #[inline] pub fn is_term(&self) -> bool { self.is_tty } /// Checks for common terminal features. #[inline] pub fn features(&self) -> TermFeatures<'_> { TermFeatures(self) } /// 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(self) } /// Moves the cursor to `x` and `y` #[inline] pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> { move_cursor_to(self, x, y) } /// Moves the cursor up `n` lines #[inline] pub fn move_cursor_up(&self, n: usize) -> io::Result<()> { move_cursor_up(self, n) } /// Moves the cursor down `n` lines #[inline] pub fn move_cursor_down(&self, n: usize) -> io::Result<()> { move_cursor_down(self, n) } /// Moves the cursor left `n` lines #[inline] pub fn move_cursor_left(&self, n: usize) -> io::Result<()> { move_cursor_left(self, n) } /// Moves the cursor down `n` lines #[inline] pub fn move_cursor_right(&self, n: usize) -> io::Result<()> { move_cursor_right(self, n) } /// Clears the current line. /// /// The positions the cursor at the beginning of the line again. #[inline] 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. #[inline] pub fn clear_screen(&self) -> io::Result<()> { clear_screen(self) } /// Clears the entire screen. #[inline] pub fn clear_to_end_of_screen(&self) -> io::Result<()> { clear_to_end_of_screen(self) } /// Clears the last char in the the current line. #[inline] pub fn clear_chars(&self, n: usize) -> io::Result<()> { clear_chars(self, n) } /// Set the terminal title pub fn set_title(&self, title: T) { if !self.is_tty { return; } set_title(title); } /// Makes cursor visible again #[inline] pub fn show_cursor(&self) -> io::Result<()> { show_cursor(self) } /// Hides cursor #[inline] pub fn hide_cursor(&self) -> io::Result<()> { hide_cursor(self) } // helpers #[cfg(all(windows, feature = "windows-console-colors"))] fn write_through(&self, bytes: &[u8]) -> io::Result<()> { if self.is_msys_tty || !self.is_tty { self.write_through_common(bytes) } else { use winapi_util::console::Console; match self.inner.target { TermTarget::Stdout => console_colors(self, Console::stdout()?, bytes), TermTarget::Stderr => console_colors(self, Console::stderr()?, bytes), } } } #[cfg(not(all(windows, feature = "windows-console-colors")))] fn write_through(&self, bytes: &[u8]) -> io::Result<()> { self.write_through_common(bytes) } pub(crate) fn write_through_common(&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 for stdout. /// /// 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` feature on the stdout terminal. #[inline] pub fn user_attended() -> bool { Term::stdout().features().is_attended() } /// A fast way to check if the application has a user attended for stderr. /// /// This means that stderr is connected to a terminal instead of a /// file or redirected by other means. This is a shortcut for /// checking the `is_attended` feature on the stderr terminal. #[inline] pub fn user_attended_stderr() -> bool { Term::stderr().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 { match self.inner.buffer { Some(ref buffer) => buffer.lock().unwrap().write_all(buf), None => self.write_through(buf), }?; Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Term::flush(self) } } impl<'a> io::Write for &'a Term { fn write(&mut self, buf: &[u8]) -> io::Result { match self.inner.buffer { Some(ref buffer) => buffer.lock().unwrap().write_all(buf), None => self.write_through(buf), }?; Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Term::flush(self) } } 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 crate::unix_term::*; #[cfg(target_arch = "wasm32")] pub use crate::wasm_term::*; #[cfg(windows)] pub use crate::windows_term::*; console-0.13.0/src/unix_term.rs010064400007650000024000000210231374302716500145710ustar 00000000000000use std::env; use std::fmt::Display; use std::fs; use std::io; use std::io::{BufRead, BufReader}; use std::os::unix::io::AsRawFd; use std::str; use crate::kb::Key; use crate::term::Term; pub use crate::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 is_a_color_terminal(out: &Term) -> bool { if !is_a_terminal(out) { return false; } match env::var("TERM") { Ok(term) => term != "dumb", Err(_) => false, } } pub fn c_result libc::c_int>(f: F) -> io::Result<()> { let res = f(); if res != 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } #[inline] pub fn terminal_size(out: &Term) -> Option<(u16, u16)> { terminal_size::terminal_size_using_fd(out.as_raw_fd()).map(|x| ((x.1).0, (x.0).0)) } 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 = core::mem::MaybeUninit::uninit(); c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?; let mut termios = unsafe { termios.assume_init() }; let original = termios; termios.c_lflag &= !libc::ECHO; c_result(|| unsafe { libc::tcsetattr(fd, libc::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) }; c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &original) })?; read_rv.map(|_| { let len = rv.trim_end_matches(&['\r', '\n'][..]).len(); rv.truncate(len); rv }) } fn read_single_char(fd: i32) -> io::Result> { let mut pollfd = libc::pollfd { fd, events: libc::POLLIN, revents: 0, }; // timeout of zero means that it will not block let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, 0) }; if ret < 0 { return Err(io::Error::last_os_error()); } let is_ready = pollfd.revents & libc::POLLIN != 0; if is_ready { // if there is something to be read, take 1 byte from it let mut buf: [u8; 1] = [0]; read_bytes(fd, &mut buf, 1)?; Ok(Some(buf[0] as char)) } else { //there is nothing to be read Ok(None) } } // Similar to libc::read. Read count bytes into slice buf from descriptor fd. // If successful, return the number of bytes read. // Will return an error if nothing was read, i.e when called at end of file. fn read_bytes(fd: i32, buf: &mut [u8], count: u8) -> io::Result { let read = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, count as usize) }; if read < 0 { Err(io::Error::last_os_error()) } else if read == 0 { Err(io::Error::new( io::ErrorKind::UnexpectedEof, "Reached end of file", )) } else if buf[0] == b'\x03' { Err(io::Error::new( io::ErrorKind::Interrupted, "read interrupted", )) } else { Ok(read as u8) } } 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 termios = core::mem::MaybeUninit::uninit(); c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?; let mut termios = unsafe { termios.assume_init() }; let original = termios; unsafe { libc::cfmakeraw(&mut termios) }; c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?; let rv = match read_single_char(fd)? { Some('\x1b') => { // Escape was read, keep reading in case we find a familiar key if let Some(c1) = read_single_char(fd)? { if c1 == '[' { if let Some(c2) = read_single_char(fd)? { match c2 { 'A' => Ok(Key::ArrowUp), 'B' => Ok(Key::ArrowDown), 'C' => Ok(Key::ArrowRight), 'D' => Ok(Key::ArrowLeft), 'H' => Ok(Key::Home), 'F' => Ok(Key::End), 'Z' => Ok(Key::BackTab), _ => { let c3 = read_single_char(fd)?; if let Some(c3) = c3 { if c3 == '~' { match c2 { '2' => Ok(Key::Insert), '3' => Ok(Key::Del), '5' => Ok(Key::PageUp), '6' => Ok(Key::PageDown), _ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])), } } else { Ok(Key::UnknownEscSeq(vec![c1, c2, c3])) } } else { // \x1b[ and 1 more char Ok(Key::UnknownEscSeq(vec![c1, c2])) } } } } else { // \x1b[ and no more input Ok(Key::UnknownEscSeq(vec![c1])) } } else { // char after escape is not [ Ok(Key::UnknownEscSeq(vec![c1])) } } else { //nothing after escape Ok(Key::Escape) } } Some(c) => { let byte = c as u8; let mut buf: [u8; 4] = [byte, 0, 0, 0]; if byte & 224u8 == 192u8 { // a two byte unicode character read_bytes(fd, &mut buf[1..], 1)?; Ok(key_from_utf8(&buf[..2])) } else if byte & 240u8 == 224u8 { // a three byte unicode character read_bytes(fd, &mut buf[1..], 2)?; Ok(key_from_utf8(&buf[..3])) } else if byte & 248u8 == 240u8 { // a four byte unicode character read_bytes(fd, &mut buf[1..], 3)?; Ok(key_from_utf8(&buf[..4])) } else { Ok(match c { '\n' | '\r' => Key::Enter, '\x7f' => Key::Backspace, '\t' => Key::Tab, _ => Key::Char(c), }) } } None => { // there is no subsequent byte ready to be read, block and wait for input let mut pollfd = libc::pollfd { fd, events: libc::POLLIN, revents: 0, }; // negative timeout means that it will block indefinitely let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, -1) }; if ret < 0 { return Err(io::Error::last_os_error()); } read_single_key() } }; c_result(|| unsafe { libc::tcsetattr(fd, libc::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_utf8(buf: &[u8]) -> Key { if let Ok(s) = str::from_utf8(buf) { if let Some(c) = s.chars().next() { return Key::Char(c); } } Key::Unknown } #[cfg(not(target_os = "macos"))] lazy_static::lazy_static! { static ref IS_LANG_UTF8: bool = { match std::env::var("LANG") { Ok(lang) => lang.to_uppercase().ends_with("UTF-8"), _ => false, } }; } #[cfg(target_os = "macos")] pub fn wants_emoji() -> bool { true } #[cfg(not(target_os = "macos"))] pub fn wants_emoji() -> bool { *IS_LANG_UTF8 } pub fn set_title(title: T) { print!("\x1b]0;{}\x07", title); } console-0.13.0/src/utils.rs010064400007650000024000000604661374301472500137330ustar 00000000000000use std::borrow::Cow; use std::collections::BTreeSet; use std::env; use std::fmt; use std::sync::atomic::{AtomicBool, Ordering}; use crate::term::{wants_emoji, Term}; use lazy_static::lazy_static; #[cfg(feature = "ansi-parsing")] use crate::ansi::{strip_ansi_codes, AnsiCodeIterator}; #[cfg(not(feature = "ansi-parsing"))] fn strip_ansi_codes(s: &str) -> &str { s } fn default_colors_enabled(out: &Term) -> bool { (out.features().colors_supported() && &env::var("CLICOLOR").unwrap_or_else(|_| "1".into()) != "0") || &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0" } lazy_static! { static ref STDOUT_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stdout())); static ref STDERR_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stderr())); } /// Returns `true` if colors should be enabled for stdout. /// /// 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. #[inline] pub fn colors_enabled() -> bool { STDOUT_COLORS.load(Ordering::Relaxed) } /// Forces colorization on or off for stdout. /// /// This overrides the default for the current process and changes the return value of the /// `colors_enabled` function. #[inline] pub fn set_colors_enabled(val: bool) { STDOUT_COLORS.store(val, Ordering::Relaxed) } /// Returns `true` if colors should be enabled for stderr. /// /// 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. #[inline] pub fn colors_enabled_stderr() -> bool { STDERR_COLORS.load(Ordering::Relaxed) } /// Forces colorization on or off for stderr. /// /// This overrides the default for the current process and changes the return value of the /// `colors_enabled` function. #[inline] pub fn set_colors_enabled_stderr(val: bool) { STDERR_COLORS.store(val, Ordering::Relaxed) } /// Measure the width of a string in terminal characters. pub fn measure_text_width(s: &str) -> usize { str_width(&strip_ansi_codes(s)) } /// A terminal color. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Color { Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, Color256(u8), } 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, Color::Color256(x) => x as usize, } } #[inline] fn is_color256(self) -> bool { match self { Color::Color256(_) => true, _ => false, } } } /// 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, fg_bright: bool, bg_bright: bool, attrs: BTreeSet, force: Option, for_stderr: bool, } 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, fg_bright: false, bg_bright: false, attrs: BTreeSet::new(), force: None, for_stderr: false, } } /// 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(), "bright" => rv.bright(), "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(), "on_bright" => rv.on_bright(), "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 } /// Specifies that style is applying to something being written on stderr. #[inline] pub fn for_stderr(mut self) -> Style { self.for_stderr = true; self } /// Specifies that style is applying to something being written on stdout. /// /// This is the default behaviour. #[inline] pub fn for_stdout(mut self) -> Style { self.for_stderr = false; 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 color256(self, color: u8) -> Style { self.fg(Color::Color256(color)) } #[inline] pub fn bright(mut self) -> Style { self.fg_bright = true; self } #[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 on_color256(self, color: u8) -> Style { self.bg(Color::Color256(color)) } #[inline] pub fn on_bright(mut self) -> Style { self.bg_bright = true; self } #[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 } /// Specifies that style is applying to something being written on stderr #[inline] pub fn for_stderr(mut self) -> StyledObject { self.style = self.style.for_stderr(); self } /// Specifies that style is applying to something being written on stdout /// /// This is the default #[inline] pub fn for_stdout(mut self) -> StyledObject { self.style = self.style.for_stdout(); 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 color256(self, color: u8) -> StyledObject { self.fg(Color::Color256(color)) } #[inline] pub fn bright(mut self) -> StyledObject { self.style = self.style.bright(); self } #[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 on_color256(self, color: u8) -> StyledObject { self.bg(Color::Color256(color)) } #[inline] pub fn on_bright(mut self) -> StyledObject { self.style = self.style.on_bright(); self } #[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(|| match self.style.for_stderr { true => colors_enabled_stderr(), false => colors_enabled(), }) { if let Some(fg) = self.style.fg { if fg.is_color256() { write!(f, "\x1b[38;5;{}m", fg.ansi_num())?; } else if self.style.fg_bright { write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?; } else { write!(f, "\x1b[{}m", fg.ansi_num() + 30)?; } reset = true; } if let Some(bg) = self.style.bg { if bg.is_color256() { write!(f, "\x1b[48;5;{}m", bg.ansi_num())?; } else if self.style.bg_bright { write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?; } else { 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) } } } fn str_width(s: &str) -> usize { #[cfg(feature = "unicode-width")] { use unicode_width::UnicodeWidthStr; s.width() } #[cfg(not(feature = "unicode-width"))] { s.chars().count() } } #[cfg(feature = "ansi-parsing")] fn char_width(c: char) -> usize { #[cfg(feature = "unicode-width")] { use unicode_width::UnicodeWidthChar; c.width().unwrap_or(0) } #[cfg(not(feature = "unicode-width"))] { let _c = c; 1 } } /// 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> { #[cfg(feature = "ansi-parsing")] { use std::cmp::Ordering; 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 str_width(s) + length > width - str_width(tail) { let ts = iter.current_slice(); let mut s_byte = 0; let mut s_width = 0; let rest_width = width - str_width(tail) - length; for c in s.chars() { s_byte += c.len_utf8(); s_width += char_width(c); match s_width.cmp(&rest_width) { Ordering::Equal => break, Ordering::Greater => { s_byte -= c.len_utf8(); break; } Ordering::Less => continue, } } let idx = ts.len() - s.len() + s_byte; let mut buf = ts[..idx].to_string(); buf.push_str(tail); rv = Some(buf); } length += str_width(s); } } (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) } } #[cfg(not(feature = "ansi-parsing"))] { if s.len() <= width - tail.len() { Cow::Borrowed(s) } else { Cow::Owned(format!( "{}{}", s.get(..width - tail.len()).unwrap_or_default(), tail )) } } } /// 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), if cfg!(feature = "ansi-parsing") { 3 } else { 21 } ); } #[test] #[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))] 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_truncate_str_no_ansi() { assert_eq!(&truncate_str("foo bar", 5, ""), "foo b"); assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !"); assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar..."); } #[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..." ); } console-0.13.0/src/wasm_term.rs010064400007650000024000000013571374303030000145460ustar 00000000000000use std::fmt::Display; use std::io; use crate::kb::Key; use crate::term::Term; pub use crate::common_term::*; pub const DEFAULT_WIDTH: u16 = 80; #[inline] pub fn is_a_terminal(_out: &Term) -> bool { false } #[inline] pub fn is_a_color_terminal(_out: &Term) -> bool { false } #[inline] pub fn terminal_size(_out: &Term) -> Option<(u16, u16)> { None } pub fn read_secure() -> io::Result { Err(io::Error::new( io::ErrorKind::Other, "unsupported operation", )) } pub fn read_single_key() -> io::Result { Err(io::Error::new( io::ErrorKind::Other, "unsupported operation", )) } #[inline] pub fn wants_emoji() -> bool { false } pub fn set_title(_title: T) {} console-0.13.0/src/windows_term.rs010064400007650000024000000466651374302704600153210ustar 00000000000000use std::char; use std::cmp; use std::env; 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; #[cfg(feature = "windows-console-colors")] use regex::Regex; use winapi::ctypes::c_void; use winapi::shared::minwindef::DWORD; use winapi::shared::minwindef::MAX_PATH; use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode}; 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::{ FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetConsoleCursorInfo, GetConsoleScreenBufferInfo, SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleTitleW, CONSOLE_CURSOR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, KEY_EVENT, KEY_EVENT_RECORD, }; use winapi::um::winnt::{CHAR, HANDLE, INT, WCHAR}; #[cfg(feature = "windows-console-colors")] use winapi_util::console::{Color, Console, Intense}; use crate::common_term; use crate::kb::Key; use crate::term::{Term, TermTarget}; #[cfg(feature = "windows-console-colors")] lazy_static::lazy_static! { static ref INTENSE_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)8;5;(8|9|1[0-5])m").unwrap(); static ref NORMAL_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)([0-7])m").unwrap(); static ref ATTR_RE: Regex = Regex::new(r"\x1b\[([1-8])m").unwrap(); } const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x4; 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) } pub fn is_a_color_terminal(out: &Term) -> bool { if !is_a_terminal(out) { return false; } if msys_tty_on(out) { return match env::var("TERM") { Ok(term) => term != "dumb", Err(_) => true, }; } enable_ansi_on(out) } fn enable_ansi_on(out: &Term) -> bool { unsafe { let handle = as_handle(out); let mut dw_mode = 0; if GetConsoleMode(handle, &mut dw_mode) == 0 { return false; } dw_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; if SetConsoleMode(handle, dw_mode) == 0 { return false; } true } } 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 } #[inline] pub fn terminal_size(_out: &Term) -> Option<(u16, u16)> { // TODO: Use term target for getting size terminal_size::terminal_size().map(|x| ((x.1).0, (x.0).0)) } pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> { if out.is_msys_tty { return common_term::move_cursor_to(out, x, y); } if let Some((hand, _)) = get_console_screen_buffer_info(as_handle(out)) { unsafe { SetConsoleCursorPosition( hand, COORD { X: x as i16, Y: y as i16, }, ); } } Ok(()) } pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> { if out.is_msys_tty { return common_term::move_cursor_up(out, n); } if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize - n)?; } Ok(()) } pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> { if out.is_msys_tty { return common_term::move_cursor_down(out, n); } if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize + n)?; } Ok(()) } pub fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> { if out.is_msys_tty { return common_term::move_cursor_left(out, n); } if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { move_cursor_to( out, csbi.dwCursorPosition.X as usize - n, csbi.dwCursorPosition.Y as usize, )?; } Ok(()) } pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> { if out.is_msys_tty { return common_term::move_cursor_right(out, n); } if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { move_cursor_to( out, csbi.dwCursorPosition.X as usize + n, csbi.dwCursorPosition.Y as usize, )?; } Ok(()) } pub fn clear_line(out: &Term) -> io::Result<()> { if out.is_msys_tty { 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); FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written); SetConsoleCursorPosition(hand, pos); } } Ok(()) } pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> { if out.is_msys_tty { return common_term::clear_chars(out, n); } if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { unsafe { let width = cmp::min(csbi.dwCursorPosition.X, n as i16); let pos = COORD { X: csbi.dwCursorPosition.X - width, Y: csbi.dwCursorPosition.Y, }; let mut written = 0; FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written); FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written); SetConsoleCursorPosition(hand, pos); } } Ok(()) } pub fn clear_screen(out: &Term) -> io::Result<()> { if out.is_msys_tty { 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. FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written); SetConsoleCursorPosition(hand, pos); } } Ok(()) } pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> { if out.is_msys_tty { return common_term::clear_to_end_of_screen(out); } if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { unsafe { let bottom = csbi.srWindow.Right as DWORD * csbi.srWindow.Bottom as DWORD; let cells = bottom - (csbi.dwCursorPosition.X as DWORD * csbi.dwCursorPosition.Y as DWORD); // as DWORD, or else this causes stack overflows. let pos = COORD { X: 0, Y: csbi.dwCursorPosition.Y, }; let mut written = 0; FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as DWORD no longer needed. FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written); SetConsoleCursorPosition(hand, pos); } } Ok(()) } pub fn show_cursor(out: &Term) -> io::Result<()> { if out.is_msys_tty { return common_term::show_cursor(out); } if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) { unsafe { cci.bVisible = 1; SetConsoleCursorInfo(hand, &mut cci); } } Ok(()) } pub fn hide_cursor(out: &Term) -> io::Result<()> { if out.is_msys_tty { return common_term::hide_cursor(out); } if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) { unsafe { cci.bVisible = 0; SetConsoleCursorInfo(hand, &mut cci); } } 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)), } } fn get_console_cursor_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_CURSOR_INFO)> { let mut cci: CONSOLE_CURSOR_INFO = unsafe { mem::zeroed() }; match unsafe { GetConsoleCursorInfo(hand, &mut cci) } { 0 => None, _ => Some((hand, cci)), } } 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::Backspace, winapi::um::winuser::VK_TAB => Key::Tab, winapi::um::winuser::VK_HOME => Key::Home, winapi::um::winuser::VK_END => Key::End, winapi::um::winuser::VK_DELETE => Key::Del, _ => 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 if c == '\x08' { Ok(Key::Backspace) } else if c == '\x1B' { Ok(Key::Escape) } 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()); } } #[cfg(feature = "windows-console-colors")] pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<()> { use crate::ansi::AnsiCodeIterator; use std::str::from_utf8; let s = from_utf8(bytes).expect("data to be printed is not an ansi string"); let mut iter = AnsiCodeIterator::new(s); while !iter.rest_slice().is_empty() { if let Some((part, is_esc)) = iter.next() { if !is_esc { out.write_through_common(part.as_bytes())?; } else if part == "\x1b[0m" { con.reset()?; } else if let Some(cap) = INTENSE_COLOR_RE.captures(part) { let color = get_color_from_ansi(cap.get(2).unwrap().as_str()); match cap.get(1).unwrap().as_str() { "3" => con.fg(Intense::Yes, color)?, "4" => con.bg(Intense::Yes, color)?, _ => unreachable!(), }; } else if let Some(cap) = NORMAL_COLOR_RE.captures(part) { let color = get_color_from_ansi(cap.get(2).unwrap().as_str()); match cap.get(1).unwrap().as_str() { "3" => con.fg(Intense::No, color)?, "4" => con.bg(Intense::No, color)?, _ => unreachable!(), }; } else if !ATTR_RE.is_match(part) { out.write_through_common(part.as_bytes())?; } } } Ok(()) } #[cfg(feature = "windows-console-colors")] fn get_color_from_ansi(ansi: &str) -> Color { match ansi { "0" | "8" => Color::Black, "1" | "9" => Color::Red, "2" | "10" => Color::Green, "3" | "11" => Color::Yellow, "4" | "12" => Color::Blue, "5" | "13" => Color::Magenta, "6" | "14" => Color::Cyan, "7" | "15" => Color::White, _ => unreachable!(), } }