From c23a457e72abe608715ac76f076f47dc42af07a5 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 20:31:44 +0200 Subject: Merging upstream version 1.74.1+dfsg1. Signed-off-by: Daniel Baumann --- vendor/console/.cargo-checksum.json | 1 + vendor/console/CHANGELOG.md | 103 +++ vendor/console/Cargo.lock | 272 ++++++ vendor/console/Cargo.toml | 72 ++ vendor/console/LICENSE | 22 + vendor/console/Makefile | 39 + vendor/console/README.md | 76 ++ vendor/console/benches/ansi_parser.rs | 27 + vendor/console/examples/colors.rs | 14 + vendor/console/examples/colors256.rs | 17 + vendor/console/examples/cursor_at.rs | 30 + vendor/console/examples/term.rs | 33 + vendor/console/scripts/wasmtime-wrapper.sh | 4 + vendor/console/src/ansi.rs | 438 ++++++++++ vendor/console/src/common_term.rs | 72 ++ vendor/console/src/kb.rs | 29 + vendor/console/src/lib.rs | 104 +++ vendor/console/src/term.rs | 632 ++++++++++++++ vendor/console/src/unix_term.rs | 362 ++++++++ vendor/console/src/utils.rs | 962 +++++++++++++++++++++ vendor/console/src/wasm_term.rs | 54 ++ vendor/console/src/windows_term/colors.rs | 451 ++++++++++ vendor/console/src/windows_term/mod.rs | 563 ++++++++++++ .../console/tests/data/sample_zellij_session.log | 56 ++ 24 files changed, 4433 insertions(+) create mode 100644 vendor/console/.cargo-checksum.json create mode 100644 vendor/console/CHANGELOG.md create mode 100644 vendor/console/Cargo.lock create mode 100644 vendor/console/Cargo.toml create mode 100644 vendor/console/LICENSE create mode 100644 vendor/console/Makefile create mode 100644 vendor/console/README.md create mode 100644 vendor/console/benches/ansi_parser.rs create mode 100644 vendor/console/examples/colors.rs create mode 100644 vendor/console/examples/colors256.rs create mode 100644 vendor/console/examples/cursor_at.rs create mode 100644 vendor/console/examples/term.rs create mode 100755 vendor/console/scripts/wasmtime-wrapper.sh create mode 100644 vendor/console/src/ansi.rs create mode 100644 vendor/console/src/common_term.rs create mode 100644 vendor/console/src/kb.rs create mode 100644 vendor/console/src/lib.rs create mode 100644 vendor/console/src/term.rs create mode 100644 vendor/console/src/unix_term.rs create mode 100644 vendor/console/src/utils.rs create mode 100644 vendor/console/src/wasm_term.rs create mode 100644 vendor/console/src/windows_term/colors.rs create mode 100644 vendor/console/src/windows_term/mod.rs create mode 100755 vendor/console/tests/data/sample_zellij_session.log (limited to 'vendor/console') diff --git a/vendor/console/.cargo-checksum.json b/vendor/console/.cargo-checksum.json new file mode 100644 index 000000000..ab1674641 --- /dev/null +++ b/vendor/console/.cargo-checksum.json @@ -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"} \ No newline at end of file diff --git a/vendor/console/CHANGELOG.md b/vendor/console/CHANGELOG.md new file mode 100644 index 000000000..1f13206f2 --- /dev/null +++ b/vendor/console/CHANGELOG.md @@ -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()` diff --git a/vendor/console/Cargo.lock b/vendor/console/Cargo.lock new file mode 100644 index 000000000..22334e609 --- /dev/null +++ b/vendor/console/Cargo.lock @@ -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" diff --git a/vendor/console/Cargo.toml b/vendor/console/Cargo.toml new file mode 100644 index 000000000..85849e16a --- /dev/null +++ b/vendor/console/Cargo.toml @@ -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 "] +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", +] diff --git a/vendor/console/LICENSE b/vendor/console/LICENSE new file mode 100644 index 000000000..dc9a85c1e --- /dev/null +++ b/vendor/console/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2017 Armin Ronacher + +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. + diff --git a/vendor/console/Makefile b/vendor/console/Makefile new file mode 100644 index 000000000..ccab99dd7 --- /dev/null +++ b/vendor/console/Makefile @@ -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 diff --git a/vendor/console/README.md b/vendor/console/README.md new file mode 100644 index 000000000..9a046d859 --- /dev/null +++ b/vendor/console/README.md @@ -0,0 +1,76 @@ +# `console` + +[![Build Status](https://github.com/console-rs/console/workflows/CI/badge.svg?branch=master)](https://github.com/console-rs/console/actions?query=workflow%3ACI) +[![Crates.io](https://img.shields.io/crates/d/console.svg)](https://crates.io/crates/console) +[![License](https://img.shields.io/github/license/console-rs/console)](https://github.com/console-rs/console/blob/master/LICENSE) +[![rustc 1.48.0](https://img.shields.io/badge/rust-1.48%2B-orange.svg)](https://img.shields.io/badge/rust-1.48%2B-orange.svg) +[![Documentation](https://docs.rs/console/badge.svg)](https://docs.rs/console) + +**console** is a library for Rust that provides access to various terminal +features so you can build nicer looking command line interfaces. It +comes with various tools and utilities for working with Terminals and +formatting text. + +Best paired with other libraries in the family: + +* [dialoguer](https://docs.rs/dialoguer) +* [indicatif](https://docs.rs/indicatif) + +## Terminal Access + +The terminal is abstracted through the `console::Term` type. It can +either directly provide access to the connected terminal or by buffering +up commands. A buffered terminal will however not be completely buffered +on windows where cursor movements are currently directly passed through. + +Example usage: + +```rust +use std::thread; +use std::time::Duration; + +use console::Term; + +let term = Term::stdout(); +term.write_line("Hello World!")?; +thread::sleep(Duration::from_millis(2000)); +term.clear_line()?; +``` + +## Colors and Styles + +`console` automaticaly detects when to use colors based on the tty flag. It also +provides higher level wrappers for styling text and other things that can be +displayed with the `style` function and utility types. + +Example usage: + +```rust +use console::style; + +println!("This is {} neat", style("quite").cyan()); +``` + +You can also store styles and apply them to text later: + +```rust +use console::Style; + +let cyan = Style::new().cyan(); +println!("This is {} neat", cyan.apply_to("quite")); +``` + +## Working with ANSI Codes + +The crate provides the function `strip_ansi_codes` to remove ANSI codes +from a string as well as `measure_text_width` to calculate the width of a +string as it would be displayed by the terminal. Both of those together +are useful for more complex formatting. + +## Unicode Width Support + +By default this crate depends on the `unicode-width` crate to calculate +the width of terminal characters. If you do not need this you can disable +the `unicode-width` feature which will cut down on dependencies. + +License: MIT diff --git a/vendor/console/benches/ansi_parser.rs b/vendor/console/benches/ansi_parser.rs new file mode 100644 index 000000000..2d74c429d --- /dev/null +++ b/vendor/console/benches/ansi_parser.rs @@ -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); diff --git a/vendor/console/examples/colors.rs b/vendor/console/examples/colors.rs new file mode 100644 index 000000000..69c51fd18 --- /dev/null +++ b/vendor/console/examples/colors.rs @@ -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() + ); +} diff --git a/vendor/console/examples/colors256.rs b/vendor/console/examples/colors256.rs new file mode 100644 index 000000000..60acb88ec --- /dev/null +++ b/vendor/console/examples/colors256.rs @@ -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!(); + } + } +} diff --git a/vendor/console/examples/cursor_at.rs b/vendor/console/examples/cursor_at.rs new file mode 100644 index 000000000..9a1bb3d51 --- /dev/null +++ b/vendor/console/examples/cursor_at.rs @@ -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(); +} diff --git a/vendor/console/examples/term.rs b/vendor/console/examples/term.rs new file mode 100644 index 000000000..a0637f739 --- /dev/null +++ b/vendor/console/examples/term.rs @@ -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(); +} diff --git a/vendor/console/scripts/wasmtime-wrapper.sh b/vendor/console/scripts/wasmtime-wrapper.sh new file mode 100755 index 000000000..75d8ca5b7 --- /dev/null +++ b/vendor/console/scripts/wasmtime-wrapper.sh @@ -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)" -- "$@" diff --git a/vendor/console/src/ansi.rs b/vendor/console/src/ansi.rs new file mode 100644 index 000000000..3a3c96c37 --- /dev/null +++ b/vendor/console/src/ansi.rs @@ -0,0 +1,438 @@ +use std::{ + borrow::Cow, + iter::{FusedIterator, Peekable}, + str::CharIndices, +}; + +#[derive(Debug, Clone, Copy)] +enum State { + Start, + S1, + S2, + S3, + S4, + S5, + S6, + S7, + S8, + S9, + S10, + S11, + Trap, +} + +impl Default for State { + fn default() -> Self { + Self::Start + } +} + +impl State { + fn is_final(&self) -> bool { + #[allow(clippy::match_like_matches_macro)] + match self { + Self::S3 | Self::S5 | Self::S6 | Self::S7 | Self::S8 | Self::S9 | Self::S11 => true, + _ => false, + } + } + + fn is_trapped(&self) -> bool { + #[allow(clippy::match_like_matches_macro)] + match self { + Self::Trap => true, + _ => false, + } + } + + fn transition(&mut self, c: char) { + *self = match c { + '\u{1b}' | '\u{9b}' => match self { + Self::Start => Self::S1, + _ => Self::Trap, + }, + '(' | ')' => match self { + Self::S1 => Self::S2, + Self::S2 | Self::S4 => Self::S4, + _ => Self::Trap, + }, + ';' => match self { + Self::S1 | Self::S2 | Self::S4 => Self::S4, + Self::S5 | Self::S6 | Self::S7 | Self::S8 | Self::S10 => Self::S10, + _ => Self::Trap, + }, + + '[' | '#' | '?' => match self { + Self::S1 | Self::S2 | Self::S4 => Self::S4, + _ => Self::Trap, + }, + '0'..='2' => match self { + Self::S1 | Self::S4 => Self::S5, + Self::S2 => Self::S3, + Self::S5 => Self::S6, + Self::S6 => Self::S7, + Self::S7 => Self::S8, + Self::S8 => Self::S9, + Self::S10 => Self::S5, + _ => Self::Trap, + }, + '3'..='9' => match self { + Self::S1 | Self::S4 => Self::S5, + Self::S2 => Self::S5, + Self::S5 => Self::S6, + Self::S6 => Self::S7, + Self::S7 => Self::S8, + Self::S8 => Self::S9, + Self::S10 => Self::S5, + _ => Self::Trap, + }, + 'A'..='P' | 'R' | 'Z' | 'c' | 'f'..='n' | 'q' | 'r' | 'y' | '=' | '>' | '<' => { + match self { + Self::S1 + | Self::S2 + | Self::S4 + | Self::S5 + | Self::S6 + | Self::S7 + | Self::S8 + | Self::S10 => Self::S11, + _ => Self::Trap, + } + } + _ => Self::Trap, + }; + } +} + +#[derive(Debug)] +struct Matches<'a> { + s: &'a str, + it: Peekable>, +} + +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 { + 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) -> 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 { + 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> 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) { + if len == 0 { + if let Ok(s) = std::str::from_utf8(chunk) { + let old_matches: Vec<_> = STRIP_ANSI_RE.find_iter(s).collect(); + let new_matches: Vec<_> = Matches::new(s).collect(); + assert_eq!(old_matches, new_matches); + } + + return; + } + + for b in POSSIBLE_BYTES { + chunk.push(*b); + _check_all_strings_of_len(len - 1, chunk); + chunk.pop(); + } + } + + for str_len in 0..=6 { + check_all_strings_of_len(str_len); + } + } + + #[test] + fn complex_data() { + let s = std::fs::read_to_string( + std::path::Path::new("tests") + .join("data") + .join("sample_zellij_session.log"), + ) + .unwrap(); + + let old_matches: Vec<_> = STRIP_ANSI_RE.find_iter(&s).collect(); + let new_matches: Vec<_> = Matches::new(&s).collect(); + assert_eq!(old_matches, new_matches); + } + + #[test] + fn state_machine() { + let ansi_code = "\x1b)B"; + let mut state = State::default(); + assert!(!state.is_final()); + + for c in ansi_code.chars() { + state.transition(c); + } + assert!(state.is_final()); + + state.transition('A'); + assert!(state.is_trapped()); + } + + #[test] + fn back_to_back_entry_char() { + let s = "\x1b\x1bf"; + let matches: Vec<_> = Matches::new(s).map(|m| m.as_str()).collect(); + assert_eq!(&["\x1bf"], matches.as_slice()); + } + + #[test] + fn early_paren_can_use_many_chars() { + let s = "\x1b(C"; + let matches: Vec<_> = Matches::new(s).map(|m| m.as_str()).collect(); + assert_eq!(&[s], matches.as_slice()); + } + + #[test] + fn long_run_of_digits() { + let s = "\u{1b}00000"; + let matches: Vec<_> = Matches::new(s).map(|m| m.as_str()).collect(); + assert_eq!(&[s], matches.as_slice()); + } + + #[test] + fn test_ansi_iter_re_vt100() { + let s = "\x1b(0lpq\x1b)Benglish"; + let mut iter = AnsiCodeIterator::new(s); + assert_eq!(iter.next(), Some(("\x1b(0", true))); + assert_eq!(iter.next(), Some(("lpq", false))); + assert_eq!(iter.next(), Some(("\x1b)B", true))); + assert_eq!(iter.next(), Some(("english", false))); + } + + #[test] + fn test_ansi_iter_re() { + use crate::style; + let s = format!("Hello {}!", style("World").red().force_styling(true)); + let mut iter = AnsiCodeIterator::new(&s); + assert_eq!(iter.next(), Some(("Hello ", false))); + assert_eq!(iter.current_slice(), "Hello "); + assert_eq!(iter.rest_slice(), "\x1b[31mWorld\x1b[0m!"); + assert_eq!(iter.next(), Some(("\x1b[31m", true))); + assert_eq!(iter.current_slice(), "Hello \x1b[31m"); + assert_eq!(iter.rest_slice(), "World\x1b[0m!"); + assert_eq!(iter.next(), Some(("World", false))); + assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld"); + assert_eq!(iter.rest_slice(), "\x1b[0m!"); + assert_eq!(iter.next(), Some(("\x1b[0m", true))); + assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m"); + assert_eq!(iter.rest_slice(), "!"); + assert_eq!(iter.next(), Some(("!", false))); + assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m!"); + assert_eq!(iter.rest_slice(), ""); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_ansi_iter_re_on_multi() { + use crate::style; + let s = format!("{}", style("a").red().bold().force_styling(true)); + let mut iter = AnsiCodeIterator::new(&s); + assert_eq!(iter.next(), Some(("\x1b[31m", true))); + assert_eq!(iter.current_slice(), "\x1b[31m"); + assert_eq!(iter.rest_slice(), "\x1b[1ma\x1b[0m"); + assert_eq!(iter.next(), Some(("\x1b[1m", true))); + assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1m"); + assert_eq!(iter.rest_slice(), "a\x1b[0m"); + assert_eq!(iter.next(), Some(("a", false))); + assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma"); + assert_eq!(iter.rest_slice(), "\x1b[0m"); + assert_eq!(iter.next(), Some(("\x1b[0m", true))); + assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma\x1b[0m"); + assert_eq!(iter.rest_slice(), ""); + assert_eq!(iter.next(), None); + } +} diff --git a/vendor/console/src/common_term.rs b/vendor/console/src/common_term.rs new file mode 100644 index 000000000..020660a5b --- /dev/null +++ b/vendor/console/src/common_term.rs @@ -0,0 +1,72 @@ +use std::io; + +use crate::term::Term; + +pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> { + if n > 0 { + out.write_str(&format!("\x1b[{}B", n)) + } else { + Ok(()) + } +} + +pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> { + if n > 0 { + out.write_str(&format!("\x1b[{}A", n)) + } else { + Ok(()) + } +} +pub fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> { + if n > 0 { + out.write_str(&format!("\x1b[{}D", n)) + } else { + Ok(()) + } +} + +pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> { + if n > 0 { + out.write_str(&format!("\x1b[{}C", n)) + } else { + Ok(()) + } +} + +#[inline] +pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> { + out.write_str(&format!("\x1B[{};{}H", y + 1, x + 1)) +} + +pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> { + if n > 0 { + out.write_str(&format!("\x1b[{}D\x1b[0K", n)) + } else { + Ok(()) + } +} + +#[inline] +pub fn clear_line(out: &Term) -> io::Result<()> { + out.write_str("\r\x1b[2K") +} + +#[inline] +pub fn clear_screen(out: &Term) -> io::Result<()> { + out.write_str("\r\x1b[2J\r\x1b[H") +} + +#[inline] +pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> { + out.write_str("\r\x1b[0J") +} + +#[inline] +pub fn show_cursor(out: &Term) -> io::Result<()> { + out.write_str("\x1b[?25h") +} + +#[inline] +pub fn hide_cursor(out: &Term) -> io::Result<()> { + out.write_str("\x1b[?25l") +} diff --git a/vendor/console/src/kb.rs b/vendor/console/src/kb.rs new file mode 100644 index 000000000..5258c1350 --- /dev/null +++ b/vendor/console/src/kb.rs @@ -0,0 +1,29 @@ +/// Key mapping +/// +/// This is an incomplete mapping of keys that are supported for reading +/// from the keyboard. +#[non_exhaustive] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum Key { + Unknown, + /// Unrecognized sequence containing Esc and a list of chars + UnknownEscSeq(Vec), + ArrowLeft, + ArrowRight, + ArrowUp, + ArrowDown, + Enter, + Escape, + Backspace, + Home, + End, + Tab, + BackTab, + Alt, + Del, + Shift, + Insert, + PageUp, + PageDown, + Char(char), +} diff --git a/vendor/console/src/lib.rs b/vendor/console/src/lib.rs new file mode 100644 index 000000000..1b18afc03 --- /dev/null +++ b/vendor/console/src/lib.rs @@ -0,0 +1,104 @@ +//! console is a library for Rust that provides access to various terminal +//! features so you can build nicer looking command line interfaces. It +//! comes with various tools and utilities for working with Terminals and +//! formatting text. +//! +//! Best paired with other libraries in the family: +//! +//! * [dialoguer](https://docs.rs/dialoguer) +//! * [indicatif](https://docs.rs/indicatif) +//! +//! # Terminal Access +//! +//! The terminal is abstracted through the `console::Term` type. It can +//! either directly provide access to the connected terminal or by buffering +//! up commands. A buffered terminal will however not be completely buffered +//! on windows where cursor movements are currently directly passed through. +//! +//! Example usage: +//! +//! ``` +//! # fn test() -> Result<(), Box> { +//! use std::thread; +//! use std::time::Duration; +//! +//! use console::Term; +//! +//! let term = Term::stdout(); +//! term.write_line("Hello World!")?; +//! thread::sleep(Duration::from_millis(2000)); +//! term.clear_line()?; +//! # Ok(()) } test().unwrap(); +//! ``` +//! +//! # Colors and Styles +//! +//! `console` automaticaly detects when to use colors based on the tty flag. It also +//! provides higher level wrappers for styling text and other things that can be +//! displayed with the `style` function and utility types. +//! +//! Example usage: +//! +//! ``` +//! use console::style; +//! +//! println!("This is {} neat", style("quite").cyan()); +//! ``` +//! +//! You can also store styles and apply them to text later: +//! +//! ``` +//! use console::Style; +//! +//! let cyan = Style::new().cyan(); +//! println!("This is {} neat", cyan.apply_to("quite")); +//! ``` +//! +//! # Working with ANSI Codes +//! +//! The crate provids the function `strip_ansi_codes` to remove ANSI codes +//! from a string as well as `measure_text_width` to calculate the width of a +//! string as it would be displayed by the terminal. Both of those together +//! are useful for more complex formatting. +//! +//! # Unicode Width Support +//! +//! By default this crate depends on the `unicode-width` crate to calculate +//! the width of terminal characters. If you do not need this you can disable +//! the `unicode-width` feature which will cut down on dependencies. +//! +//! # Features +//! +//! By default all features are enabled. The following features exist: +//! +//! * `unicode-width`: adds support for unicode width calculations +//! * `ansi-parsing`: adds support for parsing ansi codes (this adds support +//! for stripping and taking ansi escape codes into account for length +//! calculations). + +pub use crate::kb::Key; +pub use crate::term::{ + user_attended, user_attended_stderr, Term, TermFamily, TermFeatures, TermTarget, +}; +pub use crate::utils::{ + colors_enabled, colors_enabled_stderr, measure_text_width, pad_str, pad_str_with, + set_colors_enabled, set_colors_enabled_stderr, style, truncate_str, Alignment, Attribute, + Color, Emoji, Style, StyledObject, +}; + +#[cfg(feature = "ansi-parsing")] +pub use crate::ansi::{strip_ansi_codes, AnsiCodeIterator}; + +mod common_term; +mod kb; +mod term; +#[cfg(unix)] +mod unix_term; +mod utils; +#[cfg(target_arch = "wasm32")] +mod wasm_term; +#[cfg(windows)] +mod windows_term; + +#[cfg(feature = "ansi-parsing")] +mod ansi; diff --git a/vendor/console/src/term.rs b/vendor/console/src/term.rs new file mode 100644 index 000000000..0a4025850 --- /dev/null +++ b/vendor/console/src/term.rs @@ -0,0 +1,632 @@ +use std::fmt::{Debug, Display}; +use std::io::{self, Read, Write}; +use std::sync::{Arc, Mutex}; + +#[cfg(unix)] +use std::os::unix::io::{AsRawFd, RawFd}; +#[cfg(windows)] +use std::os::windows::io::{AsRawHandle, RawHandle}; + +use crate::{kb::Key, utils::Style}; + +#[cfg(unix)] +trait TermWrite: Write + Debug + AsRawFd + Send {} +#[cfg(unix)] +impl TermWrite for T {} + +#[cfg(unix)] +trait TermRead: Read + Debug + AsRawFd + Send {} +#[cfg(unix)] +impl TermRead for T {} + +#[cfg(unix)] +#[derive(Debug, Clone)] +pub struct ReadWritePair { + #[allow(unused)] + read: Arc>, + write: Arc>, + 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>>, +} + +/// 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, + 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(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(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 { + 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 { + 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 { + 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 { + if !self.is_tty { + return Ok("".into()); + } + self.write_str(initial)?; + + let mut chars: Vec = 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::()) + } + + /// 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 { + 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(&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 { + 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 { + 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 { + io::stdin().read(buf) + } +} + +impl<'a> Read for &'a Term { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + io::stdin().read(buf) + } +} + +#[cfg(unix)] +pub use crate::unix_term::*; +#[cfg(target_arch = "wasm32")] +pub use crate::wasm_term::*; +#[cfg(windows)] +pub use crate::windows_term::*; diff --git a/vendor/console/src/unix_term.rs b/vendor/console/src/unix_term.rs new file mode 100644 index 000000000..8e1e59259 --- /dev/null +++ b/vendor/console/src/unix_term.rs @@ -0,0 +1,362 @@ +use std::env; +use std::fmt::Display; +use std::fs; +use std::io; +use std::io::{BufRead, BufReader}; +use std::mem; +use std::os::unix::io::AsRawFd; +use std::ptr; +use std::str; + +use crate::kb::Key; +use crate::term::Term; + +pub use crate::common_term::*; + +pub const DEFAULT_WIDTH: u16 = 80; + +#[inline] +pub fn is_a_terminal(out: &Term) -> bool { + unsafe { libc::isatty(out.as_raw_fd()) != 0 } +} + +pub fn is_a_color_terminal(out: &Term) -> bool { + if !is_a_terminal(out) { + return false; + } + + if env::var("NO_COLOR").is_ok() { + return false; + } + + match env::var("TERM") { + Ok(term) => term != "dumb", + Err(_) => false, + } +} + +pub fn c_result 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 { + 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 { + 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 { + 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 { + // 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> { + // 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 { + 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 { + 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 { + 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 = 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(title: T) { + print!("\x1b]0;{}\x07", title); +} diff --git a/vendor/console/src/utils.rs b/vendor/console/src/utils.rs new file mode 100644 index 000000000..9e6b942f7 --- /dev/null +++ b/vendor/console/src/utils.rs @@ -0,0 +1,962 @@ +use std::borrow::Cow; +use std::collections::BTreeSet; +use std::env; +use std::fmt; +use std::sync::atomic::{AtomicBool, Ordering}; + +use lazy_static::lazy_static; + +use crate::term::{wants_emoji, Term}; + +#[cfg(feature = "ansi-parsing")] +use crate::ansi::{strip_ansi_codes, AnsiCodeIterator}; + +#[cfg(not(feature = "ansi-parsing"))] +fn strip_ansi_codes(s: &str) -> &str { + s +} + +fn default_colors_enabled(out: &Term) -> bool { + (out.features().colors_supported() + && &env::var("CLICOLOR").unwrap_or_else(|_| "1".into()) != "0") + || &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0" +} + +lazy_static! { + static ref STDOUT_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stdout())); + static ref STDERR_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stderr())); +} + +/// Returns `true` if colors should be enabled for stdout. +/// +/// This honors the [clicolors spec](http://bixense.com/clicolors/). +/// +/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped. +/// * `CLICOLOR == 0`: Don't output ANSI color escape codes. +/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what. +#[inline] +pub fn colors_enabled() -> bool { + STDOUT_COLORS.load(Ordering::Relaxed) +} + +/// Forces colorization on or off for stdout. +/// +/// This overrides the default for the current process and changes the return value of the +/// `colors_enabled` function. +#[inline] +pub fn set_colors_enabled(val: bool) { + STDOUT_COLORS.store(val, Ordering::Relaxed) +} + +/// Returns `true` if colors should be enabled for stderr. +/// +/// This honors the [clicolors spec](http://bixense.com/clicolors/). +/// +/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped. +/// * `CLICOLOR == 0`: Don't output ANSI color escape codes. +/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what. +#[inline] +pub fn colors_enabled_stderr() -> bool { + STDERR_COLORS.load(Ordering::Relaxed) +} + +/// Forces colorization on or off for stderr. +/// +/// This overrides the default for the current process and changes the return value of the +/// `colors_enabled` function. +#[inline] +pub fn set_colors_enabled_stderr(val: bool) { + STDERR_COLORS.store(val, Ordering::Relaxed) +} + +/// Measure the width of a string in terminal characters. +pub fn measure_text_width(s: &str) -> usize { + str_width(&strip_ansi_codes(s)) +} + +/// A terminal color. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Color { + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + White, + Color256(u8), +} + +impl Color { + #[inline] + fn ansi_num(self) -> usize { + match self { + Color::Black => 0, + Color::Red => 1, + Color::Green => 2, + Color::Yellow => 3, + Color::Blue => 4, + Color::Magenta => 5, + Color::Cyan => 6, + Color::White => 7, + Color::Color256(x) => x as usize, + } + } + + #[inline] + fn is_color256(self) -> bool { + #[allow(clippy::match_like_matches_macro)] + match self { + Color::Color256(_) => true, + _ => false, + } + } +} + +/// A terminal style attribute. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)] +pub enum Attribute { + Bold, + Dim, + Italic, + Underlined, + Blink, + BlinkFast, + Reverse, + Hidden, + StrikeThrough, +} + +impl Attribute { + #[inline] + fn ansi_num(self) -> usize { + match self { + Attribute::Bold => 1, + Attribute::Dim => 2, + Attribute::Italic => 3, + Attribute::Underlined => 4, + Attribute::Blink => 5, + Attribute::BlinkFast => 6, + Attribute::Reverse => 7, + Attribute::Hidden => 8, + Attribute::StrikeThrough => 9, + } + } +} + +/// Defines the alignment for padding operations. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Alignment { + Left, + Center, + Right, +} + +/// A stored style that can be applied. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Style { + fg: Option, + bg: Option, + fg_bright: bool, + bg_bright: bool, + attrs: BTreeSet, + force: Option, + 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::() { + rv.on_color256(n) + } else { + continue; + } + } + c => { + if let Ok(n) = c.parse::() { + rv.color256(n) + } else { + continue; + } + } + }; + } + rv + } + + /// Apply the style to something that can be displayed. + pub fn apply_to(&self, val: D) -> StyledObject { + 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(val: D) -> StyledObject { + Style::new().apply_to(val) +} + +/// A formatting wrapper that can be styled for a terminal. +#[derive(Clone)] +pub struct StyledObject { + style: Style, + val: D, +} + +impl StyledObject { + /// Forces styling on or off. + /// + /// This overrides the automatic detection. + #[inline] + pub fn force_styling(mut self, value: bool) -> StyledObject { + 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 { + 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 { + self.style = self.style.for_stdout(); + self + } + + /// Sets a foreground color. + #[inline] + pub fn fg(mut self, color: Color) -> StyledObject { + self.style = self.style.fg(color); + self + } + + /// Sets a background color. + #[inline] + pub fn bg(mut self, color: Color) -> StyledObject { + self.style = self.style.bg(color); + self + } + + /// Adds a attr. + #[inline] + pub fn attr(mut self, attr: Attribute) -> StyledObject { + self.style = self.style.attr(attr); + self + } + + #[inline] + pub fn black(self) -> StyledObject { + self.fg(Color::Black) + } + #[inline] + pub fn red(self) -> StyledObject { + self.fg(Color::Red) + } + #[inline] + pub fn green(self) -> StyledObject { + self.fg(Color::Green) + } + #[inline] + pub fn yellow(self) -> StyledObject { + self.fg(Color::Yellow) + } + #[inline] + pub fn blue(self) -> StyledObject { + self.fg(Color::Blue) + } + #[inline] + pub fn magenta(self) -> StyledObject { + self.fg(Color::Magenta) + } + #[inline] + pub fn cyan(self) -> StyledObject { + self.fg(Color::Cyan) + } + #[inline] + pub fn white(self) -> StyledObject { + self.fg(Color::White) + } + #[inline] + pub fn color256(self, color: u8) -> StyledObject { + self.fg(Color::Color256(color)) + } + + #[inline] + pub fn bright(mut self) -> StyledObject { + self.style = self.style.bright(); + self + } + + #[inline] + pub fn on_black(self) -> StyledObject { + self.bg(Color::Black) + } + #[inline] + pub fn on_red(self) -> StyledObject { + self.bg(Color::Red) + } + #[inline] + pub fn on_green(self) -> StyledObject { + self.bg(Color::Green) + } + #[inline] + pub fn on_yellow(self) -> StyledObject { + self.bg(Color::Yellow) + } + #[inline] + pub fn on_blue(self) -> StyledObject { + self.bg(Color::Blue) + } + #[inline] + pub fn on_magenta(self) -> StyledObject { + self.bg(Color::Magenta) + } + #[inline] + pub fn on_cyan(self) -> StyledObject { + self.bg(Color::Cyan) + } + #[inline] + pub fn on_white(self) -> StyledObject { + self.bg(Color::White) + } + #[inline] + pub fn on_color256(self, color: u8) -> StyledObject { + self.bg(Color::Color256(color)) + } + + #[inline] + pub fn on_bright(mut self) -> StyledObject { + self.style = self.style.on_bright(); + self + } + + #[inline] + pub fn bold(self) -> StyledObject { + self.attr(Attribute::Bold) + } + #[inline] + pub fn dim(self) -> StyledObject { + self.attr(Attribute::Dim) + } + #[inline] + pub fn italic(self) -> StyledObject { + self.attr(Attribute::Italic) + } + #[inline] + pub fn underlined(self) -> StyledObject { + self.attr(Attribute::Underlined) + } + #[inline] + pub fn blink(self) -> StyledObject { + self.attr(Attribute::Blink) + } + #[inline] + pub fn blink_fast(self) -> StyledObject { + self.attr(Attribute::BlinkFast) + } + #[inline] + pub fn reverse(self) -> StyledObject { + self.attr(Attribute::Reverse) + } + #[inline] + pub fn hidden(self) -> StyledObject { + self.attr(Attribute::Hidden) + } + #[inline] + pub fn strikethrough(self) -> StyledObject { + self.attr(Attribute::StrikeThrough) + } +} + +macro_rules! impl_fmt { + ($name:ident) => { + impl fmt::$name for StyledObject { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut reset = false; + if self + .style + .force + .unwrap_or_else(|| match self.style.for_stderr { + true => colors_enabled_stderr(), + false => colors_enabled(), + }) + { + if let Some(fg) = self.style.fg { + if fg.is_color256() { + write!(f, "\x1b[38;5;{}m", fg.ansi_num())?; + } else if self.style.fg_bright { + write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?; + } else { + write!(f, "\x1b[{}m", fg.ansi_num() + 30)?; + } + reset = true; + } + if let Some(bg) = self.style.bg { + if bg.is_color256() { + write!(f, "\x1b[48;5;{}m", bg.ansi_num())?; + } else if self.style.bg_bright { + write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?; + } else { + write!(f, "\x1b[{}m", bg.ansi_num() + 40)?; + } + reset = true; + } + for attr in &self.style.attrs { + write!(f, "\x1b[{}m", attr.ansi_num())?; + reset = true; + } + } + fmt::$name::fmt(&self.val, f)?; + if reset { + write!(f, "\x1b[0m")?; + } + Ok(()) + } + } + }; +} + +impl_fmt!(Binary); +impl_fmt!(Debug); +impl_fmt!(Display); +impl_fmt!(LowerExp); +impl_fmt!(LowerHex); +impl_fmt!(Octal); +impl_fmt!(Pointer); +impl_fmt!(UpperExp); +impl_fmt!(UpperHex); + +/// "Intelligent" emoji formatter. +/// +/// This struct intelligently wraps an emoji so that it is rendered +/// only on systems that want emojis and renders a fallback on others. +/// +/// Example: +/// +/// ```rust +/// use console::Emoji; +/// println!("[3/4] {}Downloading ...", Emoji("🚚 ", "")); +/// println!("[4/4] {} Done!", Emoji("✨", ":-)")); +/// ``` +#[derive(Copy, Clone)] +pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str); + +impl<'a, 'b> Emoji<'a, 'b> { + pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> { + Emoji(emoji, fallback) + } +} + +impl<'a, 'b> fmt::Display for Emoji<'a, 'b> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if wants_emoji() { + write!(f, "{}", self.0) + } else { + write!(f, "{}", self.1) + } + } +} + +fn str_width(s: &str) -> usize { + #[cfg(feature = "unicode-width")] + { + use unicode_width::UnicodeWidthStr; + s.width() + } + #[cfg(not(feature = "unicode-width"))] + { + s.chars().count() + } +} + +#[cfg(feature = "ansi-parsing")] +fn char_width(c: char) -> usize { + #[cfg(feature = "unicode-width")] + { + use unicode_width::UnicodeWidthChar; + c.width().unwrap_or(0) + } + #[cfg(not(feature = "unicode-width"))] + { + let _c = c; + 1 + } +} + +/// Truncates a string to a certain number of characters. +/// +/// This ensures that escape codes are not screwed up in the process. +/// If the maximum length is hit the string will be truncated but +/// escapes code will still be honored. If truncation takes place +/// the tail string will be appended. +pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> { + #[cfg(feature = "ansi-parsing")] + { + use std::cmp::Ordering; + let mut iter = AnsiCodeIterator::new(s); + let mut length = 0; + let mut rv = None; + + while let Some(item) = iter.next() { + match item { + (s, false) => { + if rv.is_none() { + if str_width(s) + length > width - str_width(tail) { + let ts = iter.current_slice(); + + let mut s_byte = 0; + let mut s_width = 0; + let rest_width = width - str_width(tail) - length; + for c in s.chars() { + s_byte += c.len_utf8(); + s_width += char_width(c); + match s_width.cmp(&rest_width) { + Ordering::Equal => break, + Ordering::Greater => { + s_byte -= c.len_utf8(); + break; + } + Ordering::Less => continue, + } + } + + let idx = ts.len() - s.len() + s_byte; + let mut buf = ts[..idx].to_string(); + buf.push_str(tail); + rv = Some(buf); + } + length += str_width(s); + } + } + (s, true) => { + if rv.is_some() { + rv.as_mut().unwrap().push_str(s); + } + } + } + } + + if let Some(buf) = rv { + Cow::Owned(buf) + } else { + Cow::Borrowed(s) + } + } + + #[cfg(not(feature = "ansi-parsing"))] + { + if s.len() <= width - tail.len() { + Cow::Borrowed(s) + } else { + Cow::Owned(format!( + "{}{}", + s.get(..width - tail.len()).unwrap_or_default(), + tail + )) + } + } +} + +/// Pads a string to fill a certain number of characters. +/// +/// This will honor ansi codes correctly and allows you to align a string +/// on the left, right or centered. Additionally truncation can be enabled +/// by setting `truncate` to a string that should be used as a truncation +/// marker. +pub fn pad_str<'a>( + s: &'a str, + width: usize, + align: Alignment, + truncate: Option<&str>, +) -> Cow<'a, str> { + pad_str_with(s, width, align, truncate, ' ') +} +/// Pads a string with specific padding to fill a certain number of characters. +/// +/// This will honor ansi codes correctly and allows you to align a string +/// on the left, right or centered. Additionally truncation can be enabled +/// by setting `truncate` to a string that should be used as a truncation +/// marker. +pub fn pad_str_with<'a>( + s: &'a str, + width: usize, + align: Alignment, + truncate: Option<&str>, + pad: char, +) -> Cow<'a, str> { + let cols = measure_text_width(s); + + if cols >= width { + return match truncate { + None => Cow::Borrowed(s), + Some(tail) => truncate_str(s, width, tail), + }; + } + + let diff = width - cols; + + let (left_pad, right_pad) = match align { + Alignment::Left => (0, diff), + Alignment::Right => (diff, 0), + Alignment::Center => (diff / 2, diff - diff / 2), + }; + + let mut rv = String::new(); + for _ in 0..left_pad { + rv.push(pad); + } + rv.push_str(s); + for _ in 0..right_pad { + rv.push(pad); + } + Cow::Owned(rv) +} + +#[test] +fn test_text_width() { + let s = style("foo") + .red() + .on_black() + .bold() + .force_styling(true) + .to_string(); + assert_eq!( + measure_text_width(&s), + if cfg!(feature = "ansi-parsing") { + 3 + } else if cfg!(feature = "unicode-width") { + 17 + } else { + 21 + } + ); +} + +#[test] +#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))] +fn test_truncate_str() { + let s = format!("foo {}", style("bar").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 5, ""), + &format!("foo {}", style("b").red().force_styling(true)) + ); + let s = format!("foo {}", style("bar").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 5, "!"), + &format!("foo {}", style("!").red().force_styling(true)) + ); + let s = format!("foo {} baz", style("bar").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 10, "..."), + &format!("foo {}...", style("bar").red().force_styling(true)) + ); + let s = format!("foo {}", style("バー").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 5, ""), + &format!("foo {}", style("").red().force_styling(true)) + ); + let s = format!("foo {}", style("バー").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 6, ""), + &format!("foo {}", style("バ").red().force_styling(true)) + ); +} + +#[test] +fn test_truncate_str_no_ansi() { + assert_eq!(&truncate_str("foo bar", 5, ""), "foo b"); + assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !"); + assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar..."); +} + +#[test] +fn test_pad_str() { + assert_eq!(pad_str("foo", 7, Alignment::Center, None), " foo "); + assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo "); + assert_eq!(pad_str("foo", 7, Alignment::Right, None), " foo"); + assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo"); + assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar"); + assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo"); + assert_eq!( + pad_str("foobarbaz", 6, Alignment::Left, Some("...")), + "foo..." + ); +} + +#[test] +fn test_pad_str_with() { + assert_eq!( + pad_str_with("foo", 7, Alignment::Center, None, '#'), + "##foo##" + ); + assert_eq!( + pad_str_with("foo", 7, Alignment::Left, None, '#'), + "foo####" + ); + assert_eq!( + pad_str_with("foo", 7, Alignment::Right, None, '#'), + "####foo" + ); + assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo"); + assert_eq!( + pad_str_with("foobar", 3, Alignment::Left, None, '#'), + "foobar" + ); + assert_eq!( + pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'), + "foo" + ); + assert_eq!( + pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'), + "foo..." + ); +} diff --git a/vendor/console/src/wasm_term.rs b/vendor/console/src/wasm_term.rs new file mode 100644 index 000000000..764bc3414 --- /dev/null +++ b/vendor/console/src/wasm_term.rs @@ -0,0 +1,54 @@ +use std::fmt::Display; +use std::io; + +use crate::kb::Key; +use crate::term::Term; + +pub use crate::common_term::*; + +pub const DEFAULT_WIDTH: u16 = 80; + +#[inline] +pub fn is_a_terminal(_out: &Term) -> bool { + #[cfg(target = "wasm32-wasi")] + { + unsafe { libc::isatty(out.as_raw_fd()) != 0 } + } + #[cfg(not(target = "wasm32-wasi"))] + { + false + } +} + +#[inline] +pub fn is_a_color_terminal(_out: &Term) -> bool { + // We currently never report color terminals. For discussion see + // the issue in the WASI repo: https://github.com/WebAssembly/WASI/issues/162 + false +} + +#[inline] +pub fn terminal_size(_out: &Term) -> Option<(u16, u16)> { + None +} + +pub fn read_secure() -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "unsupported operation", + )) +} + +pub fn read_single_key() -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "unsupported operation", + )) +} + +#[inline] +pub fn wants_emoji() -> bool { + false +} + +pub fn set_title(_title: T) {} diff --git a/vendor/console/src/windows_term/colors.rs b/vendor/console/src/windows_term/colors.rs new file mode 100644 index 000000000..dc8209ded --- /dev/null +++ b/vendor/console/src/windows_term/colors.rs @@ -0,0 +1,451 @@ +use std::io; +use std::mem; +use std::os::windows::io::AsRawHandle; +use std::str::Bytes; + +use windows_sys::Win32::Foundation::HANDLE; +use windows_sys::Win32::System::Console::{ + GetConsoleScreenBufferInfo, SetConsoleTextAttribute, CONSOLE_SCREEN_BUFFER_INFO, + FOREGROUND_BLUE as FG_BLUE, FOREGROUND_GREEN as FG_GREEN, FOREGROUND_INTENSITY as FG_INTENSITY, + FOREGROUND_RED as FG_RED, +}; + +use crate::Term; + +type WORD = u16; + +const FG_CYAN: WORD = FG_BLUE | FG_GREEN; +const FG_MAGENTA: WORD = FG_BLUE | FG_RED; +const FG_YELLOW: WORD = FG_GREEN | FG_RED; +const FG_WHITE: WORD = FG_BLUE | FG_GREEN | FG_RED; + +/// Query the given handle for information about the console's screen buffer. +/// +/// The given handle should represent a console. Otherwise, an error is +/// returned. +/// +/// This corresponds to calling [`GetConsoleScreenBufferInfo`]. +/// +/// [`GetConsoleScreenBufferInfo`]: https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo +pub fn screen_buffer_info(h: HANDLE) -> io::Result { + 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 { + 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 { + 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 { + 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 { + match byte { + b'3' => Some(Self::Foreground), + b'4' => Some(Self::Background), + _ => None, + } + } +} + +fn driver(parse: fn(Bytes<'_>) -> Option, part: &str) -> Option { + 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 { + 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 { + 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 { + let color = match b { + b'0' => Color::Black, + b'1' => Color::Red, + b'2' => Color::Green, + b'3' => Color::Yellow, + b'4' => Color::Blue, + b'5' => Color::Magenta, + b'6' => Color::Cyan, + b'7' => Color::White, + _ => return None, + }; + Some(color) +} + +fn parse_suffix(bytes: &mut Bytes<'_>) -> Option<()> { + if bytes.next()? == b'm' { + Some(()) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn color_parsing() { + let intense_color = "leading bytes \x1b[38;5;10m trailing bytes"; + let parsed = driver(parse_color, intense_color).unwrap(); + assert_eq!(parsed, (Intense::Yes, Color::Green, FgBg::Foreground)); + + let normal_color = "leading bytes \x1b[40m trailing bytes"; + let parsed = driver(parse_color, normal_color).unwrap(); + assert_eq!(parsed, (Intense::No, Color::Black, FgBg::Background)); + } + + #[test] + fn attr_parsing() { + let attr = "leading bytes \x1b[1m trailing bytes"; + let parsed = driver(parse_attr, attr).unwrap(); + assert_eq!(parsed, b'1'); + } +} diff --git a/vendor/console/src/windows_term/mod.rs b/vendor/console/src/windows_term/mod.rs new file mode 100644 index 000000000..c4ec193cf --- /dev/null +++ b/vendor/console/src/windows_term/mod.rs @@ -0,0 +1,563 @@ +use std::cmp; +use std::env; +use std::ffi::OsStr; +use std::fmt::Display; +use std::io; +use std::iter::once; +use std::mem; +use std::os::raw::c_void; +use std::os::windows::ffi::OsStrExt; +use std::os::windows::io::AsRawHandle; +use std::slice; +use std::{char, mem::MaybeUninit}; + +use encode_unicode::error::InvalidUtf16Tuple; +use encode_unicode::CharExt; +use windows_sys::Win32::Foundation::{CHAR, HANDLE, INVALID_HANDLE_VALUE, MAX_PATH}; +use windows_sys::Win32::Storage::FileSystem::{ + FileNameInfo, GetFileInformationByHandleEx, FILE_NAME_INFO, +}; +use windows_sys::Win32::System::Console::{ + FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetConsoleCursorInfo, GetConsoleMode, + GetConsoleScreenBufferInfo, GetNumberOfConsoleInputEvents, GetStdHandle, ReadConsoleInputW, + SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleMode, SetConsoleTitleW, + CONSOLE_CURSOR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, KEY_EVENT, + KEY_EVENT_RECORD, STD_ERROR_HANDLE, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, +}; +use windows_sys::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY; + +use crate::common_term; +use crate::kb::Key; +use crate::term::{Term, TermTarget}; + +#[cfg(feature = "windows-console-colors")] +mod colors; + +#[cfg(feature = "windows-console-colors")] +pub use self::colors::*; + +const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x4; +pub const DEFAULT_WIDTH: u16 = 79; + +pub fn as_handle(term: &Term) -> HANDLE { + // convert between windows_sys::Win32::Foundation::HANDLE and std::os::windows::raw::HANDLE + term.as_raw_handle() as HANDLE +} + +pub fn is_a_terminal(out: &Term) -> bool { + let (fd, others) = match out.target() { + TermTarget::Stdout => (STD_OUTPUT_HANDLE, [STD_INPUT_HANDLE, STD_ERROR_HANDLE]), + TermTarget::Stderr => (STD_ERROR_HANDLE, [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE]), + }; + + if unsafe { console_on_any(&[fd]) } { + // False positives aren't possible. If we got a console then + // we definitely have a tty on stdin. + return true; + } + + // At this point, we *could* have a false negative. We can determine that + // this is true negative if we can detect the presence of a console on + // any of the other streams. If another stream has a console, then we know + // we're in a Windows console and can therefore trust the negative. + if unsafe { console_on_any(&others) } { + return false; + } + + msys_tty_on(out) +} + +pub fn is_a_color_terminal(out: &Term) -> bool { + if !is_a_terminal(out) { + return false; + } + if msys_tty_on(out) { + return match env::var("TERM") { + Ok(term) => term != "dumb", + Err(_) => true, + }; + } + enable_ansi_on(out) +} + +fn enable_ansi_on(out: &Term) -> bool { + unsafe { + let handle = as_handle(out); + + let mut dw_mode = 0; + if GetConsoleMode(handle, &mut dw_mode) == 0 { + return false; + } + + dw_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if SetConsoleMode(handle, dw_mode) == 0 { + return false; + } + + true + } +} + +unsafe fn console_on_any(fds: &[STD_HANDLE]) -> bool { + for &fd in fds { + let mut out = 0; + let handle = GetStdHandle(fd); + if GetConsoleMode(handle, &mut out) != 0 { + return true; + } + } + false +} + +pub fn terminal_size(out: &Term) -> Option<(u16, u16)> { + use windows_sys::Win32::System::Console::SMALL_RECT; + + // convert between windows_sys::Win32::Foundation::HANDLE and std::os::windows::raw::HANDLE + let handle = out.as_raw_handle(); + let hand = handle as windows_sys::Win32::Foundation::HANDLE; + + if hand == INVALID_HANDLE_VALUE { + return None; + } + + let zc = COORD { X: 0, Y: 0 }; + let mut csbi = CONSOLE_SCREEN_BUFFER_INFO { + dwSize: zc, + dwCursorPosition: zc, + wAttributes: 0, + srWindow: SMALL_RECT { + Left: 0, + Top: 0, + Right: 0, + Bottom: 0, + }, + dwMaximumWindowSize: zc, + }; + if unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } == 0 { + return None; + } + + let rows = (csbi.srWindow.Bottom - csbi.srWindow.Top + 1) as u16; + let columns = (csbi.srWindow.Right - csbi.srWindow.Left + 1) as u16; + + Some((rows, columns)) +} + +pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_to(out, x, y); + } + if let Some((hand, _)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + SetConsoleCursorPosition( + hand, + COORD { + X: x as i16, + Y: y as i16, + }, + ); + } + } + Ok(()) +} + +pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_up(out, n); + } + + if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize - n)?; + } + Ok(()) +} + +pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_down(out, n); + } + + if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize + n)?; + } + Ok(()) +} + +pub fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_left(out, n); + } + + if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + move_cursor_to( + out, + csbi.dwCursorPosition.X as usize - n, + csbi.dwCursorPosition.Y as usize, + )?; + } + Ok(()) +} + +pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_right(out, n); + } + + if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + move_cursor_to( + out, + csbi.dwCursorPosition.X as usize + n, + csbi.dwCursorPosition.Y as usize, + )?; + } + Ok(()) +} + +pub fn clear_line(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::clear_line(out); + } + if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + let width = csbi.srWindow.Right - csbi.srWindow.Left; + let pos = COORD { + X: 0, + Y: csbi.dwCursorPosition.Y, + }; + let mut written = 0; + FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as u32, pos, &mut written); + FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, pos, &mut written); + SetConsoleCursorPosition(hand, pos); + } + } + Ok(()) +} + +pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::clear_chars(out, n); + } + if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + let width = cmp::min(csbi.dwCursorPosition.X, n as i16); + let pos = COORD { + X: csbi.dwCursorPosition.X - width, + Y: csbi.dwCursorPosition.Y, + }; + let mut written = 0; + FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as u32, pos, &mut written); + FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, pos, &mut written); + SetConsoleCursorPosition(hand, pos); + } + } + Ok(()) +} + +pub fn clear_screen(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::clear_screen(out); + } + if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + let cells = csbi.dwSize.X as u32 * csbi.dwSize.Y as u32; // as u32, or else this causes stack overflows. + let pos = COORD { X: 0, Y: 0 }; + let mut written = 0; + FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as u32 no longer needed. + FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written); + SetConsoleCursorPosition(hand, pos); + } + } + Ok(()) +} + +pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::clear_to_end_of_screen(out); + } + if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + let bottom = csbi.srWindow.Right as u32 * csbi.srWindow.Bottom as u32; + let cells = bottom - (csbi.dwCursorPosition.X as u32 * csbi.dwCursorPosition.Y as u32); // as u32, or else this causes stack overflows. + let pos = COORD { + X: 0, + Y: csbi.dwCursorPosition.Y, + }; + let mut written = 0; + FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as u32 no longer needed. + FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written); + SetConsoleCursorPosition(hand, pos); + } + } + Ok(()) +} + +pub fn show_cursor(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::show_cursor(out); + } + if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) { + unsafe { + cci.bVisible = 1; + SetConsoleCursorInfo(hand, &cci); + } + } + Ok(()) +} + +pub fn hide_cursor(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::hide_cursor(out); + } + if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) { + unsafe { + cci.bVisible = 0; + SetConsoleCursorInfo(hand, &cci); + } + } + Ok(()) +} + +fn get_console_screen_buffer_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_SCREEN_BUFFER_INFO)> { + let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() }; + match unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } { + 0 => None, + _ => Some((hand, csbi)), + } +} + +fn get_console_cursor_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_CURSOR_INFO)> { + let mut cci: CONSOLE_CURSOR_INFO = unsafe { mem::zeroed() }; + match unsafe { GetConsoleCursorInfo(hand, &mut cci) } { + 0 => None, + _ => Some((hand, cci)), + } +} + +pub fn key_from_key_code(code: VIRTUAL_KEY) -> Key { + use windows_sys::Win32::UI::Input::KeyboardAndMouse; + + match code { + KeyboardAndMouse::VK_LEFT => Key::ArrowLeft, + KeyboardAndMouse::VK_RIGHT => Key::ArrowRight, + KeyboardAndMouse::VK_UP => Key::ArrowUp, + KeyboardAndMouse::VK_DOWN => Key::ArrowDown, + KeyboardAndMouse::VK_RETURN => Key::Enter, + KeyboardAndMouse::VK_ESCAPE => Key::Escape, + KeyboardAndMouse::VK_BACK => Key::Backspace, + KeyboardAndMouse::VK_TAB => Key::Tab, + KeyboardAndMouse::VK_HOME => Key::Home, + KeyboardAndMouse::VK_END => Key::End, + KeyboardAndMouse::VK_DELETE => Key::Del, + KeyboardAndMouse::VK_SHIFT => Key::Shift, + KeyboardAndMouse::VK_MENU => Key::Alt, + _ => Key::Unknown, + } +} + +pub fn read_secure() -> io::Result { + 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 { + 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 { + 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 { + 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 { + 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::(); + let mut name_info_bytes = vec![0u8; size + MAX_PATH as usize * mem::size_of::()]; + 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(title: T) { + let buffer: Vec = OsStr::new(&format!("{}", title)) + .encode_wide() + .chain(once(0)) + .collect(); + unsafe { + SetConsoleTitleW(buffer.as_ptr()); + } +} diff --git a/vendor/console/tests/data/sample_zellij_session.log b/vendor/console/tests/data/sample_zellij_session.log new file mode 100755 index 000000000..a77725897 --- /dev/null +++ b/vendor/console/tests/data/sample_zellij_session.log @@ -0,0 +1,56 @@ +% ]2;wintermute@ai:~/Programming/Repos/lib/console]1;..s/lib/console  +console on  use-handwritten-ansi-parser [!] is 📦 v0.15.0 via 🦀 v1.57.0  +❯ [?1h=[?2004hnnfnf[?1l>[?2004l +]2;neofetch]1;nf[?25l[?7l -` + .o+` + `ooo/ + `+oooo: + `+oooooo: + -+oooooo+: + `/:-:++oooo+: + `/++++/+++++++: + `/++++++++++++++: + `/+++ooooooooooooo/` + ./ooosssso++osssssso+` + .oossssso-````/ossssss+` + -osssssso. :ssssssso. + :osssssss/ osssso+++. + /ossssssss/ +ssssooo/- + `/ossssso+/:- -:/+osssso+- + `+sso+:-` `.-/+oso: + `++:. `-/+/ + .` `/ +wintermute@ai +------------- +OS: Arch Linux x86_64 +Host: 82BH Yoga 7 14ITL5 +Kernel: 5.15.12-arch1-1 +Uptime: 6 hours, 14 mins +Packages: 1397 (pacman) +Shell: zsh 5.8 +Resolution: 2560x1440 +WM: i3 +Theme: Adwaita [GTK3] +Icons: Adwaita [GTK3] +Terminal: zellij +CPU: 11th Gen Intel i5-1135G7 (8) @ 4.200GHz +GPU: Intel TigerLake-LP GT2 [Iris Xe Graphics] +Memory: 6181MiB / 15800MiB + +         +         + + +[?25h[?7h% ]2;wintermute@ai:~/Programming/Repos/lib/console]1;..s/lib/console  +console on  use-handwritten-ansi-parser [!] is 📦 v0.15.0 via 🦀 v1.57.0  +❯ [?1h=[?2004h[?2004l +% ]2;wintermute@ai:~/Programming/Repos/lib/console]1;..s/lib/console  +console on  use-handwritten-ansi-parser [!] is 📦 v0.15.0 via 🦀 v1.57.0  +❯ [?1h=[?2004h^jjq rslt.jsonjjq rslt.jsonjjc          cc  +console on  use-handwritten-ansi-parser [!] is 📦 v0.15.0 via 🦀 v1.57.0  +❮ jjcc  +console on  use-handwritten-ansi-parser [!] is 📦 v0.15.0 via 🦀 v1.57.0  +❯ rrangerrannggeranger[?1l>[?2004l +]2;. ranger]1;ranger[?1049h(B[?7h[?1h=[?25l[?1006;1000h(Bloading...kranger\[?1h=[?1006;1000hkranger\ (Bwintermute@ai /home/wintermute/Programming/Repos/lib/console/examples  a2s-rs (B=✓(B (B examples 4 (B ... (B console (B (B src9 (B (B fast_rsync (B (B target4 (B (B fax (B (B (BCargo.lock8.1 K (B ghostwriter (B (B (BCargo.toml1.08 K (B lead-oxide (B (B (BCHANGELOG.md1.09 K (B pdf (B (B (BLICENSE1.09 K (B pubproxpy (B (B (BREADME.md1.89 K (B rand-dir  rust-s3  rust-slack  steamlocate-~  subwinder  sysinfo  thing  vdf-rs  zip-extensio~ (Bdrwxr-xr-x 2 wintermute wintermute 4 2022-01-03 00:11 (git: use-handwritten-ansi-parser) 2022-01-05 11:59 13.3K sum, 126G free 1/8 Al[?7ll[?7h(B(B colors.rs (B (Bcolors256.rs(B (Bcursor_at.rs(B (Bterm.rs⌂ 2022-01-05 11:59(B(B⌂+(B>✓=✓=✓=✓>?=✓=✓=✓⌂+=✓=✓=✓⌂?=+=✓(B(B✓(B✓(B+✓·✓·✓✓✓✓✓✓ 2022-01-05 11:59(B(Bsrc examples4 (B✓(B ...(B(B src 9 +(B 95 17:39+2(B(B ansi.rs +(B (Bcommon_term.rs✓(B (Bkb.rs✓(B (Blib.rs✓(B (Bterm.rs✓(B (Bunix_term.rs✓(B (Butils.rs✓(B (Bwasm_term.rs✓(B (Bwindows_term.rs✓(B(Btarget(B...(B(B src9 (B+(B(B target 4 ·(B 442:53·3(B(B debug ·(B release(B·(B (BCACHEDIR.TAG·(B(Btarget/debugexamples (B✓(B (B debug 6 ·(B ...(B (B src (B+(B (B release8 (B·(B (B target ·(B (B (BCACHEDIR.TAG177 B ·(BCargo.lock ·(BCargo.toml ✓(BCHANGELOG.md ✓(BLICENSE ✓(BREADME.md ✓(B 765.061/3(B(B build ·(B deps(B·(B examples(B·(B incremental(B·(B(Brelease debug6 (B·(B ...(B(B release 8 ·(B 812(B(B build ·(B deps(B·(B examples(B·(B incremental(B·(B (Blibconsole.d·(B (Blibconsole.rlib·(B(Brelease/builddebug (B·(B (B build 6 ·(B ...(B (B release ·(B (B deps107 (B·(B (B (BCACHEDIR.TAG ·(B (B examples0 (B (B incremental0 (B·(B (B (Blibconsole.d502 B ·(B (B (Blibconsole.rlib839 K ·(B 863 8401/6(B(B libc-09f1df6a1e55c67a ·(B libc-34b2cd9a598131ff(B·(B libc-1205fcf5a10a7473(B·(B libc-2978e3f83ccd5ba9(B·(B num-traits-4b89db8e22802f0d(B·(B num-traits-b59938ea688eb694(B·(B[?1l>[?12l[?25h[?1006;1000l[?1049l [?1l>% ]2;wintermute@ai:~/Programming/Repos/lib/console/target/release]1;..arget/release  +console/target/release on  use-handwritten-ansi-parser [!] took 3s +❯ [?1h=[?2004h \ No newline at end of file -- cgit v1.2.3