diff options
Diffstat (limited to 'vendor/console/src')
-rw-r--r-- | vendor/console/src/ansi.rs | 438 | ||||
-rw-r--r-- | vendor/console/src/common_term.rs | 72 | ||||
-rw-r--r-- | vendor/console/src/kb.rs | 29 | ||||
-rw-r--r-- | vendor/console/src/lib.rs | 104 | ||||
-rw-r--r-- | vendor/console/src/term.rs | 632 | ||||
-rw-r--r-- | vendor/console/src/unix_term.rs | 362 | ||||
-rw-r--r-- | vendor/console/src/utils.rs | 962 | ||||
-rw-r--r-- | vendor/console/src/wasm_term.rs | 54 | ||||
-rw-r--r-- | vendor/console/src/windows_term/colors.rs | 451 | ||||
-rw-r--r-- | vendor/console/src/windows_term/mod.rs | 563 |
10 files changed, 0 insertions, 3667 deletions
diff --git a/vendor/console/src/ansi.rs b/vendor/console/src/ansi.rs deleted file mode 100644 index 3a3c96c..0000000 --- a/vendor/console/src/ansi.rs +++ /dev/null @@ -1,438 +0,0 @@ -use std::{ - borrow::Cow, - iter::{FusedIterator, Peekable}, - str::CharIndices, -}; - -#[derive(Debug, Clone, Copy)] -enum State { - Start, - S1, - S2, - S3, - S4, - S5, - S6, - S7, - S8, - S9, - S10, - S11, - Trap, -} - -impl Default for State { - fn default() -> Self { - Self::Start - } -} - -impl State { - fn is_final(&self) -> bool { - #[allow(clippy::match_like_matches_macro)] - match self { - Self::S3 | Self::S5 | Self::S6 | Self::S7 | Self::S8 | Self::S9 | Self::S11 => true, - _ => false, - } - } - - fn is_trapped(&self) -> bool { - #[allow(clippy::match_like_matches_macro)] - match self { - Self::Trap => true, - _ => false, - } - } - - fn transition(&mut self, c: char) { - *self = match c { - '\u{1b}' | '\u{9b}' => match self { - Self::Start => Self::S1, - _ => Self::Trap, - }, - '(' | ')' => match self { - Self::S1 => Self::S2, - Self::S2 | Self::S4 => Self::S4, - _ => Self::Trap, - }, - ';' => match self { - Self::S1 | Self::S2 | Self::S4 => Self::S4, - Self::S5 | Self::S6 | Self::S7 | Self::S8 | Self::S10 => Self::S10, - _ => Self::Trap, - }, - - '[' | '#' | '?' => match self { - Self::S1 | Self::S2 | Self::S4 => Self::S4, - _ => Self::Trap, - }, - '0'..='2' => match self { - Self::S1 | Self::S4 => Self::S5, - Self::S2 => Self::S3, - Self::S5 => Self::S6, - Self::S6 => Self::S7, - Self::S7 => Self::S8, - Self::S8 => Self::S9, - Self::S10 => Self::S5, - _ => Self::Trap, - }, - '3'..='9' => match self { - Self::S1 | Self::S4 => Self::S5, - Self::S2 => Self::S5, - Self::S5 => Self::S6, - Self::S6 => Self::S7, - Self::S7 => Self::S8, - Self::S8 => Self::S9, - Self::S10 => Self::S5, - _ => Self::Trap, - }, - 'A'..='P' | 'R' | 'Z' | 'c' | 'f'..='n' | 'q' | 'r' | 'y' | '=' | '>' | '<' => { - match self { - Self::S1 - | Self::S2 - | Self::S4 - | Self::S5 - | Self::S6 - | Self::S7 - | Self::S8 - | Self::S10 => Self::S11, - _ => Self::Trap, - } - } - _ => Self::Trap, - }; - } -} - -#[derive(Debug)] -struct Matches<'a> { - s: &'a str, - it: Peekable<CharIndices<'a>>, -} - -impl<'a> Matches<'a> { - fn new(s: &'a str) -> Self { - let it = s.char_indices().peekable(); - Self { s, it } - } -} - -#[derive(Debug)] -struct Match<'a> { - text: &'a str, - start: usize, - end: usize, -} - -impl<'a> Match<'a> { - #[inline] - pub fn as_str(&self) -> &'a str { - &self.text[self.start..self.end] - } -} - -impl<'a> Iterator for Matches<'a> { - type Item = Match<'a>; - - fn next(&mut self) -> Option<Self::Item> { - find_ansi_code_exclusive(&mut self.it).map(|(start, end)| Match { - text: self.s, - start, - end, - }) - } -} - -impl<'a> FusedIterator for Matches<'a> {} - -fn find_ansi_code_exclusive(it: &mut Peekable<CharIndices>) -> Option<(usize, usize)> { - 'outer: loop { - if let (start, '\u{1b}') | (start, '\u{9b}') = it.peek()? { - let start = *start; - let mut state = State::default(); - let mut maybe_end = None; - - loop { - let item = it.peek(); - - if let Some((idx, c)) = item { - state.transition(*c); - - if state.is_final() { - maybe_end = Some(*idx); - } - } - - // The match is greedy so run till we hit the trap state no matter what. A valid - // match is just one that was final at some point - if state.is_trapped() || item.is_none() { - match maybe_end { - Some(end) => { - // All possible final characters are a single byte so it's safe to make - // the end exclusive by just adding one - return Some((start, end + 1)); - } - // The character we are peeking right now might be the start of a match so - // we want to continue the loop without popping off that char - None => continue 'outer, - } - } - - it.next(); - } - } - - it.next(); - } -} - -/// Helper function to strip ansi codes. -pub fn strip_ansi_codes(s: &str) -> Cow<str> { - let mut char_it = s.char_indices().peekable(); - match find_ansi_code_exclusive(&mut char_it) { - Some(_) => { - let stripped: String = AnsiCodeIterator::new(s) - .filter_map(|(text, is_ansi)| if is_ansi { None } else { Some(text) }) - .collect(); - Cow::Owned(stripped) - } - None => Cow::Borrowed(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<'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: Matches::new(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 - } - } -} - -impl<'a> FusedIterator for AnsiCodeIterator<'a> {} - -#[cfg(test)] -mod tests { - use super::*; - - use lazy_static::lazy_static; - use proptest::prelude::*; - use regex::Regex; - - // The manual dfa `State` is a handwritten translation from the previously used regex. That - // regex is kept here and used to ensure that the new matches are the same as the old - lazy_static! { - static ref STRIP_ANSI_RE: Regex = Regex::new( - r"[\x1b\x9b]([()][012AB]|[\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><])", - ) - .unwrap(); - } - - impl<'a, 'b> PartialEq<Match<'a>> for regex::Match<'b> { - fn eq(&self, other: &Match<'a>) -> bool { - self.start() == other.start && self.end() == other.end - } - } - - proptest! { - #[test] - fn dfa_matches_old_regex(s in r"([\x1b\x9b]?.*){0,5}") { - let old_matches: Vec<_> = STRIP_ANSI_RE.find_iter(&s).collect(); - let new_matches: Vec<_> = Matches::new(&s).collect(); - assert_eq!(old_matches, new_matches); - } - } - - #[test] - fn dfa_matches_regex_on_small_strings() { - // To make sure the test runs in a reasonable time this is a slimmed down list of - // characters to reduce the groups that are only used with each other along with one - // arbitrarily chosen character not used in the regex (' ') - const POSSIBLE_BYTES: &[u8] = &[b' ', 0x1b, 0x9b, b'(', b'0', b'[', b';', b'3', b'C']; - - fn check_all_strings_of_len(len: usize) { - _check_all_strings_of_len(len, &mut Vec::with_capacity(len)); - } - - fn _check_all_strings_of_len(len: usize, chunk: &mut Vec<u8>) { - if len == 0 { - if let Ok(s) = std::str::from_utf8(chunk) { - let old_matches: Vec<_> = STRIP_ANSI_RE.find_iter(s).collect(); - let new_matches: Vec<_> = Matches::new(s).collect(); - assert_eq!(old_matches, new_matches); - } - - return; - } - - for b in POSSIBLE_BYTES { - chunk.push(*b); - _check_all_strings_of_len(len - 1, chunk); - chunk.pop(); - } - } - - for str_len in 0..=6 { - check_all_strings_of_len(str_len); - } - } - - #[test] - fn complex_data() { - let s = std::fs::read_to_string( - std::path::Path::new("tests") - .join("data") - .join("sample_zellij_session.log"), - ) - .unwrap(); - - let old_matches: Vec<_> = STRIP_ANSI_RE.find_iter(&s).collect(); - let new_matches: Vec<_> = Matches::new(&s).collect(); - assert_eq!(old_matches, new_matches); - } - - #[test] - fn state_machine() { - let ansi_code = "\x1b)B"; - let mut state = State::default(); - assert!(!state.is_final()); - - for c in ansi_code.chars() { - state.transition(c); - } - assert!(state.is_final()); - - state.transition('A'); - assert!(state.is_trapped()); - } - - #[test] - fn back_to_back_entry_char() { - let s = "\x1b\x1bf"; - let matches: Vec<_> = Matches::new(s).map(|m| m.as_str()).collect(); - assert_eq!(&["\x1bf"], matches.as_slice()); - } - - #[test] - fn early_paren_can_use_many_chars() { - let s = "\x1b(C"; - let matches: Vec<_> = Matches::new(s).map(|m| m.as_str()).collect(); - assert_eq!(&[s], matches.as_slice()); - } - - #[test] - fn long_run_of_digits() { - let s = "\u{1b}00000"; - let matches: Vec<_> = Matches::new(s).map(|m| m.as_str()).collect(); - assert_eq!(&[s], matches.as_slice()); - } - - #[test] - fn test_ansi_iter_re_vt100() { - let s = "\x1b(0lpq\x1b)Benglish"; - let mut iter = AnsiCodeIterator::new(s); - assert_eq!(iter.next(), Some(("\x1b(0", true))); - assert_eq!(iter.next(), Some(("lpq", false))); - assert_eq!(iter.next(), Some(("\x1b)B", true))); - assert_eq!(iter.next(), Some(("english", false))); - } - - #[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); - } -} diff --git a/vendor/console/src/common_term.rs b/vendor/console/src/common_term.rs deleted file mode 100644 index 020660a..0000000 --- a/vendor/console/src/common_term.rs +++ /dev/null @@ -1,72 +0,0 @@ -use 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") -} diff --git a/vendor/console/src/kb.rs b/vendor/console/src/kb.rs deleted file mode 100644 index 5258c13..0000000 --- a/vendor/console/src/kb.rs +++ /dev/null @@ -1,29 +0,0 @@ -/// Key mapping -/// -/// This is an incomplete mapping of keys that are supported for reading -/// from the keyboard. -#[non_exhaustive] -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub enum Key { - Unknown, - /// Unrecognized sequence containing Esc and a list of chars - UnknownEscSeq(Vec<char>), - ArrowLeft, - ArrowRight, - ArrowUp, - ArrowDown, - Enter, - Escape, - Backspace, - Home, - End, - Tab, - BackTab, - Alt, - Del, - Shift, - Insert, - PageUp, - PageDown, - Char(char), -} diff --git a/vendor/console/src/lib.rs b/vendor/console/src/lib.rs deleted file mode 100644 index 1b18afc..0000000 --- a/vendor/console/src/lib.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! 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<dyn std::error::Error>> { -//! 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` automaticaly detects when to use colors based on the tty flag. 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, pad_str_with, - 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; diff --git a/vendor/console/src/term.rs b/vendor/console/src/term.rs deleted file mode 100644 index 0a40258..0000000 --- a/vendor/console/src/term.rs +++ /dev/null @@ -1,632 +0,0 @@ -use std::fmt::{Debug, Display}; -use std::io::{self, Read, 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}; - -#[cfg(unix)] -trait TermWrite: Write + Debug + AsRawFd + Send {} -#[cfg(unix)] -impl<T: Write + Debug + AsRawFd + Send> TermWrite for T {} - -#[cfg(unix)] -trait TermRead: Read + Debug + AsRawFd + Send {} -#[cfg(unix)] -impl<T: Read + Debug + AsRawFd + Send> TermRead for T {} - -#[cfg(unix)] -#[derive(Debug, Clone)] -pub struct ReadWritePair { - #[allow(unused)] - read: Arc<Mutex<dyn TermRead>>, - write: Arc<Mutex<dyn TermWrite>>, - style: Style, -} - -/// Where the term is writing. -#[derive(Debug, Clone)] -pub enum TermTarget { - Stdout, - Stderr, - #[cfg(unix)] - ReadWritePair(ReadWritePair), -} - -#[derive(Debug)] -pub struct TermInner { - target: TermTarget, - buffer: Option<Mutex<Vec<u8>>>, -} - -/// 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> { - /// Check if this is a real user attended terminal (`isatty`) - #[inline] - pub fn is_attended(&self) -> bool { - is_a_terminal(self.0) - } - - /// Check 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) - } - - /// Check 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 - } - } - - /// Check if this terminal wants emojis. - #[inline] - pub fn wants_emoji(&self) -> bool { - self.is_attended() && wants_emoji() - } - - /// Return 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<TermInner>, - 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![])), - }) - } - - /// Return a terminal for the given Read/Write pair styled like stderr. - #[cfg(unix)] - pub fn read_write_pair<R, W>(read: R, write: W) -> Term - where - R: Read + Debug + AsRawFd + Send + 'static, - W: Write + Debug + AsRawFd + Send + 'static, - { - Self::read_write_pair_with_style(read, write, Style::new().for_stderr()) - } - - /// Return a terminal for the given Read/Write pair. - #[cfg(unix)] - pub fn read_write_pair_with_style<R, W>(read: R, write: W, style: Style) -> Term - where - R: Read + Debug + AsRawFd + Send + 'static, - W: Write + Debug + AsRawFd + Send + 'static, - { - Term::with_inner(TermInner { - target: TermTarget::ReadWritePair(ReadWritePair { - read: Arc::new(Mutex::new(read)), - write: Arc::new(Mutex::new(write)), - style, - }), - buffer: None, - }) - } - - /// Return the style for this terminal. - #[inline] - pub fn style(&self) -> Style { - match self.inner.target { - TermTarget::Stderr => Style::new().for_stderr(), - TermTarget::Stdout => Style::new().for_stdout(), - #[cfg(unix)] - TermTarget::ReadWritePair(ReadWritePair { ref style, .. }) => style.clone(), - } - } - - /// Return the target of this terminal. - #[inline] - pub fn target(&self) -> TermTarget { - self.inner.target.clone() - } - - #[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()), - } - } - - /// Write a string to the terminal and add 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 - /// or complete key chord is entered. If the terminal is not user attended - /// the return value will be an error. - pub fn read_char(&self) -> io::Result<char> { - if !self.is_tty { - return Err(io::Error::new( - io::ErrorKind::NotConnected, - "Not a terminal", - )); - } - 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<Key> { - 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<String> { - 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<String> { - if !self.is_tty { - return Ok("".into()); - } - self.write_str(initial)?; - - let mut chars: Vec<char> = 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 => { - self.write_line("")?; - break; - } - _ => (), - } - } - Ok(chars.iter().collect::<String>()) - } - - /// Read a line of input securely. - /// - /// 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<String> { - if !self.is_tty { - return Ok("".into()); - } - match read_secure() { - Ok(rv) => { - self.write_line("")?; - Ok(rv) - } - Err(err) => Err(err), - } - } - - /// Flush 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(()) - } - - /// Check if the terminal is indeed a terminal. - #[inline] - pub fn is_term(&self) -> bool { - self.is_tty - } - - /// Check for common terminal features. - #[inline] - pub fn features(&self) -> TermFeatures<'_> { - TermFeatures(self) - } - - /// Return the terminal size in rows and columns or gets sensible defaults. - #[inline] - pub fn size(&self) -> (u16, u16) { - self.size_checked().unwrap_or((24, DEFAULT_WIDTH)) - } - - /// Return 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) - } - - /// Move the cursor to row `x` and column `y`. Values are 0-based. - #[inline] - pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> { - move_cursor_to(self, x, y) - } - - /// Move the cursor up by `n` lines, if possible. - /// - /// If there are less than `n` lines above the current cursor position, - /// the cursor is moved to the top line of the terminal (i.e., as far up as possible). - #[inline] - pub fn move_cursor_up(&self, n: usize) -> io::Result<()> { - move_cursor_up(self, n) - } - - /// Move the cursor down by `n` lines, if possible. - /// - /// If there are less than `n` lines below the current cursor position, - /// the cursor is moved to the bottom line of the terminal (i.e., as far down as possible). - #[inline] - pub fn move_cursor_down(&self, n: usize) -> io::Result<()> { - move_cursor_down(self, n) - } - - /// Move the cursor `n` characters to the left, if possible. - /// - /// If there are fewer than `n` characters to the left of the current cursor position, - /// the cursor is moved to the beginning of the line (i.e., as far to the left as possible). - #[inline] - pub fn move_cursor_left(&self, n: usize) -> io::Result<()> { - move_cursor_left(self, n) - } - - /// Move the cursor `n` characters to the right. - /// - /// If there are fewer than `n` characters to the right of the current cursor position, - /// the cursor is moved to the end of the current line (i.e., as far to the right as possible). - #[inline] - pub fn move_cursor_right(&self, n: usize) -> io::Result<()> { - move_cursor_right(self, n) - } - - /// Clear the current line. - /// - /// Position the cursor at the beginning of the current line. - #[inline] - pub fn clear_line(&self) -> io::Result<()> { - clear_line(self) - } - - /// Clear the last `n` lines before the current line. - /// - /// Position 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(()) - } - - /// Clear the entire screen. - /// - /// Move the cursor to the upper left corner of the screen. - #[inline] - pub fn clear_screen(&self) -> io::Result<()> { - clear_screen(self) - } - - /// Clear everything from the current cursor position to the end of the screen. - /// The cursor stays in its position. - #[inline] - pub fn clear_to_end_of_screen(&self) -> io::Result<()> { - clear_to_end_of_screen(self) - } - - /// Clear the last `n` characters of 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<T: Display>(&self, title: T) { - if !self.is_tty { - return; - } - set_title(title); - } - - /// Make the cursor visible again. - #[inline] - pub fn show_cursor(&self) -> io::Result<()> { - show_cursor(self) - } - - /// Hide the 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 { - 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()?; - } - #[cfg(unix)] - TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => { - let mut write = write.lock().unwrap(); - write.write_all(bytes)?; - write.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, - TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => { - write.lock().unwrap().as_raw_fd() - } - } - } -} - -#[cfg(windows)] -impl AsRawHandle for Term { - fn as_raw_handle(&self) -> RawHandle { - use windows_sys::Win32::System::Console::{ - GetStdHandle, 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 Write for Term { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - 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> Write for &'a Term { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - 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 Read for Term { - fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { - io::stdin().read(buf) - } -} - -impl<'a> Read for &'a Term { - fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { - 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::*; diff --git a/vendor/console/src/unix_term.rs b/vendor/console/src/unix_term.rs deleted file mode 100644 index 8e1e592..0000000 --- a/vendor/console/src/unix_term.rs +++ /dev/null @@ -1,362 +0,0 @@ -use std::env; -use 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::ptr; -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; - } - - if env::var("NO_COLOR").is_ok() { - return false; - } - - match env::var("TERM") { - Ok(term) => term != "dumb", - Err(_) => false, - } -} - -pub fn c_result<F: FnOnce() -> libc::c_int>(f: F) -> io::Result<()> { - let res = f(); - if res != 0 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } -} - -pub fn terminal_size(out: &Term) -> Option<(u16, u16)> { - unsafe { - if libc::isatty(libc::STDOUT_FILENO) != 1 { - return None; - } - - let mut winsize: libc::winsize = std::mem::zeroed(); - - // FIXME: ".into()" used as a temporary fix for a libc bug - // https://github.com/rust-lang/libc/pull/704 - #[allow(clippy::useless_conversion)] - libc::ioctl(out.as_raw_fd(), 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<String> { - let f_tty; - let fd = unsafe { - if libc::isatty(libc::STDIN_FILENO) == 1 { - f_tty = None; - libc::STDIN_FILENO - } else { - let f = fs::OpenOptions::new() - .read(true) - .write(true) - .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 poll_fd(fd: i32, timeout: i32) -> io::Result<bool> { - let mut pollfd = libc::pollfd { - fd, - events: libc::POLLIN, - revents: 0, - }; - let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, timeout) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(pollfd.revents & libc::POLLIN != 0) - } -} - -#[cfg(target_os = "macos")] -fn select_fd(fd: i32, timeout: i32) -> io::Result<bool> { - unsafe { - let mut read_fd_set: libc::fd_set = mem::zeroed(); - - let mut timeout_val; - let timeout = if timeout < 0 { - ptr::null_mut() - } else { - timeout_val = libc::timeval { - tv_sec: (timeout / 1000) as _, - tv_usec: (timeout * 1000) as _, - }; - &mut timeout_val - }; - - libc::FD_ZERO(&mut read_fd_set); - libc::FD_SET(fd, &mut read_fd_set); - let ret = libc::select( - fd + 1, - &mut read_fd_set, - ptr::null_mut(), - ptr::null_mut(), - timeout, - ); - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(libc::FD_ISSET(fd, &read_fd_set)) - } - } -} - -fn select_or_poll_term_fd(fd: i32, timeout: i32) -> io::Result<bool> { - // There is a bug on macos that ttys cannot be polled, only select() - // works. However given how problematic select is in general, we - // normally want to use poll there too. - #[cfg(target_os = "macos")] - { - if unsafe { libc::isatty(fd) == 1 } { - return select_fd(fd, timeout); - } - } - poll_fd(fd, timeout) -} - -fn read_single_char(fd: i32) -> io::Result<Option<char>> { - // timeout of zero means that it will not block - let is_ready = select_or_poll_term_fd(fd, 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<u8> { - 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) - } -} - -fn read_single_key_impl(fd: i32) -> Result<Key, io::Error> { - loop { - match read_single_char(fd)? { - Some('\x1b') => { - // Escape was read, keep reading in case we find a familiar key - break 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 { - '1' => Ok(Key::Home), // tmux - '2' => Ok(Key::Insert), - '3' => Ok(Key::Del), - '4' => Ok(Key::End), // tmux - '5' => Ok(Key::PageUp), - '6' => Ok(Key::PageDown), - '7' => Ok(Key::Home), // xrvt - '8' => Ok(Key::End), // xrvt - _ => 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]; - - break 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, - '\x01' => Key::Home, // Control-A (home) - '\x05' => Key::End, // Control-E (end) - '\x08' => Key::Backspace, // Control-H (8) (Identical to '\b') - _ => Key::Char(c), - }) - }; - } - None => { - // there is no subsequent byte ready to be read, block and wait for input - // negative timeout means that it will block indefinitely - match select_or_poll_term_fd(fd, -1) { - Ok(_) => continue, - Err(_) => break Err(io::Error::last_os_error()), - } - } - } - } -} - -pub fn read_single_key() -> io::Result<Key> { - let tty_f; - let fd = unsafe { - if libc::isatty(libc::STDIN_FILENO) == 1 { - libc::STDIN_FILENO - } else { - tty_f = fs::OpenOptions::new() - .read(true) - .write(true) - .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) }; - termios.c_oflag = original.c_oflag; - c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?; - let rv: io::Result<Key> = read_single_key_impl(fd); - 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<T: Display>(title: T) { - print!("\x1b]0;{}\x07", title); -} diff --git a/vendor/console/src/utils.rs b/vendor/console/src/utils.rs deleted file mode 100644 index 9e6b942..0000000 --- a/vendor/console/src/utils.rs +++ /dev/null @@ -1,962 +0,0 @@ -use std::borrow::Cow; -use std::collections::BTreeSet; -use std::env; -use std::fmt; -use std::sync::atomic::{AtomicBool, Ordering}; - -use lazy_static::lazy_static; - -use crate::term::{wants_emoji, Term}; - -#[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 { - #[allow(clippy::match_like_matches_macro)] - 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, - BlinkFast, - Reverse, - Hidden, - StrikeThrough, -} - -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::BlinkFast => 6, - Attribute::Reverse => 7, - Attribute::Hidden => 8, - Attribute::StrikeThrough => 9, - } - } -} - -/// 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<Color>, - bg: Option<Color>, - fg_bright: bool, - bg_bright: bool, - attrs: BTreeSet<Attribute>, - force: Option<bool>, - 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. `9.on_12` is - /// the same, but using 256 color numbers. 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(), - "blink_fast" => rv.blink_fast(), - "reverse" => rv.reverse(), - "hidden" => rv.hidden(), - "strikethrough" => rv.strikethrough(), - on_c if on_c.starts_with("on_") => { - if let Ok(n) = on_c[3..].parse::<u8>() { - rv.on_color256(n) - } else { - continue; - } - } - c => { - if let Ok(n) = c.parse::<u8>() { - rv.color256(n) - } else { - continue; - } - } - }; - } - rv - } - - /// Apply the style to something that can be displayed. - pub fn apply_to<D>(&self, val: D) -> StyledObject<D> { - StyledObject { - style: self.clone(), - val, - } - } - - /// Forces styling on or off. - /// - /// This overrides the automatic detection. - #[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 blink_fast(self) -> Style { - self.attr(Attribute::BlinkFast) - } - #[inline] - pub fn reverse(self) -> Style { - self.attr(Attribute::Reverse) - } - #[inline] - pub fn hidden(self) -> Style { - self.attr(Attribute::Hidden) - } - #[inline] - pub fn strikethrough(self) -> Style { - self.attr(Attribute::StrikeThrough) - } -} - -/// 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<D>(val: D) -> StyledObject<D> { - Style::new().apply_to(val) -} - -/// A formatting wrapper that can be styled for a terminal. -#[derive(Clone)] -pub struct StyledObject<D> { - style: Style, - val: D, -} - -impl<D> StyledObject<D> { - /// Forces styling on or off. - /// - /// This overrides the automatic detection. - #[inline] - pub fn force_styling(mut self, value: bool) -> StyledObject<D> { - 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<D> { - 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<D> { - self.style = self.style.for_stdout(); - self - } - - /// Sets a foreground color. - #[inline] - pub fn fg(mut self, color: Color) -> StyledObject<D> { - self.style = self.style.fg(color); - self - } - - /// Sets a background color. - #[inline] - pub fn bg(mut self, color: Color) -> StyledObject<D> { - self.style = self.style.bg(color); - self - } - - /// Adds a attr. - #[inline] - pub fn attr(mut self, attr: Attribute) -> StyledObject<D> { - self.style = self.style.attr(attr); - self - } - - #[inline] - pub fn black(self) -> StyledObject<D> { - self.fg(Color::Black) - } - #[inline] - pub fn red(self) -> StyledObject<D> { - self.fg(Color::Red) - } - #[inline] - pub fn green(self) -> StyledObject<D> { - self.fg(Color::Green) - } - #[inline] - pub fn yellow(self) -> StyledObject<D> { - self.fg(Color::Yellow) - } - #[inline] - pub fn blue(self) -> StyledObject<D> { - self.fg(Color::Blue) - } - #[inline] - pub fn magenta(self) -> StyledObject<D> { - self.fg(Color::Magenta) - } - #[inline] - pub fn cyan(self) -> StyledObject<D> { - self.fg(Color::Cyan) - } - #[inline] - pub fn white(self) -> StyledObject<D> { - self.fg(Color::White) - } - #[inline] - pub fn color256(self, color: u8) -> StyledObject<D> { - self.fg(Color::Color256(color)) - } - - #[inline] - pub fn bright(mut self) -> StyledObject<D> { - self.style = self.style.bright(); - self - } - - #[inline] - pub fn on_black(self) -> StyledObject<D> { - self.bg(Color::Black) - } - #[inline] - pub fn on_red(self) -> StyledObject<D> { - self.bg(Color::Red) - } - #[inline] - pub fn on_green(self) -> StyledObject<D> { - self.bg(Color::Green) - } - #[inline] - pub fn on_yellow(self) -> StyledObject<D> { - self.bg(Color::Yellow) - } - #[inline] - pub fn on_blue(self) -> StyledObject<D> { - self.bg(Color::Blue) - } - #[inline] - pub fn on_magenta(self) -> StyledObject<D> { - self.bg(Color::Magenta) - } - #[inline] - pub fn on_cyan(self) -> StyledObject<D> { - self.bg(Color::Cyan) - } - #[inline] - pub fn on_white(self) -> StyledObject<D> { - self.bg(Color::White) - } - #[inline] - pub fn on_color256(self, color: u8) -> StyledObject<D> { - self.bg(Color::Color256(color)) - } - - #[inline] - pub fn on_bright(mut self) -> StyledObject<D> { - self.style = self.style.on_bright(); - self - } - - #[inline] - pub fn bold(self) -> StyledObject<D> { - self.attr(Attribute::Bold) - } - #[inline] - pub fn dim(self) -> StyledObject<D> { - self.attr(Attribute::Dim) - } - #[inline] - pub fn italic(self) -> StyledObject<D> { - self.attr(Attribute::Italic) - } - #[inline] - pub fn underlined(self) -> StyledObject<D> { - self.attr(Attribute::Underlined) - } - #[inline] - pub fn blink(self) -> StyledObject<D> { - self.attr(Attribute::Blink) - } - #[inline] - pub fn blink_fast(self) -> StyledObject<D> { - self.attr(Attribute::BlinkFast) - } - #[inline] - pub fn reverse(self) -> StyledObject<D> { - self.attr(Attribute::Reverse) - } - #[inline] - pub fn hidden(self) -> StyledObject<D> { - self.attr(Attribute::Hidden) - } - #[inline] - pub fn strikethrough(self) -> StyledObject<D> { - self.attr(Attribute::StrikeThrough) - } -} - -macro_rules! impl_fmt { - ($name:ident) => { - impl<D: fmt::$name> fmt::$name for StyledObject<D> { - 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 if cfg!(feature = "unicode-width") { - 17 - } 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..." - ); -} diff --git a/vendor/console/src/wasm_term.rs b/vendor/console/src/wasm_term.rs deleted file mode 100644 index 764bc34..0000000 --- a/vendor/console/src/wasm_term.rs +++ /dev/null @@ -1,54 +0,0 @@ -use 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 { - #[cfg(target = "wasm32-wasi")] - { - unsafe { libc::isatty(out.as_raw_fd()) != 0 } - } - #[cfg(not(target = "wasm32-wasi"))] - { - false - } -} - -#[inline] -pub fn is_a_color_terminal(_out: &Term) -> bool { - // We currently never report color terminals. For discussion see - // the issue in the WASI repo: https://github.com/WebAssembly/WASI/issues/162 - false -} - -#[inline] -pub fn terminal_size(_out: &Term) -> Option<(u16, u16)> { - None -} - -pub fn read_secure() -> io::Result<String> { - Err(io::Error::new( - io::ErrorKind::Other, - "unsupported operation", - )) -} - -pub fn read_single_key() -> io::Result<Key> { - Err(io::Error::new( - io::ErrorKind::Other, - "unsupported operation", - )) -} - -#[inline] -pub fn wants_emoji() -> bool { - false -} - -pub fn set_title<T: Display>(_title: T) {} diff --git a/vendor/console/src/windows_term/colors.rs b/vendor/console/src/windows_term/colors.rs deleted file mode 100644 index dc8209d..0000000 --- a/vendor/console/src/windows_term/colors.rs +++ /dev/null @@ -1,451 +0,0 @@ -use std::io; -use std::mem; -use std::os::windows::io::AsRawHandle; -use std::str::Bytes; - -use windows_sys::Win32::Foundation::HANDLE; -use windows_sys::Win32::System::Console::{ - GetConsoleScreenBufferInfo, SetConsoleTextAttribute, CONSOLE_SCREEN_BUFFER_INFO, - FOREGROUND_BLUE as FG_BLUE, FOREGROUND_GREEN as FG_GREEN, FOREGROUND_INTENSITY as FG_INTENSITY, - FOREGROUND_RED as FG_RED, -}; - -use crate::Term; - -type WORD = u16; - -const FG_CYAN: WORD = FG_BLUE | FG_GREEN; -const FG_MAGENTA: WORD = FG_BLUE | FG_RED; -const FG_YELLOW: WORD = FG_GREEN | FG_RED; -const FG_WHITE: WORD = FG_BLUE | FG_GREEN | FG_RED; - -/// Query the given handle for information about the console's screen buffer. -/// -/// The given handle should represent a console. Otherwise, an error is -/// returned. -/// -/// This corresponds to calling [`GetConsoleScreenBufferInfo`]. -/// -/// [`GetConsoleScreenBufferInfo`]: https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo -pub fn screen_buffer_info(h: HANDLE) -> io::Result<ScreenBufferInfo> { - unsafe { - let mut info: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed(); - let rc = GetConsoleScreenBufferInfo(h, &mut info); - if rc == 0 { - return Err(io::Error::last_os_error()); - } - Ok(ScreenBufferInfo(info)) - } -} - -/// Set the text attributes of the console represented by the given handle. -/// -/// This corresponds to calling [`SetConsoleTextAttribute`]. -/// -/// [`SetConsoleTextAttribute`]: https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute -pub fn set_text_attributes(h: HANDLE, attributes: u16) -> io::Result<()> { - if unsafe { SetConsoleTextAttribute(h, attributes) } == 0 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } -} - -/// Represents console screen buffer information such as size, cursor position -/// and styling attributes. -/// -/// This wraps a [`CONSOLE_SCREEN_BUFFER_INFO`]. -/// -/// [`CONSOLE_SCREEN_BUFFER_INFO`]: https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str -#[derive(Clone)] -pub struct ScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO); - -impl ScreenBufferInfo { - /// Returns the character attributes associated with this console. - /// - /// This corresponds to `wAttributes`. - /// - /// See [`char info`] for more details. - /// - /// [`char info`]: https://docs.microsoft.com/en-us/windows/console/char-info-str - pub fn attributes(&self) -> u16 { - self.0.wAttributes - } -} - -/// A Windows console. -/// -/// This represents a very limited set of functionality available to a Windows -/// console. In particular, it can only change text attributes such as color -/// and intensity. This may grow over time. If you need more routines, please -/// file an issue and/or PR. -/// -/// There is no way to "write" to this console. Simply write to -/// stdout or stderr instead, while interleaving instructions to the console -/// to change text attributes. -/// -/// A common pitfall when using a console is to forget to flush writes to -/// stdout before setting new text attributes. -#[derive(Debug)] -pub struct Console { - kind: HandleKind, - start_attr: TextAttributes, - cur_attr: TextAttributes, -} - -#[derive(Clone, Copy, Debug)] -enum HandleKind { - Stdout, - Stderr, -} - -impl HandleKind { - fn handle(&self) -> HANDLE { - match *self { - HandleKind::Stdout => io::stdout().as_raw_handle() as HANDLE, - HandleKind::Stderr => io::stderr().as_raw_handle() as HANDLE, - } - } -} - -impl Console { - /// Get a console for a standard I/O stream. - fn create_for_stream(kind: HandleKind) -> io::Result<Console> { - let h = kind.handle(); - let info = screen_buffer_info(h)?; - let attr = TextAttributes::from_word(info.attributes()); - Ok(Console { - kind: kind, - start_attr: attr, - cur_attr: attr, - }) - } - - /// Create a new Console to stdout. - /// - /// If there was a problem creating the console, then an error is returned. - pub fn stdout() -> io::Result<Console> { - Self::create_for_stream(HandleKind::Stdout) - } - - /// Create a new Console to stderr. - /// - /// If there was a problem creating the console, then an error is returned. - pub fn stderr() -> io::Result<Console> { - Self::create_for_stream(HandleKind::Stderr) - } - - /// Applies the current text attributes. - fn set(&mut self) -> io::Result<()> { - set_text_attributes(self.kind.handle(), self.cur_attr.to_word()) - } - - /// Apply the given intensity and color attributes to the console - /// foreground. - /// - /// If there was a problem setting attributes on the console, then an error - /// is returned. - pub fn fg(&mut self, intense: Intense, color: Color) -> io::Result<()> { - self.cur_attr.fg_color = color; - self.cur_attr.fg_intense = intense; - self.set() - } - - /// Apply the given intensity and color attributes to the console - /// background. - /// - /// If there was a problem setting attributes on the console, then an error - /// is returned. - pub fn bg(&mut self, intense: Intense, color: Color) -> io::Result<()> { - self.cur_attr.bg_color = color; - self.cur_attr.bg_intense = intense; - self.set() - } - - /// Reset the console text attributes to their original settings. - /// - /// The original settings correspond to the text attributes on the console - /// when this `Console` value was created. - /// - /// If there was a problem setting attributes on the console, then an error - /// is returned. - pub fn reset(&mut self) -> io::Result<()> { - self.cur_attr = self.start_attr; - self.set() - } -} - -/// A representation of text attributes for the Windows console. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -struct TextAttributes { - fg_color: Color, - fg_intense: Intense, - bg_color: Color, - bg_intense: Intense, -} - -impl TextAttributes { - fn to_word(&self) -> WORD { - let mut w = 0; - w |= self.fg_color.to_fg(); - w |= self.fg_intense.to_fg(); - w |= self.bg_color.to_bg(); - w |= self.bg_intense.to_bg(); - w - } - - fn from_word(word: WORD) -> TextAttributes { - TextAttributes { - fg_color: Color::from_fg(word), - fg_intense: Intense::from_fg(word), - bg_color: Color::from_bg(word), - bg_intense: Intense::from_bg(word), - } - } -} - -/// Whether to use intense colors or not. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Intense { - Yes, - No, -} - -impl Intense { - fn to_bg(&self) -> WORD { - self.to_fg() << 4 - } - - fn from_bg(word: WORD) -> Intense { - Intense::from_fg(word >> 4) - } - - fn to_fg(&self) -> WORD { - match *self { - Intense::No => 0, - Intense::Yes => FG_INTENSITY, - } - } - - fn from_fg(word: WORD) -> Intense { - if word & FG_INTENSITY > 0 { - Intense::Yes - } else { - Intense::No - } - } -} - -/// The set of available colors for use with a Windows console. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Color { - Black, - Blue, - Green, - Red, - Cyan, - Magenta, - Yellow, - White, -} - -impl Color { - fn to_bg(&self) -> WORD { - self.to_fg() << 4 - } - - fn from_bg(word: WORD) -> Color { - Color::from_fg(word >> 4) - } - - fn to_fg(&self) -> WORD { - match *self { - Color::Black => 0, - Color::Blue => FG_BLUE, - Color::Green => FG_GREEN, - Color::Red => FG_RED, - Color::Cyan => FG_CYAN, - Color::Magenta => FG_MAGENTA, - Color::Yellow => FG_YELLOW, - Color::White => FG_WHITE, - } - } - - fn from_fg(word: WORD) -> Color { - match word & 0b111 { - FG_BLUE => Color::Blue, - FG_GREEN => Color::Green, - FG_RED => Color::Red, - FG_CYAN => Color::Cyan, - FG_MAGENTA => Color::Magenta, - FG_YELLOW => Color::Yellow, - FG_WHITE => Color::White, - _ => Color::Black, - } - } -} - -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((intense, color, fg_bg)) = driver(parse_color, part) { - match fg_bg { - FgBg::Foreground => con.fg(intense, color), - FgBg::Background => con.bg(intense, color), - }?; - } else if driver(parse_attr, part).is_none() { - out.write_through_common(part.as_bytes())?; - } - } - } - - Ok(()) -} - -#[derive(Debug, PartialEq, Eq)] -enum FgBg { - Foreground, - Background, -} - -impl FgBg { - fn new(byte: u8) -> Option<Self> { - match byte { - b'3' => Some(Self::Foreground), - b'4' => Some(Self::Background), - _ => None, - } - } -} - -fn driver<Out>(parse: fn(Bytes<'_>) -> Option<Out>, part: &str) -> Option<Out> { - let mut bytes = part.bytes(); - - loop { - while bytes.next()? != b'\x1b' {} - - if let ret @ Some(_) = (parse)(bytes.clone()) { - return ret; - } - } -} - -// `driver(parse_color, s)` parses the equivalent of the regex -// \x1b\[(3|4)8;5;(8|9|1[0-5])m -// for intense or -// \x1b\[(3|4)([0-7])m -// for normal -fn parse_color(mut bytes: Bytes<'_>) -> Option<(Intense, Color, FgBg)> { - parse_prefix(&mut bytes)?; - - let fg_bg = FgBg::new(bytes.next()?)?; - let (intense, color) = match bytes.next()? { - b @ b'0'..=b'7' => (Intense::No, normal_color_ansi_from_byte(b)?), - b'8' => { - if &[bytes.next()?, bytes.next()?, bytes.next()?] != b";5;" { - return None; - } - (Intense::Yes, parse_intense_color_ansi(&mut bytes)?) - } - _ => return None, - }; - - parse_suffix(&mut bytes)?; - Some((intense, color, fg_bg)) -} - -// `driver(parse_attr, s)` parses the equivalent of the regex -// \x1b\[([1-8])m -fn parse_attr(mut bytes: Bytes<'_>) -> Option<u8> { - parse_prefix(&mut bytes)?; - let attr = match bytes.next()? { - attr @ b'1'..=b'8' => attr, - _ => return None, - }; - parse_suffix(&mut bytes)?; - Some(attr) -} - -fn parse_prefix(bytes: &mut Bytes<'_>) -> Option<()> { - if bytes.next()? == b'[' { - Some(()) - } else { - None - } -} - -fn parse_intense_color_ansi(bytes: &mut Bytes<'_>) -> Option<Color> { - let color = match bytes.next()? { - b'8' => Color::Black, - b'9' => Color::Red, - b'1' => match bytes.next()? { - b'0' => Color::Green, - b'1' => Color::Yellow, - b'2' => Color::Blue, - b'3' => Color::Magenta, - b'4' => Color::Cyan, - b'5' => Color::White, - _ => return None, - }, - _ => return None, - }; - Some(color) -} - -fn normal_color_ansi_from_byte(b: u8) -> Option<Color> { - let color = match b { - b'0' => Color::Black, - b'1' => Color::Red, - b'2' => Color::Green, - b'3' => Color::Yellow, - b'4' => Color::Blue, - b'5' => Color::Magenta, - b'6' => Color::Cyan, - b'7' => Color::White, - _ => return None, - }; - Some(color) -} - -fn parse_suffix(bytes: &mut Bytes<'_>) -> Option<()> { - if bytes.next()? == b'm' { - Some(()) - } else { - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn color_parsing() { - let intense_color = "leading bytes \x1b[38;5;10m trailing bytes"; - let parsed = driver(parse_color, intense_color).unwrap(); - assert_eq!(parsed, (Intense::Yes, Color::Green, FgBg::Foreground)); - - let normal_color = "leading bytes \x1b[40m trailing bytes"; - let parsed = driver(parse_color, normal_color).unwrap(); - assert_eq!(parsed, (Intense::No, Color::Black, FgBg::Background)); - } - - #[test] - fn attr_parsing() { - let attr = "leading bytes \x1b[1m trailing bytes"; - let parsed = driver(parse_attr, attr).unwrap(); - assert_eq!(parsed, b'1'); - } -} diff --git a/vendor/console/src/windows_term/mod.rs b/vendor/console/src/windows_term/mod.rs deleted file mode 100644 index c4ec193..0000000 --- a/vendor/console/src/windows_term/mod.rs +++ /dev/null @@ -1,563 +0,0 @@ -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::raw::c_void; -use std::os::windows::ffi::OsStrExt; -use std::os::windows::io::AsRawHandle; -use std::slice; -use std::{char, mem::MaybeUninit}; - -use encode_unicode::error::InvalidUtf16Tuple; -use encode_unicode::CharExt; -use windows_sys::Win32::Foundation::{CHAR, HANDLE, INVALID_HANDLE_VALUE, MAX_PATH}; -use windows_sys::Win32::Storage::FileSystem::{ - FileNameInfo, GetFileInformationByHandleEx, FILE_NAME_INFO, -}; -use windows_sys::Win32::System::Console::{ - FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetConsoleCursorInfo, GetConsoleMode, - GetConsoleScreenBufferInfo, GetNumberOfConsoleInputEvents, GetStdHandle, ReadConsoleInputW, - SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleMode, SetConsoleTitleW, - CONSOLE_CURSOR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, KEY_EVENT, - KEY_EVENT_RECORD, STD_ERROR_HANDLE, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, -}; -use windows_sys::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY; - -use crate::common_term; -use crate::kb::Key; -use crate::term::{Term, TermTarget}; - -#[cfg(feature = "windows-console-colors")] -mod colors; - -#[cfg(feature = "windows-console-colors")] -pub use self::colors::*; - -const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x4; -pub const DEFAULT_WIDTH: u16 = 79; - -pub fn as_handle(term: &Term) -> HANDLE { - // convert between windows_sys::Win32::Foundation::HANDLE and std::os::windows::raw::HANDLE - term.as_raw_handle() as 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: &[STD_HANDLE]) -> 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(out: &Term) -> Option<(u16, u16)> { - use windows_sys::Win32::System::Console::SMALL_RECT; - - // convert between windows_sys::Win32::Foundation::HANDLE and std::os::windows::raw::HANDLE - let handle = out.as_raw_handle(); - let hand = handle as windows_sys::Win32::Foundation::HANDLE; - - if hand == INVALID_HANDLE_VALUE { - return None; - } - - let zc = COORD { X: 0, Y: 0 }; - let mut csbi = CONSOLE_SCREEN_BUFFER_INFO { - dwSize: zc, - dwCursorPosition: zc, - wAttributes: 0, - srWindow: SMALL_RECT { - Left: 0, - Top: 0, - Right: 0, - Bottom: 0, - }, - dwMaximumWindowSize: zc, - }; - if unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } == 0 { - return None; - } - - let rows = (csbi.srWindow.Bottom - csbi.srWindow.Top + 1) as u16; - let columns = (csbi.srWindow.Right - csbi.srWindow.Left + 1) as u16; - - Some((rows, columns)) -} - -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 u32, pos, &mut written); - FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, 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 u32, pos, &mut written); - FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, 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 u32 * csbi.dwSize.Y as u32; // as u32, 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 u32 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 u32 * csbi.srWindow.Bottom as u32; - let cells = bottom - (csbi.dwCursorPosition.X as u32 * csbi.dwCursorPosition.Y as u32); // as u32, 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 u32 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, &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, &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: VIRTUAL_KEY) -> Key { - use windows_sys::Win32::UI::Input::KeyboardAndMouse; - - match code { - KeyboardAndMouse::VK_LEFT => Key::ArrowLeft, - KeyboardAndMouse::VK_RIGHT => Key::ArrowRight, - KeyboardAndMouse::VK_UP => Key::ArrowUp, - KeyboardAndMouse::VK_DOWN => Key::ArrowDown, - KeyboardAndMouse::VK_RETURN => Key::Enter, - KeyboardAndMouse::VK_ESCAPE => Key::Escape, - KeyboardAndMouse::VK_BACK => Key::Backspace, - KeyboardAndMouse::VK_TAB => Key::Tab, - KeyboardAndMouse::VK_HOME => Key::Home, - KeyboardAndMouse::VK_END => Key::End, - KeyboardAndMouse::VK_DELETE => Key::Del, - KeyboardAndMouse::VK_SHIFT => Key::Shift, - KeyboardAndMouse::VK_MENU => Key::Alt, - _ => Key::Unknown, - } -} - -pub fn read_secure() -> io::Result<String> { - let mut rv = String::new(); - loop { - match read_single_key()? { - Key::Enter => { - break; - } - Key::Char('\x08') => { - if !rv.is_empty() { - 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<Key> { - let key_event = read_key_event()?; - - let unicode_char = unsafe { key_event.uChar.UnicodeChar }; - if unicode_char == 0 { - Ok(key_from_key_code(key_event.wVirtualKeyCode)) - } 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<HANDLE> { - 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<u32> { - let handle = get_stdin_handle()?; - let mut event_count: u32 = 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<KEY_EVENT_RECORD> { - let handle = get_stdin_handle()?; - let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() }; - - let mut events_read: u32 = 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 as u16 { - // 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 { - // If WT_SESSION is set, we can assume we're running in the nne - // Windows Terminal. The correct way to detect this is not available - // yet. See https://github.com/microsoft/terminal/issues/1040 - env::var("WT_SESSION").is_ok() -} - -/// 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 { - // Check whether the Windows 10 native pty is enabled - { - let mut out = MaybeUninit::uninit(); - let res = GetConsoleMode(handle as HANDLE, out.as_mut_ptr()); - if res != 0 // If res is true then out was initialized. - && (out.assume_init() & ENABLE_VIRTUAL_TERMINAL_PROCESSING) - == ENABLE_VIRTUAL_TERMINAL_PROCESSING - { - return true; - } - } - - let size = mem::size_of::<FILE_NAME_INFO>(); - let mut name_info_bytes = vec![0u8; size + MAX_PATH as usize * mem::size_of::<u16>()]; - let res = GetFileInformationByHandleEx( - handle as HANDLE, - 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<T: Display>(title: T) { - let buffer: Vec<u16> = OsStr::new(&format!("{}", title)) - .encode_wide() - .chain(once(0)) - .collect(); - unsafe { - SetConsoleTitleW(buffer.as_ptr()); - } -} |