Initial vendor packages

Signed-off-by: Valentin Popov <valentin@popov.link>
This commit is contained in:
2024-01-08 01:21:28 +04:00
parent 5ecd8cf2cb
commit 1b6a04ca55
7309 changed files with 2160054 additions and 0 deletions

438
vendor/console/src/ansi.rs vendored Normal file
View File

@ -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);
}
}

72
vendor/console/src/common_term.rs vendored Normal file
View File

@ -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")
}

29
vendor/console/src/kb.rs vendored Normal file
View File

@ -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),
}

104
vendor/console/src/lib.rs vendored Normal file
View File

@ -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;

632
vendor/console/src/term.rs vendored Normal file
View File

@ -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::*;

362
vendor/console/src/unix_term.rs vendored Normal file
View File

@ -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);
}

962
vendor/console/src/utils.rs vendored Normal file
View File

@ -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..."
);
}

54
vendor/console/src/wasm_term.rs vendored Normal file
View File

@ -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) {}

View File

@ -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');
}
}

563
vendor/console/src/windows_term/mod.rs vendored Normal file
View File

@ -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());
}
}