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

1
vendor/console/.cargo-checksum.json vendored Normal file
View File

@ -0,0 +1 @@
{"files":{"CHANGELOG.md":"705c0d0cea7a8c090d679ac9399d9dcc31ed15c40eb571d91f03947ad86efe70","Cargo.lock":"3f19acf9c8353b6860267b136400702ec92486f700fc49d18f8862c9db056157","Cargo.toml":"9dccef268d95485475a4be6261bc2f51af881c6e78187dc881afa01621a23e01","LICENSE":"2022c11b24fc1e50fa06a5959e40198a3464f560729ad3d00139abb8a328362d","Makefile":"e708c1503cf3c5e6a218cdd703467f3ff7155f52b5a17950c6ecdf889a9e1338","README.md":"4f2d74ab86b2c1c517aafee98a8d7e53c0da38e1a4b765c7f1a8e0d6e7b1d07a","benches/ansi_parser.rs":"d323989ce68c1a1c0b6cd10f573e4dde11836a168f1b1bbe6c9c21b8db082629","examples/colors.rs":"395c5f8c4950830f3f960ed9b5522e93d5fdbda1d9c3d16c77144f4d87b503c7","examples/colors256.rs":"523682df72f7a4fe398e8d681661a007b96ad52d0c4c963cb22b07b85b1b4f2d","examples/cursor_at.rs":"9e21cedb6d0211fab831c71131bbf5e54e6df8e50129c36faee8957a45c33e1f","examples/term.rs":"10739f800cf0548bb8639d809c1f32b886f27ae4ba716d5db642fb6f37f580c4","scripts/wasmtime-wrapper.sh":"f7cbec1e6de7a456880c737ee264f2d010a40e6e80b4241e4099fb149389291a","src/ansi.rs":"b42b6806a7ca971e3ce2f307b74a3e7ff3f867b67cb342209b1153c0fed2e292","src/common_term.rs":"c924164ac0731148f3c696af0a9af5759465c959631d12fb90462a515e8c186c","src/kb.rs":"72f56323ca7b6741ddb2982c67610fe944cd58095051d84403a15d6e061422d0","src/lib.rs":"1bc9f12672660506c74a313132bb3d2f4fdec550b46c6df75b1cc8124b13d583","src/term.rs":"390403b40053f484a446795198dbaaf9774ea678f153bb19cd70d247b5e01947","src/unix_term.rs":"a1f70fcca92fa019159a7da82d52c62c242acfa51ba85ebae81623c7e6e603aa","src/utils.rs":"1d481c0f6593b29155238fd335f18096b14988fcb725eb2d82667b8f324ac6f6","src/wasm_term.rs":"43b9968ef158df93a269b62d5224837c10ef4d0b7f9c7a3253f8ad099a448af5","src/windows_term/colors.rs":"4c7a53c2341502965dc339f7cd9df54bf3f0b9fbddbba5158757743900e028c5","src/windows_term/mod.rs":"3da9e7e19b9ec0bd172403c8f142192f3e418210d3e28858f363a61e8a041eaf","tests/data/sample_zellij_session.log":"341286d8721451c0a7dc647e7b98438191651199b2d5f03d132e3e971aeddadb"},"package":"c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"}

103
vendor/console/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,103 @@
# Changelog
## 0.15.7
### Enhancements
* Set an appropriate lower version of libc for macos changes.
* Improved behavior of `read_single_key` so it does not disturb other
threads quite as much. (#165)
* More reliably reset raw mode in terminal. (#171)
## 0.15.6
### Enhancements
* Switch to `select()` on macOS for polling on TTYs to work around
a macOS bug. (#169)
* Added blink fast and strikethrough attributes. (#159)
## 0.15.5
### Enhancements
* Removed `regex` dependency. (#153)
* Clarified that `clicolors-control` is no longer used.
* Handle non-tty terminals in `read_char`. (#124)
## 0.15.4
### Enhancements
* Fix for regression where console size was misreported on windows. (#151)
## 0.15.3
### Enhancements
* Dropped `terminal_size` dependency.
## 0.15.2
### Enhancements
* Dropped `once_cell` dependency to support MSRV again.
## 0.15.1
### Enhancements
* ANSI support no longer depends on `regex` crate.
* Crate now supports `minver`.
## 0.15.0
### Enhancements
* Added more key recognitions
* Exposed `pad_str_with` to public API
* Added `ReadWritePair`
* Support `color256` in `Style::from_dotted_str`
### BREAKING
* Added `ReadWritePair` to `TermTarget` to allow arbitrary read write pairs behave as a term
* Removed `Copy` and `PartialEq` from `TermTarget`
## 0.14.1
### Enhancements
* Added `NO_COLOR` support
* Added some more key recognitions
* Undeprecate `Term::is_term`
## 0.14.0
### Enhancements
* Added emoji support for newer Windows terminals.
### BREAKING
* Made the windows terminal emulation a non default feature (`windows-console-colors`)
## 0.13.0
### Enhancements
* Added `user_attended_stderr` for checking if stderr is a terminal
* Removed `termios` dependency
### Bug Fixes
* Better handling of key recognition on unix
* `Term::terminal_size()` on stderr terms correctly returns stderr term info
### Deprecated
* Deprecate `Term::is_term()` in favor of `Term::features().is_attended()`
### BREAKING
* Remove `Term::want_emoji()` in favor of `Term::features().wants_emoji()`

272
vendor/console/Cargo.lock generated vendored Normal file
View File

@ -0,0 +1,272 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console"
version = "0.15.7"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"proptest",
"regex",
"unicode-width",
"windows-sys",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proptest"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5"
dependencies = [
"bit-set",
"bitflags",
"byteorder",
"lazy_static",
"num-traits",
"quick-error",
"rand",
"rand_chacha",
"rand_xorshift",
"regex-syntax",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_xorshift"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
dependencies = [
"rand_core",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

72
vendor/console/Cargo.toml vendored Normal file
View File

@ -0,0 +1,72 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
rust-version = "1.48.0"
name = "console"
version = "0.15.7"
authors = ["Armin Ronacher <armin.ronacher@active-4.com>"]
description = "A terminal and console abstraction for Rust"
homepage = "https://github.com/console-rs/console"
documentation = "https://docs.rs/console"
readme = "README.md"
keywords = [
"cli",
"terminal",
"colors",
"console",
"ansi",
]
license = "MIT"
repository = "https://github.com/console-rs/console"
[dependencies.lazy_static]
version = "1.4.0"
[dependencies.libc]
version = "0.2.99"
[dependencies.unicode-width]
version = "0.1"
optional = true
[dev-dependencies.proptest]
version = "1.0.0"
features = [
"std",
"bit-set",
"break-dead-code",
]
default-features = false
[dev-dependencies.regex]
version = "1.4.2"
[features]
ansi-parsing = []
default = [
"unicode-width",
"ansi-parsing",
]
windows-console-colors = ["ansi-parsing"]
[target."cfg(windows)".dependencies.encode_unicode]
version = "0.3"
[target."cfg(windows)".dependencies.windows-sys]
version = "0.45.0"
features = [
"Win32_Foundation",
"Win32_System_Console",
"Win32_Storage_FileSystem",
"Win32_UI_Input_KeyboardAndMouse",
]

22
vendor/console/LICENSE vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2017 Armin Ronacher <armin.ronacher@active-4.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

39
vendor/console/Makefile vendored Normal file
View File

@ -0,0 +1,39 @@
all: test
check:
@cargo check --all-features
build:
@cargo build --all-features
doc:
@cargo doc --all-features
test:
@echo "CARGO TESTS"
@cargo test
@cargo test --all-features
@cargo test --no-default-features
check-minver:
@echo "MINVER CHECK"
@cargo minimal-versions check
@cargo minimal-versions check --all-features
@cargo minimal-versions check --no-default-features
format:
@rustup component add rustfmt 2> /dev/null
@cargo fmt --all
format-check:
@rustup component add rustfmt 2> /dev/null
@cargo fmt --all -- --check
lint:
@rustup component add clippy 2> /dev/null
@cargo clippy --examples --tests
msrv-lock:
@cargo update -p proptest --precise=1.0.0
.PHONY: all doc build check test format format-check lint check-minver msrv-lock

76
vendor/console/README.md vendored Normal file
View File

@ -0,0 +1,76 @@
# `console`
[![Build Status](https://github.com/console-rs/console/workflows/CI/badge.svg?branch=master)](https://github.com/console-rs/console/actions?query=workflow%3ACI)
[![Crates.io](https://img.shields.io/crates/d/console.svg)](https://crates.io/crates/console)
[![License](https://img.shields.io/github/license/console-rs/console)](https://github.com/console-rs/console/blob/master/LICENSE)
[![rustc 1.48.0](https://img.shields.io/badge/rust-1.48%2B-orange.svg)](https://img.shields.io/badge/rust-1.48%2B-orange.svg)
[![Documentation](https://docs.rs/console/badge.svg)](https://docs.rs/console)
**console** is a library for Rust that provides access to various terminal
features so you can build nicer looking command line interfaces. It
comes with various tools and utilities for working with Terminals and
formatting text.
Best paired with other libraries in the family:
* [dialoguer](https://docs.rs/dialoguer)
* [indicatif](https://docs.rs/indicatif)
## Terminal Access
The terminal is abstracted through the `console::Term` type. It can
either directly provide access to the connected terminal or by buffering
up commands. A buffered terminal will however not be completely buffered
on windows where cursor movements are currently directly passed through.
Example usage:
```rust
use std::thread;
use std::time::Duration;
use console::Term;
let term = Term::stdout();
term.write_line("Hello World!")?;
thread::sleep(Duration::from_millis(2000));
term.clear_line()?;
```
## Colors and Styles
`console` 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:
```rust
use console::style;
println!("This is {} neat", style("quite").cyan());
```
You can also store styles and apply them to text later:
```rust
use console::Style;
let cyan = Style::new().cyan();
println!("This is {} neat", cyan.apply_to("quite"));
```
## Working with ANSI Codes
The crate provides the function `strip_ansi_codes` to remove ANSI codes
from a string as well as `measure_text_width` to calculate the width of a
string as it would be displayed by the terminal. Both of those together
are useful for more complex formatting.
## Unicode Width Support
By default this crate depends on the `unicode-width` crate to calculate
the width of terminal characters. If you do not need this you can disable
the `unicode-width` feature which will cut down on dependencies.
License: MIT

27
vendor/console/benches/ansi_parser.rs vendored Normal file
View File

@ -0,0 +1,27 @@
use console::{strip_ansi_codes, AnsiCodeIterator};
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
use std::{fs, path::Path};
pub fn parse_throughput(c: &mut Criterion) {
let session_log_path = Path::new("tests")
.join("data")
.join("sample_zellij_session.log");
let session_log = fs::read_to_string(session_log_path).unwrap();
let mut group = c.benchmark_group("ansi-parsing");
group.throughput(Throughput::Bytes(session_log.len() as u64));
group.bench_function("parse", |b| {
b.iter(|| {
let v: Vec<_> = AnsiCodeIterator::new(&session_log).collect();
black_box(v);
})
});
group.bench_function("strip", |b| {
b.iter(|| black_box(strip_ansi_codes(&session_log)))
});
group.finish();
}
criterion_group!(throughput, parse_throughput);
criterion_main!(throughput);

14
vendor/console/examples/colors.rs vendored Normal file
View File

@ -0,0 +1,14 @@
use console::style;
fn main() {
println!(
"This is red on black: {:010x}",
style(42).red().on_black().bold()
);
println!("This is reversed: [{}]", style("whatever").reverse());
println!("This is cyan: {}", style("whatever").cyan());
eprintln!(
"This is black bright: {}",
style("whatever").for_stderr().bright().black()
);
}

17
vendor/console/examples/colors256.rs vendored Normal file
View File

@ -0,0 +1,17 @@
use console::style;
fn main() {
for i in 0..=255 {
print!("{:03} ", style(i).color256(i));
if i % 16 == 15 {
println!();
}
}
for i in 0..=255 {
print!("{:03} ", style(i).black().on_color256(i));
if i % 16 == 15 {
println!();
}
}
}

30
vendor/console/examples/cursor_at.rs vendored Normal file
View File

@ -0,0 +1,30 @@
extern crate console;
use std::io;
use std::thread;
use std::time::Duration;
use console::{style, Term};
fn write_chars() -> io::Result<()> {
let term = Term::stdout();
let (heigth, width) = term.size();
for x in 0..width {
for y in 0..heigth {
term.move_cursor_to(x as usize, y as usize)?;
let text = if (x + y) % 2 == 0 {
format!("{}", style(x % 10).black().on_red())
} else {
format!("{}", style(x % 10).red().on_black())
};
term.write_str(&text)?;
thread::sleep(Duration::from_micros(600));
}
}
Ok(())
}
fn main() {
write_chars().unwrap();
}

33
vendor/console/examples/term.rs vendored Normal file
View File

@ -0,0 +1,33 @@
use std::io::{self, Write};
use std::thread;
use std::time::Duration;
use console::{style, Term};
fn do_stuff() -> io::Result<()> {
let term = Term::stdout();
term.set_title("Counting...");
term.write_line("Going to do some counting now")?;
term.hide_cursor()?;
for x in 0..10 {
if x != 0 {
term.move_cursor_up(1)?;
}
term.write_line(&format!("Counting {}/10", style(x + 1).cyan()))?;
thread::sleep(Duration::from_millis(400));
}
term.show_cursor()?;
term.clear_last_lines(1)?;
term.write_line("Done counting!")?;
writeln!(&term, "Hello World!")?;
write!(&term, "To edit: ")?;
let res = term.read_line_initial_text("default")?;
writeln!(&term, "\n{}", res)?;
Ok(())
}
fn main() {
do_stuff().unwrap();
}

4
vendor/console/scripts/wasmtime-wrapper.sh vendored Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $SCRIPT_DIR/..
wasmtime run --env INSTA_WORKSPACE_ROOT=/ --mapdir "/::$(pwd)" -- "$@"

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

File diff suppressed because one or more lines are too long