Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
This commit is contained in:
1
vendor/console/.cargo-checksum.json
vendored
Normal file
1
vendor/console/.cargo-checksum.json
vendored
Normal 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
103
vendor/console/CHANGELOG.md
vendored
Normal 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
272
vendor/console/Cargo.lock
generated
vendored
Normal 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
72
vendor/console/Cargo.toml
vendored
Normal 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
22
vendor/console/LICENSE
vendored
Normal 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
39
vendor/console/Makefile
vendored
Normal 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
76
vendor/console/README.md
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
# `console`
|
||||
|
||||
[](https://github.com/console-rs/console/actions?query=workflow%3ACI)
|
||||
[](https://crates.io/crates/console)
|
||||
[](https://github.com/console-rs/console/blob/master/LICENSE)
|
||||
[](https://img.shields.io/badge/rust-1.48%2B-orange.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
27
vendor/console/benches/ansi_parser.rs
vendored
Normal 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
14
vendor/console/examples/colors.rs
vendored
Normal 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
17
vendor/console/examples/colors256.rs
vendored
Normal 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
30
vendor/console/examples/cursor_at.rs
vendored
Normal 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
33
vendor/console/examples/term.rs
vendored
Normal 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
4
vendor/console/scripts/wasmtime-wrapper.sh
vendored
Executable 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
438
vendor/console/src/ansi.rs
vendored
Normal 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
72
vendor/console/src/common_term.rs
vendored
Normal 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
29
vendor/console/src/kb.rs
vendored
Normal 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
104
vendor/console/src/lib.rs
vendored
Normal 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
632
vendor/console/src/term.rs
vendored
Normal 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
362
vendor/console/src/unix_term.rs
vendored
Normal 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
962
vendor/console/src/utils.rs
vendored
Normal 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
54
vendor/console/src/wasm_term.rs
vendored
Normal 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) {}
|
451
vendor/console/src/windows_term/colors.rs
vendored
Normal file
451
vendor/console/src/windows_term/colors.rs
vendored
Normal 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
563
vendor/console/src/windows_term/mod.rs
vendored
Normal 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());
|
||||
}
|
||||
}
|
56
vendor/console/tests/data/sample_zellij_session.log
vendored
Executable file
56
vendor/console/tests/data/sample_zellij_session.log
vendored
Executable file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user