summaryrefslogtreecommitdiff
path: root/vendor/console/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/console/src')
-rw-r--r--vendor/console/src/ansi.rs438
-rw-r--r--vendor/console/src/common_term.rs72
-rw-r--r--vendor/console/src/kb.rs29
-rw-r--r--vendor/console/src/lib.rs104
-rw-r--r--vendor/console/src/term.rs632
-rw-r--r--vendor/console/src/unix_term.rs362
-rw-r--r--vendor/console/src/utils.rs962
-rw-r--r--vendor/console/src/wasm_term.rs54
-rw-r--r--vendor/console/src/windows_term/colors.rs451
-rw-r--r--vendor/console/src/windows_term/mod.rs563
10 files changed, 3667 insertions, 0 deletions
diff --git a/vendor/console/src/ansi.rs b/vendor/console/src/ansi.rs
new file mode 100644
index 0000000..3a3c96c
--- /dev/null
+++ b/vendor/console/src/ansi.rs
@@ -0,0 +1,438 @@
+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
new file mode 100644
index 0000000..020660a
--- /dev/null
+++ b/vendor/console/src/common_term.rs
@@ -0,0 +1,72 @@
+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
new file mode 100644
index 0000000..5258c13
--- /dev/null
+++ b/vendor/console/src/kb.rs
@@ -0,0 +1,29 @@
+/// 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
new file mode 100644
index 0000000..1b18afc
--- /dev/null
+++ b/vendor/console/src/lib.rs
@@ -0,0 +1,104 @@
+//! 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
new file mode 100644
index 0000000..0a40258
--- /dev/null
+++ b/vendor/console/src/term.rs
@@ -0,0 +1,632 @@
+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
new file mode 100644
index 0000000..8e1e592
--- /dev/null
+++ b/vendor/console/src/unix_term.rs
@@ -0,0 +1,362 @@
+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
new file mode 100644
index 0000000..9e6b942
--- /dev/null
+++ b/vendor/console/src/utils.rs
@@ -0,0 +1,962 @@
+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
new file mode 100644
index 0000000..764bc34
--- /dev/null
+++ b/vendor/console/src/wasm_term.rs
@@ -0,0 +1,54 @@
+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
new file mode 100644
index 0000000..dc8209d
--- /dev/null
+++ b/vendor/console/src/windows_term/colors.rs
@@ -0,0 +1,451 @@
+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
new file mode 100644
index 0000000..c4ec193
--- /dev/null
+++ b/vendor/console/src/windows_term/mod.rs
@@ -0,0 +1,563 @@
+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());
+ }
+}