Initial vendor packages

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

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

@ -0,0 +1 @@
{"files":{"CHANGELOG.md":"abf94b42ba3b3e8982c8f3453277488cdf254717750bdde6788659ef4847d6d1","Cargo.lock":"6491d36f2328f571f3334169897ecf4d1864da6485c0acc7d1880e83681acc1e","Cargo.toml":"543ccb04d5aea6cab6b2c425332effc08773c686ef9ca92ce156db64ffefabb8","LICENSE":"2022c11b24fc1e50fa06a5959e40198a3464f560729ad3d00139abb8a328362d","Makefile":"0247f9342ca2d816bec3173b41761ccba74c2a9fa0122cd7d7d2f46b4b13dc82","README.md":"2793836e3dd9e50b735103c216c2b4c6aa0fc2335816c367ea33956de24db132","examples/buffered.rs":"abaca0a19a6b3bfef72f37c9995341550a17794709515861f0f671c71cd95a56","examples/completion.rs":"01fe4ac9c26790e82ae1ae6263faa6dc143c44deadc8b83101900fc7276e4cb2","examples/confirm.rs":"cc8f6b68dfe07ea462de33abb6e9c2f96fb98903c6f47c48b4c47b2b1aa350cd","examples/editor.rs":"651e7ddef61cd3b2bd4655b619e76ab347dbfa5d1b90a7f95704e5ccd2d53d5e","examples/fuzzyselect.rs":"1a07a63cd2acbf54ee7dc305bb6689cf054186296a0a6bb3033d584e32fbe3c2","examples/history.rs":"dcb59f6d2db60bed5625545cebfda509e38ab61fa9db28c76009ac62a89b4bce","examples/input.rs":"81c27b4f6d05c589c946a03d72db552d16afe50efe9644a871e13dca4f111fb8","examples/multi_select.rs":"d41c1ee68e2b5fd6a93f4f58399d6094caee9883506f4f9f4ca1c7ed12e31068","examples/paging.rs":"66cd79fc4110b807bfd34121746d9ab6707b501a6cf4f8dfb9cfde774ad1fb2d","examples/password.rs":"d04586c76d8811e9b6a93608f7d19f663c6c284ebfe559794c6e7e3ef83b2d08","examples/select.rs":"49c633ce9cf30df11eca564029301f1240bce74e041e40aa8815929d69c5215e","examples/sort.rs":"39dd6c326ffb48b0afc69c0b72b7a256d212eb5c57bc6df6e0c7cb7e461bc753","examples/wizard.rs":"117038bc4a85204a667d45d0a76995e535b0f982befe3cd8bdbc9088401ee418","src/completion.rs":"f17a2af4341dd7098837c52da57f90cfc755b33db21599797ae1ff9d82f2b701","src/edit.rs":"6c4622998c7d0516525361d9cb988a50908045908af00e9398f51333704fdf3a","src/history.rs":"82e3d338a7cdf9172a16baba2617a28601e32df06fe109c954051f3a64dca374","src/lib.rs":"3467d3ed35906f93e1f6cfeceda6f4a79df9448880505c9d7fa1c572c189575f","src/paging.rs":"37e8a9851c80d415e828e43565818f6fd92532eea7d5e948eb6d49c98cc90316","src/prompts/confirm.rs":"5c88bd9a7a06682043bd196bad882e9f9cd59272da866afa5357b57e456752a6","src/prompts/fuzzy_select.rs":"862a5713792117744d88d1c91660ed768770d40d444ef30b07b18e81e6a7d8dc","src/prompts/input.rs":"aecb18548a63093703172ca223dff1dedd79ab86abb856e6e6fa60758dd7d413","src/prompts/mod.rs":"33c9ac2b7bdadcf5461e8a0fcb7db87d7635d7f77a00dc9049550d819f0ae6be","src/prompts/multi_select.rs":"53b21e8b90cbbbe1dddd798a611ebcfaefe3b845a7156411c80240e88f6c2afd","src/prompts/password.rs":"13c25b3e4dd8572fe4e6ab7910847b322da4eafcb36945b4903dc5df0f89606b","src/prompts/select.rs":"c29568116bf5b7dc281832f7fe31884737772aa8f5900a03e8fe44ac37b59e9a","src/prompts/sort.rs":"2a2fb7cb21720b59d7aa80d3fff12d1c87e8125bff99f2289a6a2f92696455a2","src/theme.rs":"3ec68222853ce2cf959900fb443b7f5123418d84db7cab64f24a7b6ddde9d5e2","src/validate.rs":"c61723bfbcceec71f7b2802203791ac9b81590c1cbe85ab1c17ad5cfc907ed97"},"package":"59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87"}

116
vendor/dialoguer/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,116 @@
# Changelog
## 0.10.4
### Enhancements
* Added validator for password input
## 0.10.3
### Enhancements
* Fix various issues with fuzzy select
* Enable customization of number of rows for fuzzy select
* Added post completion text for input
* Various cursor movement improvements
* Correctly ignore unknown keys.
* Reset prompt height in `TermThemeRenderer::clear`.
## 0.10.2
### Enhancements
* Fix fuzzy select active item colors.
* Fix fuzzy search clear on cancel.
* Clear everything on cancel via escape key.
## 0.10.1
### Enhancements
* Allow matches highlighting for `FuzzySelect`
## 0.10.0
### Enhancements
* Loosen some trait bounds
* Improve keyboard interactions (#141, #162)
* Added `max_length` to `MultiSelect`, `Select` and `Sort`
* Allow completion support for `Input::interact_text*` behind `completion` feature
### Breaking
* All prompts `*::new` will now don't report selected values unless `report(true)` is called on them.
## 0.9.0
### Enhancements
* Apply input validation to the default value too in `Input`
* Added `FuzzySelect` behind `fuzzy-select` feature
* Allow history processing for `Input::interact_text*` behind `history` feature
* Added `interact_*_opt` methods for `MultiSelect` and `Sort`.
### Breaking
* Updated MSRV to `1.51.0`
* `Editor` is gated behind `editor` feature
* `Password`, `Theme::format_password_prompt` and `Theme::format_password_prompt_selection` are gated behind `password` feature
* Remove `Select::paged()`, `Sort::paged()` and `MultiSelect::paged()` in favor of automatic paging based on terminal size
## 0.8.0
### Enhancements
* `Input::validate_with` can take a `FnMut` (allowing multiple references)
### Breaking
* `Input::interact*` methods take `&mut self` instead of `&self`
## 0.7.0
### Enhancements
* Added `wait_for_newline` to `Confirm`
* More secure password prompt
* More documentation
* Added `interact_text` method for `Input` prompt
* Added `inline_selections` to `ColorfulTheme`
### Breaking
* Removed `theme::CustomPromptCharacterTheme`
* `Input` validators now take the input type `T` as arg
* `Confirm` has no `default` value by default now
## 0.6.2
### Enhancements
* Updating some docs
## 0.6.1
### Bugfixes
* `theme::ColorfulTheme` default styles are for stderr
## 0.6.0
### Breaking
* Removed `theme::SelectionStyle` enum
* Allowed more customization for `theme::Theme` trait by changing methods
* Allowed more customization for `theme::ColorfulTheme` by changing members
* Renamed prompt `Confirmation` to `Confirm`
* Renamed prompt `PasswordInput` to `Password`
* Renamed prompt `OrderList` to `Sort`
* Renamed prompt `Checkboxes` to `MultiSelect`
### Enhancements
* Improved colored theme
* Improved cursor visibility manipulation

351
vendor/dialoguer/Cargo.lock generated vendored Normal file
View File

@ -0,0 +1,351 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width",
"windows-sys 0.42.0",
]
[[package]]
name = "dialoguer"
version = "0.10.4"
dependencies = [
"console",
"fuzzy-matcher",
"shell-words",
"tempfile",
"zeroize",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "errno"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.45.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "fuzzy-matcher"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
dependencies = [
"thread_local",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.48.0",
]
[[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.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
]
[[package]]
name = "rustix"
version = "0.37.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aef160324be24d31a62147fae491c14d2204a3865c7ca8c3b0d7f7bcb3ea635"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "tempfile"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys 0.45.0",
]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[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_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[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_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[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_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[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_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "zeroize"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"

83
vendor/dialoguer/Cargo.toml vendored Normal file
View File

@ -0,0 +1,83 @@
# 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"
name = "dialoguer"
version = "0.10.4"
authors = [
"Armin Ronacher <armin.ronacher@active-4.com>",
"Pavan Kumar Sunkara <pavan.sss1991@gmail.com>",
]
description = "A command line prompting library."
homepage = "https://github.com/mitsuhiko/dialoguer"
documentation = "https://docs.rs/dialoguer"
readme = "README.md"
keywords = [
"cli",
"menu",
"prompt",
]
categories = ["command-line-interface"]
license = "MIT"
repository = "https://github.com/mitsuhiko/dialoguer"
[package.metadata.docs.rs]
all-features = true
[[example]]
name = "password"
required-features = ["password"]
[[example]]
name = "editor"
required-features = ["editor"]
[[example]]
name = "fuzzyselect"
required-features = ["fuzzy-select"]
[[example]]
name = "history"
required-features = ["history"]
[[example]]
name = "completion"
required-features = ["completion"]
[dependencies.console]
version = "0.15.0"
[dependencies.fuzzy-matcher]
version = "0.3.7"
optional = true
[dependencies.shell-words]
version = "1.1.0"
[dependencies.tempfile]
version = "3"
optional = true
[dependencies.zeroize]
version = "1.1.1"
optional = true
[features]
completion = []
default = [
"editor",
"password",
]
editor = ["tempfile"]
fuzzy-select = ["fuzzy-matcher"]
history = []
password = ["zeroize"]

22
vendor/dialoguer/LICENSE vendored Normal file
View File

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

30
vendor/dialoguer/Makefile vendored Normal file
View File

@ -0,0 +1,30 @@
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
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
.PHONY: all doc build check test format format-check lint

19
vendor/dialoguer/README.md vendored Normal file
View File

@ -0,0 +1,19 @@
# dialoguer
[![Build Status](https://github.com/console-rs/dialoguer/workflows/CI/badge.svg)](https://github.com/console-rs/dialoguer/actions?query=branch%3Amaster)
[![Latest version](https://img.shields.io/crates/v/dialoguer.svg)](https://crates.io/crates/dialoguer)
[![Documentation](https://docs.rs/dialoguer/badge.svg)](https://docs.rs/dialoguer)
A rust library for command line prompts and similar things.
Best paired with other libraries in the family:
* [console](https://github.com/console-rs/console)
* [indicatif](https://github.com/console-rs/indicatif)
## License and Links
* [Documentation](https://docs.rs/dialoguer/)
* [Issue Tracker](https://github.com/console-rs/dialoguer/issues)
* [Examples](https://github.com/console-rs/dialoguer/tree/master/examples)
* License: [MIT](https://github.com/console-rs/dialoguer/blob/main/LICENSE)

42
vendor/dialoguer/examples/buffered.rs vendored Normal file
View File

@ -0,0 +1,42 @@
use console::Term;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select, Sort};
fn main() {
let items = &[
"Ice Cream",
"Vanilla Cupcake",
"Chocolate Muffin",
"A Pile of sweet, sweet mustard",
];
let term = Term::buffered_stderr();
let theme = ColorfulTheme::default();
println!("All the following controls are run in a buffered terminal");
Confirm::with_theme(&theme)
.with_prompt("Do you want to continue?")
.interact_on(&term)
.unwrap();
let _: String = Input::with_theme(&theme)
.with_prompt("Your name")
.interact_on(&term)
.unwrap();
Select::with_theme(&theme)
.with_prompt("Pick an item")
.items(items)
.interact_on(&term)
.unwrap();
MultiSelect::with_theme(&theme)
.with_prompt("Pick some items")
.items(items)
.interact_on(&term)
.unwrap();
Sort::with_theme(&theme)
.with_prompt("Order these items")
.items(items)
.interact_on(&term)
.unwrap();
}

44
vendor/dialoguer/examples/completion.rs vendored Normal file
View File

@ -0,0 +1,44 @@
use dialoguer::{theme::ColorfulTheme, Completion, Input};
fn main() -> Result<(), std::io::Error> {
println!("Use the Right arrow or Tab to complete your command");
let completion = MyCompletion::default();
Input::<String>::with_theme(&ColorfulTheme::default())
.with_prompt("dialoguer")
.completion_with(&completion)
.interact_text()?;
Ok(())
}
struct MyCompletion {
options: Vec<String>,
}
impl Default for MyCompletion {
fn default() -> Self {
MyCompletion {
options: vec![
"orange".to_string(),
"apple".to_string(),
"banana".to_string(),
],
}
}
}
impl Completion for MyCompletion {
/// Simple completion implementation based on substring
fn get(&self, input: &str) -> Option<String> {
let matches = self
.options
.iter()
.filter(|option| option.starts_with(input))
.collect::<Vec<_>>();
if matches.len() == 1 {
Some(matches[0].to_string())
} else {
None
}
}
}

70
vendor/dialoguer/examples/confirm.rs vendored Normal file
View File

@ -0,0 +1,70 @@
use dialoguer::{theme::ColorfulTheme, Confirm};
fn main() {
if Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Do you want to continue?")
.interact()
.unwrap()
{
println!("Looks like you want to continue");
} else {
println!("nevermind then :(");
}
if Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Do you really want to continue?")
.default(true)
.interact()
.unwrap()
{
println!("Looks like you want to continue");
} else {
println!("nevermind then :(");
}
if Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Do you really really want to continue?")
.default(true)
.show_default(false)
.wait_for_newline(true)
.interact()
.unwrap()
{
println!("Looks like you want to continue");
} else {
println!("nevermind then :(");
}
if Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Do you really really really want to continue?")
.wait_for_newline(true)
.interact()
.unwrap()
{
println!("Looks like you want to continue");
} else {
println!("nevermind then :(");
}
match Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Do you really really really really want to continue?")
.interact_opt()
.unwrap()
{
Some(true) => println!("Looks like you want to continue"),
Some(false) => println!("nevermind then :("),
None => println!("Ok, we can start over later"),
}
match Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Do you really really really really really want to continue?")
.default(true)
.wait_for_newline(true)
.interact_opt()
.unwrap()
{
Some(true) => println!("Looks like you want to continue"),
Some(false) => println!("nevermind then :("),
None => println!("Ok, we can start over later"),
}
}

10
vendor/dialoguer/examples/editor.rs vendored Normal file
View File

@ -0,0 +1,10 @@
use dialoguer::Editor;
fn main() {
if let Some(rv) = Editor::new().edit("Enter a commit message").unwrap() {
println!("Your message:");
println!("{}", rv);
} else {
println!("Abort!");
}
}

View File

@ -0,0 +1,43 @@
use dialoguer::{theme::ColorfulTheme, FuzzySelect};
fn main() {
let selections = &[
"Ice Cream",
"Vanilla Cupcake",
"Chocolate Muffin",
"A Pile of sweet, sweet mustard",
"Carrots",
"Peas",
"Pistacio",
"Mustard",
"Cream",
"Banana",
"Chocolate",
"Flakes",
"Corn",
"Cake",
"Tarte",
"Cheddar",
"Vanilla",
"Hazelnut",
"Flour",
"Sugar",
"Salt",
"Potato",
"French Fries",
"Pizza",
"Mousse au chocolat",
"Brown sugar",
"Blueberry",
"Burger",
];
let selection = FuzzySelect::with_theme(&ColorfulTheme::default())
.with_prompt("Pick your flavor")
.default(0)
.items(&selections[..])
.interact()
.unwrap();
println!("Enjoy your {}!", selections[selection]);
}

51
vendor/dialoguer/examples/history.rs vendored Normal file
View File

@ -0,0 +1,51 @@
use dialoguer::{theme::ColorfulTheme, History, Input};
use std::{collections::VecDeque, process};
fn main() {
println!("Use 'exit' to quit the prompt");
println!("In this example, history is limited to 4 entries");
println!("Use the Up/Down arrows to scroll through history");
println!();
let mut history = MyHistory::default();
loop {
if let Ok(cmd) = Input::<String>::with_theme(&ColorfulTheme::default())
.with_prompt("dialoguer")
.history_with(&mut history)
.interact_text()
{
if cmd == "exit" {
process::exit(0);
}
println!("Entered {}", cmd);
}
}
}
struct MyHistory {
max: usize,
history: VecDeque<String>,
}
impl Default for MyHistory {
fn default() -> Self {
MyHistory {
max: 4,
history: VecDeque::new(),
}
}
}
impl<T: ToString> History<T> for MyHistory {
fn read(&self, pos: usize) -> Option<String> {
self.history.get(pos).cloned()
}
fn write(&mut self, val: &T) {
if self.history.len() == self.max {
self.history.pop_back();
}
self.history.push_front(val.to_string());
}
}

44
vendor/dialoguer/examples/input.rs vendored Normal file
View File

@ -0,0 +1,44 @@
use dialoguer::{theme::ColorfulTheme, Input};
fn main() {
let input: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Your name")
.interact_text()
.unwrap();
println!("Hello {}!", input);
let mail: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Your email")
.validate_with({
let mut force = None;
move |input: &String| -> Result<(), &str> {
if input.contains('@') || force.as_ref().map_or(false, |old| old == input) {
Ok(())
} else {
force = Some(input.clone());
Err("This is not a mail address; type the same value again to force use")
}
}
})
.interact_text()
.unwrap();
println!("Email: {}", mail);
let mail: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Your planet")
.default("Earth".to_string())
.interact_text()
.unwrap();
println!("Planet: {}", mail);
let mail: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Your galaxy")
.with_initial_text("Milky Way".to_string())
.interact_text()
.unwrap();
println!("Galaxy: {}", mail);
}

View File

@ -0,0 +1,42 @@
use dialoguer::{theme::ColorfulTheme, MultiSelect};
fn main() {
let multiselected = &[
"Ice Cream",
"Vanilla Cupcake",
"Chocolate Muffin",
"A Pile of sweet, sweet mustard",
];
let defaults = &[false, false, true, false];
let selections = MultiSelect::with_theme(&ColorfulTheme::default())
.with_prompt("Pick your food")
.items(&multiselected[..])
.defaults(&defaults[..])
.interact()
.unwrap();
if selections.is_empty() {
println!("You did not select anything :(");
} else {
println!("You selected these things:");
for selection in selections {
println!(" {}", multiselected[selection]);
}
}
let selections = MultiSelect::with_theme(&ColorfulTheme::default())
.with_prompt("Pick your food")
.items(&multiselected[..])
.defaults(&defaults[..])
.max_length(2)
.interact()
.unwrap();
if selections.is_empty() {
println!("You did not select anything :(");
} else {
println!("You selected these things:");
for selection in selections {
println!(" {}", multiselected[selection]);
}
}
}

43
vendor/dialoguer/examples/paging.rs vendored Normal file
View File

@ -0,0 +1,43 @@
use dialoguer::{theme::ColorfulTheme, Select};
fn main() {
let selections = &[
"Ice Cream",
"Vanilla Cupcake",
"Chocolate Muffin",
"A Pile of sweet, sweet mustard",
"Carrots",
"Peas",
"Pistacio",
"Mustard",
"Cream",
"Banana",
"Chocolate",
"Flakes",
"Corn",
"Cake",
"Tarte",
"Cheddar",
"Vanilla",
"Hazelnut",
"Flour",
"Sugar",
"Salt",
"Potato",
"French Fries",
"Pizza",
"Mousse au chocolat",
"Brown sugar",
"Blueberry",
"Burger",
];
let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Pick your flavor")
.default(0)
.items(&selections[..])
.interact()
.unwrap();
println!("Enjoy your {}!", selections[selection]);
}

18
vendor/dialoguer/examples/password.rs vendored Normal file
View File

@ -0,0 +1,18 @@
use dialoguer::{theme::ColorfulTheme, Password};
fn main() {
let password = Password::with_theme(&ColorfulTheme::default())
.with_prompt("Password")
.with_confirmation("Repeat password", "Error: the passwords don't match.")
.validate_with(|input: &String| -> Result<(), &str> {
if input.len() > 3 {
Ok(())
} else {
Err("Password must be longer than 3")
}
})
.interact()
.unwrap();
println!("Your password is {} characters long", password.len());
}

42
vendor/dialoguer/examples/select.rs vendored Normal file
View File

@ -0,0 +1,42 @@
use dialoguer::{theme::ColorfulTheme, Select};
fn main() {
let selections = &[
"Ice Cream",
"Vanilla Cupcake",
"Chocolate Muffin",
"A Pile of sweet, sweet mustard",
];
let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Pick your flavor")
.default(0)
.items(&selections[..])
.interact()
.unwrap();
println!("Enjoy your {}!", selections[selection]);
let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Optionally pick your flavor")
.default(0)
.items(&selections[..])
.interact_opt()
.unwrap();
if let Some(selection) = selection {
println!("Enjoy your {}!", selections[selection]);
} else {
println!("You didn't select anything!");
}
let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Optionally pick your flavor, hint it might be on the second page")
.default(0)
.max_length(2)
.items(&selections[..])
.interact()
.unwrap();
println!("Enjoy your {}!", selections[selection]);
}

32
vendor/dialoguer/examples/sort.rs vendored Normal file
View File

@ -0,0 +1,32 @@
use dialoguer::{theme::ColorfulTheme, Sort};
fn main() {
let list = &[
"Ice Cream",
"Vanilla Cupcake",
"Chocolate Muffin",
"A Pile of sweet, sweet mustard",
];
let sorted = Sort::with_theme(&ColorfulTheme::default())
.with_prompt("Order your foods by preference")
.items(&list[..])
.interact()
.unwrap();
println!("Your favorite item:");
println!(" {}", list[sorted[0]]);
println!("Your least favorite item:");
println!(" {}", list[sorted[sorted.len() - 1]]);
let sorted = Sort::with_theme(&ColorfulTheme::default())
.with_prompt("Order your foods by preference")
.items(&list[..])
.max_length(2)
.interact()
.unwrap();
println!("Your favorite item:");
println!(" {}", list[sorted[0]]);
println!("Your least favorite item:");
println!(" {}", list[sorted[sorted.len() - 1]]);
}

81
vendor/dialoguer/examples/wizard.rs vendored Normal file
View File

@ -0,0 +1,81 @@
use std::error::Error;
use std::net::IpAddr;
use console::Style;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
#[derive(Debug)]
#[allow(dead_code)]
struct Config {
interface: IpAddr,
hostname: String,
use_acme: bool,
private_key: Option<String>,
cert: Option<String>,
}
fn init_config() -> Result<Option<Config>, Box<dyn Error>> {
let theme = ColorfulTheme {
values_style: Style::new().yellow().dim(),
..ColorfulTheme::default()
};
println!("Welcome to the setup wizard");
if !Confirm::with_theme(&theme)
.with_prompt("Do you want to continue?")
.interact()?
{
return Ok(None);
}
let interface = Input::with_theme(&theme)
.with_prompt("Interface")
.default("127.0.0.1".parse().unwrap())
.interact()?;
let hostname = Input::with_theme(&theme)
.with_prompt("Hostname")
.interact()?;
let tls = Select::with_theme(&theme)
.with_prompt("Configure TLS")
.default(0)
.item("automatic with ACME")
.item("manual")
.item("no")
.interact()?;
let (private_key, cert, use_acme) = match tls {
0 => (Some("acme.pkey".into()), Some("acme.cert".into()), true),
1 => (
Some(
Input::with_theme(&theme)
.with_prompt(" Path to private key")
.interact()?,
),
Some(
Input::with_theme(&theme)
.with_prompt(" Path to certificate")
.interact()?,
),
false,
),
_ => (None, None, false),
};
Ok(Some(Config {
hostname,
interface,
private_key,
cert,
use_acme,
}))
}
fn main() {
match init_config() {
Ok(None) => println!("Aborted."),
Ok(Some(config)) => println!("{:#?}", config),
Err(err) => println!("error: {}", err),
}
}

4
vendor/dialoguer/src/completion.rs vendored Normal file
View File

@ -0,0 +1,4 @@
/// Trait for completion handling.
pub trait Completion {
fn get(&self, input: &str) -> Option<String>;
}

131
vendor/dialoguer/src/edit.rs vendored Normal file
View File

@ -0,0 +1,131 @@
use std::{
env,
ffi::{OsStr, OsString},
fs, io,
io::{Read, Write},
process,
};
/// Launches the default editor to edit a string.
///
/// ## Example
///
/// ```rust,no_run
/// use dialoguer::Editor;
///
/// if let Some(rv) = Editor::new().edit("Enter a commit message").unwrap() {
/// println!("Your message:");
/// println!("{}", rv);
/// } else {
/// println!("Abort!");
/// }
/// ```
pub struct Editor {
editor: OsString,
extension: String,
require_save: bool,
trim_newlines: bool,
}
fn get_default_editor() -> OsString {
if let Some(prog) = env::var_os("VISUAL") {
return prog;
}
if let Some(prog) = env::var_os("EDITOR") {
return prog;
}
if cfg!(windows) {
"notepad.exe".into()
} else {
"vi".into()
}
}
impl Default for Editor {
fn default() -> Self {
Self::new()
}
}
impl Editor {
/// Creates a new editor.
pub fn new() -> Self {
Self {
editor: get_default_editor(),
extension: ".txt".into(),
require_save: true,
trim_newlines: true,
}
}
/// Sets a specific editor executable.
pub fn executable<S: AsRef<OsStr>>(&mut self, val: S) -> &mut Self {
self.editor = val.as_ref().into();
self
}
/// Sets a specific extension
pub fn extension(&mut self, val: &str) -> &mut Self {
self.extension = val.into();
self
}
/// Enables or disables the save requirement.
pub fn require_save(&mut self, val: bool) -> &mut Self {
self.require_save = val;
self
}
/// Enables or disables trailing newline stripping.
///
/// This is on by default.
pub fn trim_newlines(&mut self, val: bool) -> &mut Self {
self.trim_newlines = val;
self
}
/// Launches the editor to edit a string.
///
/// Returns `None` if the file was not saved or otherwise the
/// entered text.
pub fn edit(&self, s: &str) -> io::Result<Option<String>> {
let mut f = tempfile::Builder::new()
.prefix("edit-")
.suffix(&self.extension)
.rand_bytes(12)
.tempfile()?;
f.write_all(s.as_bytes())?;
f.flush()?;
let ts = fs::metadata(f.path())?.modified()?;
let s: String = self.editor.clone().into_string().unwrap();
let (cmd, args) = match shell_words::split(&s) {
Ok(mut parts) => {
let cmd = parts.remove(0);
(cmd, parts)
}
Err(_) => (s, vec![]),
};
let rv = process::Command::new(cmd)
.args(args)
.arg(f.path())
.spawn()?
.wait()?;
if rv.success() && self.require_save && ts >= fs::metadata(f.path())?.modified()? {
return Ok(None);
}
let mut new_f = fs::File::open(f.path())?;
let mut rv = String::new();
new_f.read_to_string(&mut rv)?;
if self.trim_newlines {
let len = rv.trim_end_matches(&['\n', '\r'][..]).len();
rv.truncate(len);
}
Ok(Some(rv))
}
}

15
vendor/dialoguer/src/history.rs vendored Normal file
View File

@ -0,0 +1,15 @@
/// Trait for history handling.
pub trait History<T> {
/// This is called with the current position that should
/// be read from history. The `pos` represents the number
/// of times the `Up`/`Down` arrow key has been pressed.
/// This would normally be used as an index to some sort
/// of vector. If the `pos` does not have an entry, [`None`](Option::None)
/// should be returned.
fn read(&self, pos: usize) -> Option<String>;
/// This is called with the next value you should store
/// in history at the first location. Normally history
/// is implemented as a FIFO queue.
fn write(&mut self, val: &T);
}

62
vendor/dialoguer/src/lib.rs vendored Normal file
View File

@ -0,0 +1,62 @@
//! dialoguer is a library for Rust that helps you build useful small
//! interactive user inputs for the command line. It provides utilities
//! to render various simple dialogs like confirmation prompts, text
//! inputs and more.
//!
//! Best paired with other libraries in the family:
//!
//! * [indicatif](https://docs.rs/indicatif)
//! * [console](https://docs.rs/console)
//!
//! # Crate Contents
//!
//! * Confirmation prompts
//! * Input prompts (regular and password)
//! * Input validation
//! * Selections prompts (single and multi)
//! * Fuzzy select prompt
//! * Other kind of prompts
//! * Editor launching
//!
//! # Crate Features
//!
//! The following crate features are available:
//! * `editor`: enables bindings to launch editor to edit strings
//! * `fuzzy-select`: enables fuzzy select prompt
//! * `history`: enables input prompts to be able to track history of inputs
//! * `password`: enables password input prompt
//! * `completion`: enables ability to implement custom tab-completion for input prompts
//!
//! By default `editor` and `password` are enabled.
#![deny(clippy::all)]
#[cfg(feature = "completion")]
pub use completion::Completion;
pub use console;
#[cfg(feature = "editor")]
pub use edit::Editor;
#[cfg(feature = "history")]
pub use history::History;
use paging::Paging;
pub use prompts::{
confirm::Confirm, input::Input, multi_select::MultiSelect, select::Select, sort::Sort,
};
pub use validate::Validator;
#[cfg(feature = "fuzzy-select")]
pub use prompts::fuzzy_select::FuzzySelect;
#[cfg(feature = "password")]
pub use prompts::password::Password;
#[cfg(feature = "completion")]
mod completion;
#[cfg(feature = "editor")]
mod edit;
#[cfg(feature = "history")]
mod history;
mod paging;
mod prompts;
pub mod theme;
mod validate;

118
vendor/dialoguer/src/paging.rs vendored Normal file
View File

@ -0,0 +1,118 @@
use std::io;
use console::Term;
/// Creates a paging module
///
/// The paging module serves as tracking structure to allow paged views
/// and automatically (de-)activates paging depending on the current terminal size.
pub struct Paging<'a> {
pub pages: usize,
pub current_page: usize,
pub capacity: usize,
pub active: bool,
pub max_capacity: Option<usize>,
term: &'a Term,
current_term_size: (u16, u16),
items_len: usize,
activity_transition: bool,
}
impl<'a> Paging<'a> {
pub fn new(term: &'a Term, items_len: usize, max_capacity: Option<usize>) -> Paging<'a> {
let term_size = term.size();
// Subtract -2 because we need space to render the prompt, if paging is active
let capacity = max_capacity
.unwrap_or(std::usize::MAX)
.clamp(3, term_size.0 as usize)
- 2;
let pages = (items_len as f64 / capacity as f64).ceil() as usize;
Paging {
pages,
current_page: 0,
capacity,
active: pages > 1,
term,
current_term_size: term_size,
items_len,
max_capacity,
// Set transition initially to true to trigger prompt rendering for inactive paging on start
activity_transition: true,
}
}
/// Updates all internal based on the current terminal size and cursor position
pub fn update(&mut self, cursor_pos: usize) -> io::Result<()> {
let new_term_size = self.term.size();
if self.current_term_size != new_term_size {
self.current_term_size = new_term_size;
self.capacity = self
.max_capacity
.unwrap_or(std::usize::MAX)
.clamp(3, self.current_term_size.0 as usize)
- 2;
self.pages = (self.items_len as f64 / self.capacity as f64).ceil() as usize;
}
if self.active == (self.pages > 1) {
self.activity_transition = false;
} else {
self.active = self.pages > 1;
self.activity_transition = true;
// Clear everything to prevent "ghost" lines in terminal when a resize happened
self.term.clear_last_lines(self.capacity)?;
}
if cursor_pos != !0
&& (cursor_pos < self.current_page * self.capacity
|| cursor_pos >= (self.current_page + 1) * self.capacity)
{
self.current_page = cursor_pos / self.capacity;
}
Ok(())
}
/// Renders a prompt when the following conditions are met:
/// * Paging is active
/// * Transition of the paging activity happened (active -> inactive / inactive -> active)
pub fn render_prompt<F>(&mut self, mut render_prompt: F) -> io::Result<()>
where
F: FnMut(Option<(usize, usize)>) -> io::Result<()>,
{
if self.active {
let paging_info = Some((self.current_page + 1, self.pages));
render_prompt(paging_info)?;
} else if self.activity_transition {
render_prompt(None)?;
}
self.term.flush()?;
Ok(())
}
/// Navigates to the next page
pub fn next_page(&mut self) -> usize {
if self.current_page == self.pages - 1 {
self.current_page = 0;
} else {
self.current_page += 1;
}
self.current_page * self.capacity
}
/// Navigates to the previous page
pub fn previous_page(&mut self) -> usize {
if self.current_page == 0 {
self.current_page = self.pages - 1;
} else {
self.current_page -= 1;
}
self.current_page * self.capacity
}
}

287
vendor/dialoguer/src/prompts/confirm.rs vendored Normal file
View File

@ -0,0 +1,287 @@
use std::io;
use crate::theme::{SimpleTheme, TermThemeRenderer, Theme};
use console::{Key, Term};
/// Renders a confirm prompt.
///
/// ## Example usage
///
/// ```rust,no_run
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// use dialoguer::Confirm;
///
/// if Confirm::new().with_prompt("Do you want to continue?").interact()? {
/// println!("Looks like you want to continue");
/// } else {
/// println!("nevermind then :(");
/// }
/// # Ok(()) } fn main() { test().unwrap(); }
/// ```
pub struct Confirm<'a> {
prompt: String,
report: bool,
default: Option<bool>,
show_default: bool,
wait_for_newline: bool,
theme: &'a dyn Theme,
}
impl Default for Confirm<'static> {
fn default() -> Self {
Self::new()
}
}
impl Confirm<'static> {
/// Creates a confirm prompt.
pub fn new() -> Self {
Self::with_theme(&SimpleTheme)
}
}
impl Confirm<'_> {
/// Sets the confirm prompt.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
self.prompt = prompt.into();
self
}
/// Indicates whether or not to report the chosen selection after interaction.
///
/// The default is to report the chosen selection.
pub fn report(&mut self, val: bool) -> &mut Self {
self.report = val;
self
}
#[deprecated(note = "Use with_prompt() instead", since = "0.6.0")]
#[inline]
pub fn with_text(&mut self, text: &str) -> &mut Self {
self.with_prompt(text)
}
/// Sets when to react to user input.
///
/// When `false` (default), we check on each user keystroke immediately as
/// it is typed. Valid inputs can be one of 'y', 'n', or a newline to accept
/// the default.
///
/// When `true`, the user must type their choice and hit the Enter key before
/// proceeding. Valid inputs can be "yes", "no", "y", "n", or an empty string
/// to accept the default.
pub fn wait_for_newline(&mut self, wait: bool) -> &mut Self {
self.wait_for_newline = wait;
self
}
/// Sets a default.
///
/// Out of the box the prompt does not have a default and will continue
/// to display until the user inputs something and hits enter. If a default is set the user
/// can instead accept the default with enter.
pub fn default(&mut self, val: bool) -> &mut Self {
self.default = Some(val);
self
}
/// Disables or enables the default value display.
///
/// The default is to append the default value to the prompt to tell the user.
pub fn show_default(&mut self, val: bool) -> &mut Self {
self.show_default = val;
self
}
/// Enables user interaction and returns the result.
///
/// The dialog is rendered on stderr.
///
/// Result contains `bool` if user answered "yes" or "no" or `default` (configured in [`default`](Self::default) if pushes enter.
/// This unlike [`interact_opt`](Self::interact_opt) does not allow to quit with 'Esc' or 'q'.
#[inline]
pub fn interact(&self) -> io::Result<bool> {
self.interact_on(&Term::stderr())
}
/// Enables user interaction and returns the result.
///
/// The dialog is rendered on stderr.
///
/// Result contains `Some(bool)` if user answered "yes" or "no" or `Some(default)` (configured in [`default`](Self::default)) if pushes enter,
/// or `None` if user cancelled with 'Esc' or 'q'.
#[inline]
pub fn interact_opt(&self) -> io::Result<Option<bool>> {
self.interact_on_opt(&Term::stderr())
}
/// Like [interact](#method.interact) but allows a specific terminal to be set.
///
/// ## Examples
///
/// ```rust,no_run
/// use dialoguer::Confirm;
/// use console::Term;
///
/// # fn main() -> std::io::Result<()> {
/// let proceed = Confirm::new()
/// .with_prompt("Do you wish to continue?")
/// .interact_on(&Term::stderr())?;
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn interact_on(&self, term: &Term) -> io::Result<bool> {
self._interact_on(term, false)?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))
}
/// Like [`interact_opt`](Self::interact_opt) but allows a specific terminal to be set.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::Confirm;
/// use console::Term;
///
/// fn main() -> std::io::Result<()> {
/// let confirmation = Confirm::new()
/// .interact_on_opt(&Term::stdout())?;
///
/// match confirmation {
/// Some(answer) => println!("User answered {}", if answer { "yes" } else { "no " }),
/// None => println!("User did not answer")
/// }
///
/// Ok(())
/// }
/// ```
#[inline]
pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<bool>> {
self._interact_on(term, true)
}
fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<bool>> {
let mut render = TermThemeRenderer::new(term, self.theme);
let default_if_show = if self.show_default {
self.default
} else {
None
};
render.confirm_prompt(&self.prompt, default_if_show)?;
term.hide_cursor()?;
term.flush()?;
let rv;
if self.wait_for_newline {
// Waits for user input and for the user to hit the Enter key
// before validation.
let mut value = default_if_show;
loop {
let input = term.read_key()?;
match input {
Key::Char('y') | Key::Char('Y') => {
value = Some(true);
}
Key::Char('n') | Key::Char('N') => {
value = Some(false);
}
Key::Enter => {
if !allow_quit {
value = value.or(self.default);
}
if value.is_some() || allow_quit {
rv = value;
break;
}
continue;
}
Key::Escape | Key::Char('q') if allow_quit => {
value = None;
}
Key::Unknown => {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"Not a terminal",
))
}
_ => {
continue;
}
};
term.clear_line()?;
render.confirm_prompt(&self.prompt, value)?;
}
} else {
// Default behavior: matches continuously on every keystroke,
// and does not wait for user to hit the Enter key.
loop {
let input = term.read_key()?;
let value = match input {
Key::Char('y') | Key::Char('Y') => Some(true),
Key::Char('n') | Key::Char('N') => Some(false),
Key::Enter if self.default.is_some() => Some(self.default.unwrap()),
Key::Escape | Key::Char('q') if allow_quit => None,
Key::Unknown => {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"Not a terminal",
))
}
_ => {
continue;
}
};
rv = value;
break;
}
}
term.clear_line()?;
if self.report {
render.confirm_prompt_selection(&self.prompt, rv)?;
}
term.show_cursor()?;
term.flush()?;
Ok(rv)
}
}
impl<'a> Confirm<'a> {
/// Creates a confirm prompt with a specific theme.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::{
/// Confirm,
/// theme::ColorfulTheme
/// };
///
/// # fn main() -> std::io::Result<()> {
/// let proceed = Confirm::with_theme(&ColorfulTheme::default())
/// .with_prompt("Do you wish to continue?")
/// .interact()?;
/// # Ok(())
/// # }
/// ```
pub fn with_theme(theme: &'a dyn Theme) -> Self {
Self {
prompt: "".into(),
report: true,
default: None,
show_default: true,
wait_for_newline: false,
theme,
}
}
}

View File

@ -0,0 +1,326 @@
use crate::theme::{SimpleTheme, TermThemeRenderer, Theme};
use console::{Key, Term};
use fuzzy_matcher::FuzzyMatcher;
use std::{io, ops::Rem};
/// Renders a selection menu that user can fuzzy match to reduce set.
///
/// User can use fuzzy search to limit selectable items.
/// Interaction returns index of an item selected in the order they appear in `item` invocation or `items` slice.
///
/// ## Examples
///
/// ```rust,no_run
/// use dialoguer::{
/// FuzzySelect,
/// theme::ColorfulTheme
/// };
/// use console::Term;
///
/// fn main() -> std::io::Result<()> {
/// let items = vec!["Item 1", "item 2"];
/// let selection = FuzzySelect::with_theme(&ColorfulTheme::default())
/// .items(&items)
/// .default(0)
/// .interact_on_opt(&Term::stderr())?;
///
/// match selection {
/// Some(index) => println!("User selected item : {}", items[index]),
/// None => println!("User did not select anything")
/// }
///
/// Ok(())
/// }
/// ```
pub struct FuzzySelect<'a> {
default: Option<usize>,
items: Vec<String>,
prompt: String,
report: bool,
clear: bool,
highlight_matches: bool,
max_length: Option<usize>,
theme: &'a dyn Theme,
/// Search string that a fuzzy search with start with.
/// Defaults to an empty string.
initial_text: String,
}
impl Default for FuzzySelect<'static> {
fn default() -> Self {
Self::new()
}
}
impl FuzzySelect<'static> {
/// Creates the prompt with a specific text.
pub fn new() -> Self {
Self::with_theme(&SimpleTheme)
}
}
impl FuzzySelect<'_> {
/// Sets the clear behavior of the menu.
///
/// The default is to clear the menu.
pub fn clear(&mut self, val: bool) -> &mut Self {
self.clear = val;
self
}
/// Sets a default for the menu
pub fn default(&mut self, val: usize) -> &mut Self {
self.default = Some(val);
self
}
/// Add a single item to the fuzzy selector.
pub fn item<T: ToString>(&mut self, item: T) -> &mut Self {
self.items.push(item.to_string());
self
}
/// Adds multiple items to the fuzzy selector.
pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Self {
for item in items {
self.items.push(item.to_string());
}
self
}
/// Sets the search text that a fuzzy search starts with.
pub fn with_initial_text<S: Into<String>>(&mut self, initial_text: S) -> &mut Self {
self.initial_text = initial_text.into();
self
}
/// Prefaces the menu with a prompt.
///
/// When a prompt is set the system also prints out a confirmation after
/// the fuzzy selection.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
self.prompt = prompt.into();
self
}
/// Indicates whether to report the selected value after interaction.
///
/// The default is to report the selection.
pub fn report(&mut self, val: bool) -> &mut Self {
self.report = val;
self
}
/// Indicates whether to highlight matched indices
///
/// The default is to highlight the indices
pub fn highlight_matches(&mut self, val: bool) -> &mut Self {
self.highlight_matches = val;
self
}
/// Sets the maximum number of visible options.
///
/// The default is the height of the terminal minus 2.
pub fn max_length(&mut self, rows: usize) -> &mut Self {
self.max_length = Some(rows);
self
}
/// Enables user interaction and returns the result.
///
/// The user can select the items using 'Enter' and the index of selected item will be returned.
/// The dialog is rendered on stderr.
/// Result contains `index` of selected item if user hit 'Enter'.
/// This unlike [interact_opt](#method.interact_opt) does not allow to quit with 'Esc' or 'q'.
#[inline]
pub fn interact(&self) -> io::Result<usize> {
self.interact_on(&Term::stderr())
}
/// Enables user interaction and returns the result.
///
/// The user can select the items using 'Enter' and the index of selected item will be returned.
/// The dialog is rendered on stderr.
/// Result contains `Some(index)` if user hit 'Enter' or `None` if user cancelled with 'Esc' or 'q'.
#[inline]
pub fn interact_opt(&self) -> io::Result<Option<usize>> {
self.interact_on_opt(&Term::stderr())
}
/// Like `interact` but allows a specific terminal to be set.
#[inline]
pub fn interact_on(&self, term: &Term) -> io::Result<usize> {
self._interact_on(term, false)?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))
}
/// Like `interact` but allows a specific terminal to be set.
#[inline]
pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<usize>> {
self._interact_on(term, true)
}
/// Like `interact` but allows a specific terminal to be set.
fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<usize>> {
// Place cursor at the end of the search term
let mut position = self.initial_text.len();
let mut search_term = self.initial_text.to_owned();
let mut render = TermThemeRenderer::new(term, self.theme);
let mut sel = self.default;
let mut size_vec = Vec::new();
for items in self.items.iter().as_slice() {
let size = &items.len();
size_vec.push(*size);
}
// Fuzzy matcher
let matcher = fuzzy_matcher::skim::SkimMatcherV2::default();
// Subtract -2 because we need space to render the prompt.
let visible_term_rows = (term.size().0 as usize).max(3) - 2;
let visible_term_rows = self
.max_length
.unwrap_or(visible_term_rows)
.min(visible_term_rows);
// Variable used to determine if we need to scroll through the list.
let mut starting_row = 0;
term.hide_cursor()?;
loop {
render.clear()?;
render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, position)?;
// Maps all items to a tuple of item and its match score.
let mut filtered_list = self
.items
.iter()
.map(|item| (item, matcher.fuzzy_match(item, &search_term)))
.filter_map(|(item, score)| score.map(|s| (item, s)))
.collect::<Vec<_>>();
// Renders all matching items, from best match to worst.
filtered_list.sort_unstable_by(|(_, s1), (_, s2)| s2.cmp(s1));
for (idx, (item, _)) in filtered_list
.iter()
.enumerate()
.skip(starting_row)
.take(visible_term_rows)
{
render.fuzzy_select_prompt_item(
item,
Some(idx) == sel,
self.highlight_matches,
&matcher,
&search_term,
)?;
}
term.flush()?;
match (term.read_key()?, sel) {
(Key::Escape, _) if allow_quit => {
if self.clear {
render.clear()?;
term.flush()?;
}
term.show_cursor()?;
return Ok(None);
}
(Key::ArrowUp | Key::BackTab, _) if !filtered_list.is_empty() => {
if sel == Some(0) {
starting_row =
filtered_list.len().max(visible_term_rows) - visible_term_rows;
} else if sel == Some(starting_row) {
starting_row -= 1;
}
sel = match sel {
None => Some(filtered_list.len() - 1),
Some(sel) => Some(
((sel as i64 - 1 + filtered_list.len() as i64)
% (filtered_list.len() as i64))
as usize,
),
};
term.flush()?;
}
(Key::ArrowDown | Key::Tab, _) if !filtered_list.is_empty() => {
sel = match sel {
None => Some(0),
Some(sel) => {
Some((sel as u64 + 1).rem(filtered_list.len() as u64) as usize)
}
};
if sel == Some(visible_term_rows + starting_row) {
starting_row += 1;
} else if sel == Some(0) {
starting_row = 0;
}
term.flush()?;
}
(Key::ArrowLeft, _) if position > 0 => {
position -= 1;
term.flush()?;
}
(Key::ArrowRight, _) if position < search_term.len() => {
position += 1;
term.flush()?;
}
(Key::Enter, Some(sel)) if !filtered_list.is_empty() => {
if self.clear {
render.clear()?;
}
if self.report {
render
.input_prompt_selection(self.prompt.as_str(), filtered_list[sel].0)?;
}
let sel_string = filtered_list[sel].0;
let sel_string_pos_in_items =
self.items.iter().position(|item| item.eq(sel_string));
term.show_cursor()?;
return Ok(sel_string_pos_in_items);
}
(Key::Backspace, _) if position > 0 => {
position -= 1;
search_term.remove(position);
term.flush()?;
}
(Key::Char(chr), _) if !chr.is_ascii_control() => {
search_term.insert(position, chr);
position += 1;
term.flush()?;
sel = Some(0);
starting_row = 0;
}
_ => {}
}
render.clear_preserve_prompt(&size_vec)?;
}
}
}
impl<'a> FuzzySelect<'a> {
/// Same as `new` but with a specific theme.
pub fn with_theme(theme: &'a dyn Theme) -> Self {
Self {
default: None,
items: vec![],
prompt: "".into(),
report: true,
clear: true,
highlight_matches: true,
max_length: None,
theme,
initial_text: "".into(),
}
}
}

691
vendor/dialoguer/src/prompts/input.rs vendored Normal file
View File

@ -0,0 +1,691 @@
use std::{cmp::Ordering, fmt::Debug, io, iter, str::FromStr};
#[cfg(feature = "completion")]
use crate::completion::Completion;
#[cfg(feature = "history")]
use crate::history::History;
use crate::{
theme::{SimpleTheme, TermThemeRenderer, Theme},
validate::Validator,
};
use console::{Key, Term};
type ValidatorCallback<'a, T> = Box<dyn FnMut(&T) -> Option<String> + 'a>;
/// Renders an input prompt.
///
/// ## Example usage
///
/// ```rust,no_run
/// use dialoguer::Input;
///
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// let input : String = Input::new()
/// .with_prompt("Tea or coffee?")
/// .with_initial_text("Yes")
/// .default("No".into())
/// .interact_text()?;
/// # Ok(())
/// # }
/// ```
/// It can also be used with turbofish notation:
///
/// ```rust,no_run
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// # use dialoguer::Input;
/// let input = Input::<String>::new()
/// .interact_text()?;
/// # Ok(())
/// # }
/// ```
pub struct Input<'a, T> {
prompt: String,
post_completion_text: Option<String>,
report: bool,
default: Option<T>,
show_default: bool,
initial_text: Option<String>,
theme: &'a dyn Theme,
permit_empty: bool,
validator: Option<ValidatorCallback<'a, T>>,
#[cfg(feature = "history")]
history: Option<&'a mut dyn History<T>>,
#[cfg(feature = "completion")]
completion: Option<&'a dyn Completion>,
}
impl<T> Default for Input<'static, T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Input<'_, T> {
/// Creates an input prompt.
pub fn new() -> Self {
Self::with_theme(&SimpleTheme)
}
/// Sets the input prompt.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
self.prompt = prompt.into();
self
}
/// Changes the prompt text to the post completion text after input is complete
pub fn with_post_completion_text<S: Into<String>>(
&mut self,
post_completion_text: S,
) -> &mut Self {
self.post_completion_text = Some(post_completion_text.into());
self
}
/// Indicates whether to report the input value after interaction.
///
/// The default is to report the input value.
pub fn report(&mut self, val: bool) -> &mut Self {
self.report = val;
self
}
/// Sets initial text that user can accept or erase.
pub fn with_initial_text<S: Into<String>>(&mut self, val: S) -> &mut Self {
self.initial_text = Some(val.into());
self
}
/// Sets a default.
///
/// Out of the box the prompt does not have a default and will continue
/// to display until the user inputs something and hits enter. If a default is set the user
/// can instead accept the default with enter.
pub fn default(&mut self, value: T) -> &mut Self {
self.default = Some(value);
self
}
/// Enables or disables an empty input
///
/// By default, if there is no default value set for the input, the user must input a non-empty string.
pub fn allow_empty(&mut self, val: bool) -> &mut Self {
self.permit_empty = val;
self
}
/// Disables or enables the default value display.
///
/// The default behaviour is to append [`default`](#method.default) to the prompt to tell the
/// user what is the default value.
///
/// This method does not affect existence of default value, only its display in the prompt!
pub fn show_default(&mut self, val: bool) -> &mut Self {
self.show_default = val;
self
}
}
impl<'a, T> Input<'a, T> {
/// Creates an input prompt with a specific theme.
pub fn with_theme(theme: &'a dyn Theme) -> Self {
Self {
prompt: "".into(),
post_completion_text: None,
report: true,
default: None,
show_default: true,
initial_text: None,
theme,
permit_empty: false,
validator: None,
#[cfg(feature = "history")]
history: None,
#[cfg(feature = "completion")]
completion: None,
}
}
/// Enable history processing
///
/// # Example
///
/// ```no_run
/// # use dialoguer::{History, Input};
/// # use std::{collections::VecDeque, fmt::Display};
/// let mut history = MyHistory::default();
/// loop {
/// if let Ok(input) = Input::<String>::new()
/// .with_prompt("hist")
/// .history_with(&mut history)
/// .interact_text()
/// {
/// // Do something with the input
/// }
/// }
/// # struct MyHistory {
/// # history: VecDeque<String>,
/// # }
/// #
/// # impl Default for MyHistory {
/// # fn default() -> Self {
/// # MyHistory {
/// # history: VecDeque::new(),
/// # }
/// # }
/// # }
/// #
/// # impl<T: ToString> History<T> for MyHistory {
/// # fn read(&self, pos: usize) -> Option<String> {
/// # self.history.get(pos).cloned()
/// # }
/// #
/// # fn write(&mut self, val: &T)
/// # where
/// # {
/// # self.history.push_front(val.to_string());
/// # }
/// # }
/// ```
#[cfg(feature = "history")]
pub fn history_with<H>(&mut self, history: &'a mut H) -> &mut Self
where
H: History<T>,
{
self.history = Some(history);
self
}
/// Enable completion
#[cfg(feature = "completion")]
pub fn completion_with<C>(&mut self, completion: &'a C) -> &mut Self
where
C: Completion,
{
self.completion = Some(completion);
self
}
}
impl<'a, T> Input<'a, T>
where
T: 'a,
{
/// Registers a validator.
///
/// # Example
///
/// ```no_run
/// # use dialoguer::Input;
/// let mail: String = Input::new()
/// .with_prompt("Enter email")
/// .validate_with(|input: &String| -> Result<(), &str> {
/// if input.contains('@') {
/// Ok(())
/// } else {
/// Err("This is not a mail address")
/// }
/// })
/// .interact()
/// .unwrap();
/// ```
pub fn validate_with<V>(&mut self, mut validator: V) -> &mut Self
where
V: Validator<T> + 'a,
V::Err: ToString,
{
let mut old_validator_func = self.validator.take();
self.validator = Some(Box::new(move |value: &T| -> Option<String> {
if let Some(old) = old_validator_func.as_mut() {
if let Some(err) = old(value) {
return Some(err);
}
}
match validator.validate(value) {
Ok(()) => None,
Err(err) => Some(err.to_string()),
}
}));
self
}
}
impl<T> Input<'_, T>
where
T: Clone + ToString + FromStr,
<T as FromStr>::Err: Debug + ToString,
{
/// Enables the user to enter a printable ascii sequence and returns the result.
///
/// Its difference from [`interact`](#method.interact) is that it only allows ascii characters for string,
/// while [`interact`](#method.interact) allows virtually any character to be used e.g arrow keys.
///
/// The dialog is rendered on stderr.
pub fn interact_text(&mut self) -> io::Result<T> {
self.interact_text_on(&Term::stderr())
}
/// Like [`interact_text`](#method.interact_text) but allows a specific terminal to be set.
pub fn interact_text_on(&mut self, term: &Term) -> io::Result<T> {
let mut render = TermThemeRenderer::new(term, self.theme);
loop {
let default_string = self.default.as_ref().map(ToString::to_string);
let prompt_len = render.input_prompt(
&self.prompt,
if self.show_default {
default_string.as_deref()
} else {
None
},
)?;
// Read input by keystroke so that we can suppress ascii control characters
if !term.features().is_attended() {
return Ok("".to_owned().parse::<T>().unwrap());
}
let mut chars: Vec<char> = Vec::new();
let mut position = 0;
#[cfg(feature = "history")]
let mut hist_pos = 0;
if let Some(initial) = self.initial_text.as_ref() {
term.write_str(initial)?;
chars = initial.chars().collect();
position = chars.len();
}
term.flush()?;
loop {
match term.read_key()? {
Key::Backspace if position > 0 => {
position -= 1;
chars.remove(position);
let line_size = term.size().1 as usize;
// Case we want to delete last char of a line so the cursor is at the beginning of the next line
if (position + prompt_len) % (line_size - 1) == 0 {
term.clear_line()?;
term.move_cursor_up(1)?;
term.move_cursor_right(line_size + 1)?;
} else {
term.clear_chars(1)?;
}
let tail: String = chars[position..].iter().collect();
if !tail.is_empty() {
term.write_str(&tail)?;
let total = position + prompt_len + tail.len();
let total_line = total / line_size;
let line_cursor = (position + prompt_len) / line_size;
term.move_cursor_up(total_line - line_cursor)?;
term.move_cursor_left(line_size)?;
term.move_cursor_right((position + prompt_len) % line_size)?;
}
term.flush()?;
}
Key::Char(chr) if !chr.is_ascii_control() => {
chars.insert(position, chr);
position += 1;
let tail: String =
iter::once(&chr).chain(chars[position..].iter()).collect();
term.write_str(&tail)?;
term.move_cursor_left(tail.len() - 1)?;
term.flush()?;
}
Key::ArrowLeft if position > 0 => {
if (position + prompt_len) % term.size().1 as usize == 0 {
term.move_cursor_up(1)?;
term.move_cursor_right(term.size().1 as usize)?;
} else {
term.move_cursor_left(1)?;
}
position -= 1;
term.flush()?;
}
Key::ArrowRight if position < chars.len() => {
if (position + prompt_len) % (term.size().1 as usize - 1) == 0 {
term.move_cursor_down(1)?;
term.move_cursor_left(term.size().1 as usize)?;
} else {
term.move_cursor_right(1)?;
}
position += 1;
term.flush()?;
}
Key::UnknownEscSeq(seq) if seq == vec!['b'] => {
let line_size = term.size().1 as usize;
let nb_space = chars[..position]
.iter()
.rev()
.take_while(|c| c.is_whitespace())
.count();
let find_last_space = chars[..position - nb_space]
.iter()
.rposition(|c| c.is_whitespace());
// If we find a space we set the cursor to the next char else we set it to the beginning of the input
if let Some(mut last_space) = find_last_space {
if last_space < position {
last_space += 1;
let new_line = (prompt_len + last_space) / line_size;
let old_line = (prompt_len + position) / line_size;
let diff_line = old_line - new_line;
if diff_line != 0 {
term.move_cursor_up(old_line - new_line)?;
}
let new_pos_x = (prompt_len + last_space) % line_size;
let old_pos_x = (prompt_len + position) % line_size;
let diff_pos_x = new_pos_x as i64 - old_pos_x as i64;
//println!("new_pos_x = {}, old_pos_x = {}, diff = {}", new_pos_x, old_pos_x, diff_pos_x);
if diff_pos_x < 0 {
term.move_cursor_left(-diff_pos_x as usize)?;
} else {
term.move_cursor_right((diff_pos_x) as usize)?;
}
position = last_space;
}
} else {
term.move_cursor_left(position)?;
position = 0;
}
term.flush()?;
}
Key::UnknownEscSeq(seq) if seq == vec!['f'] => {
let line_size = term.size().1 as usize;
let find_next_space =
chars[position..].iter().position(|c| c.is_whitespace());
// If we find a space we set the cursor to the next char else we set it to the beginning of the input
if let Some(mut next_space) = find_next_space {
let nb_space = chars[position + next_space..]
.iter()
.take_while(|c| c.is_whitespace())
.count();
next_space += nb_space;
let new_line = (prompt_len + position + next_space) / line_size;
let old_line = (prompt_len + position) / line_size;
term.move_cursor_down(new_line - old_line)?;
let new_pos_x = (prompt_len + position + next_space) % line_size;
let old_pos_x = (prompt_len + position) % line_size;
let diff_pos_x = new_pos_x as i64 - old_pos_x as i64;
if diff_pos_x < 0 {
term.move_cursor_left(-diff_pos_x as usize)?;
} else {
term.move_cursor_right((diff_pos_x) as usize)?;
}
position += next_space;
} else {
let new_line = (prompt_len + chars.len()) / line_size;
let old_line = (prompt_len + position) / line_size;
term.move_cursor_down(new_line - old_line)?;
let new_pos_x = (prompt_len + chars.len()) % line_size;
let old_pos_x = (prompt_len + position) % line_size;
let diff_pos_x = new_pos_x as i64 - old_pos_x as i64;
match diff_pos_x.cmp(&0) {
Ordering::Less => {
term.move_cursor_left((-diff_pos_x - 1) as usize)?;
}
Ordering::Equal => {}
Ordering::Greater => {
term.move_cursor_right((diff_pos_x) as usize)?;
}
}
position = chars.len();
}
term.flush()?;
}
#[cfg(feature = "completion")]
Key::ArrowRight | Key::Tab => {
if let Some(completion) = &self.completion {
let input: String = chars.clone().into_iter().collect();
if let Some(x) = completion.get(&input) {
term.clear_chars(chars.len())?;
chars.clear();
position = 0;
for ch in x.chars() {
chars.insert(position, ch);
position += 1;
}
term.write_str(&x)?;
term.flush()?;
}
}
}
#[cfg(feature = "history")]
Key::ArrowUp => {
let line_size = term.size().1 as usize;
if let Some(history) = &self.history {
if let Some(previous) = history.read(hist_pos) {
hist_pos += 1;
let mut chars_len = chars.len();
while ((prompt_len + chars_len) / line_size) > 0 {
term.clear_chars(chars_len)?;
if (prompt_len + chars_len) % line_size == 0 {
chars_len -= std::cmp::min(chars_len, line_size);
} else {
chars_len -= std::cmp::min(
chars_len,
(prompt_len + chars_len + 1) % line_size,
);
}
if chars_len > 0 {
term.move_cursor_up(1)?;
term.move_cursor_right(line_size)?;
}
}
term.clear_chars(chars_len)?;
chars.clear();
position = 0;
for ch in previous.chars() {
chars.insert(position, ch);
position += 1;
}
term.write_str(&previous)?;
term.flush()?;
}
}
}
#[cfg(feature = "history")]
Key::ArrowDown => {
let line_size = term.size().1 as usize;
if let Some(history) = &self.history {
let mut chars_len = chars.len();
while ((prompt_len + chars_len) / line_size) > 0 {
term.clear_chars(chars_len)?;
if (prompt_len + chars_len) % line_size == 0 {
chars_len -= std::cmp::min(chars_len, line_size);
} else {
chars_len -= std::cmp::min(
chars_len,
(prompt_len + chars_len + 1) % line_size,
);
}
if chars_len > 0 {
term.move_cursor_up(1)?;
term.move_cursor_right(line_size)?;
}
}
term.clear_chars(chars_len)?;
chars.clear();
position = 0;
// Move the history position back one in case we have up arrowed into it
// and the position is sitting on the next to read
if let Some(pos) = hist_pos.checked_sub(1) {
hist_pos = pos;
// Move it back again to get the previous history entry
if let Some(pos) = pos.checked_sub(1) {
if let Some(previous) = history.read(pos) {
for ch in previous.chars() {
chars.insert(position, ch);
position += 1;
}
term.write_str(&previous)?;
}
}
}
term.flush()?;
}
}
Key::Enter => break,
_ => (),
}
}
let input = chars.iter().collect::<String>();
term.clear_line()?;
render.clear()?;
if chars.is_empty() {
if let Some(ref default) = self.default {
if let Some(ref mut validator) = self.validator {
if let Some(err) = validator(default) {
render.error(&err)?;
continue;
}
}
if self.report {
render.input_prompt_selection(&self.prompt, &default.to_string())?;
}
term.flush()?;
return Ok(default.clone());
} else if !self.permit_empty {
continue;
}
}
match input.parse::<T>() {
Ok(value) => {
if let Some(ref mut validator) = self.validator {
if let Some(err) = validator(&value) {
render.error(&err)?;
continue;
}
}
#[cfg(feature = "history")]
if let Some(history) = &mut self.history {
history.write(&value);
}
if self.report {
if let Some(post_completion_text) = &self.post_completion_text {
render.input_prompt_selection(post_completion_text, &input)?;
} else {
render.input_prompt_selection(&self.prompt, &input)?;
}
}
term.flush()?;
return Ok(value);
}
Err(err) => {
render.error(&err.to_string())?;
continue;
}
}
}
}
}
impl<T> Input<'_, T>
where
T: Clone + ToString + FromStr,
<T as FromStr>::Err: ToString,
{
/// Enables user interaction and returns the result.
///
/// Allows any characters as input, including e.g arrow keys.
/// Some of the keys might have undesired behavior.
/// For more limited version, see [`interact_text`](#method.interact_text).
///
/// If the user confirms the result is `true`, `false` otherwise.
/// The dialog is rendered on stderr.
pub fn interact(&mut self) -> io::Result<T> {
self.interact_on(&Term::stderr())
}
/// Like [`interact`](#method.interact) but allows a specific terminal to be set.
pub fn interact_on(&mut self, term: &Term) -> io::Result<T> {
let mut render = TermThemeRenderer::new(term, self.theme);
loop {
let default_string = self.default.as_ref().map(ToString::to_string);
render.input_prompt(
&self.prompt,
if self.show_default {
default_string.as_deref()
} else {
None
},
)?;
term.flush()?;
let input = if let Some(initial_text) = self.initial_text.as_ref() {
term.read_line_initial_text(initial_text)?
} else {
term.read_line()?
};
render.add_line();
term.clear_line()?;
render.clear()?;
if input.is_empty() {
if let Some(ref default) = self.default {
if let Some(ref mut validator) = self.validator {
if let Some(err) = validator(default) {
render.error(&err)?;
continue;
}
}
if self.report {
render.input_prompt_selection(&self.prompt, &default.to_string())?;
}
term.flush()?;
return Ok(default.clone());
} else if !self.permit_empty {
continue;
}
}
match input.parse::<T>() {
Ok(value) => {
if let Some(ref mut validator) = self.validator {
if let Some(err) = validator(&value) {
render.error(&err)?;
continue;
}
}
if self.report {
render.input_prompt_selection(&self.prompt, &input)?;
}
term.flush()?;
return Ok(value);
}
Err(err) => {
render.error(&err.to_string())?;
continue;
}
}
}
}
}

13
vendor/dialoguer/src/prompts/mod.rs vendored Normal file
View File

@ -0,0 +1,13 @@
#![allow(clippy::needless_doctest_main)]
pub mod confirm;
pub mod input;
pub mod multi_select;
pub mod select;
pub mod sort;
#[cfg(feature = "fuzzy-select")]
pub mod fuzzy_select;
#[cfg(feature = "password")]
pub mod password;

View File

@ -0,0 +1,356 @@
use std::{io, iter::repeat, ops::Rem};
use crate::{
theme::{SimpleTheme, TermThemeRenderer, Theme},
Paging,
};
use console::{Key, Term};
/// Renders a multi select prompt.
///
/// ## Example usage
/// ```rust,no_run
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// use dialoguer::MultiSelect;
///
/// let items = vec!["Option 1", "Option 2"];
/// let chosen : Vec<usize> = MultiSelect::new()
/// .items(&items)
/// .interact()?;
/// # Ok(())
/// # }
/// ```
pub struct MultiSelect<'a> {
defaults: Vec<bool>,
items: Vec<String>,
prompt: Option<String>,
report: bool,
clear: bool,
max_length: Option<usize>,
theme: &'a dyn Theme,
}
impl Default for MultiSelect<'static> {
fn default() -> Self {
Self::new()
}
}
impl MultiSelect<'static> {
/// Creates a multi select prompt.
pub fn new() -> Self {
Self::with_theme(&SimpleTheme)
}
}
impl MultiSelect<'_> {
/// Sets the clear behavior of the menu.
///
/// The default is to clear the menu.
pub fn clear(&mut self, val: bool) -> &mut Self {
self.clear = val;
self
}
/// Sets a defaults for the menu.
pub fn defaults(&mut self, val: &[bool]) -> &mut Self {
self.defaults = val
.to_vec()
.iter()
.copied()
.chain(repeat(false))
.take(self.items.len())
.collect();
self
}
/// Sets an optional max length for a page
///
/// Max length is disabled by None
pub fn max_length(&mut self, val: usize) -> &mut Self {
// Paging subtracts two from the capacity, paging does this to
// make an offset for the page indicator. So to make sure that
// we can show the intended amount of items we need to add two
// to our value.
self.max_length = Some(val + 2);
self
}
/// Add a single item to the selector.
#[inline]
pub fn item<T: ToString>(&mut self, item: T) -> &mut Self {
self.item_checked(item, false)
}
/// Add a single item to the selector with a default checked state.
pub fn item_checked<T: ToString>(&mut self, item: T, checked: bool) -> &mut Self {
self.items.push(item.to_string());
self.defaults.push(checked);
self
}
/// Adds multiple items to the selector.
pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Self {
for item in items {
self.items.push(item.to_string());
self.defaults.push(false);
}
self
}
/// Adds multiple items to the selector with checked state
pub fn items_checked<T: ToString>(&mut self, items: &[(T, bool)]) -> &mut Self {
for &(ref item, checked) in items {
self.items.push(item.to_string());
self.defaults.push(checked);
}
self
}
/// Prefaces the menu with a prompt.
///
/// By default, when a prompt is set the system also prints out a confirmation after
/// the selection. You can opt-out of this with [`report`](#method.report).
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
self.prompt = Some(prompt.into());
self
}
/// Indicates whether to report the selected values after interaction.
///
/// The default is to report the selections.
pub fn report(&mut self, val: bool) -> &mut Self {
self.report = val;
self
}
/// Enables user interaction and returns the result.
///
/// The user can select the items with the 'Space' bar and on 'Enter' the indices of selected items will be returned.
/// The dialog is rendered on stderr.
/// Result contains `Vec<index>` if user hit 'Enter'.
/// This unlike [`interact_opt`](Self::interact_opt) does not allow to quit with 'Esc' or 'q'.
#[inline]
pub fn interact(&self) -> io::Result<Vec<usize>> {
self.interact_on(&Term::stderr())
}
/// Enables user interaction and returns the result.
///
/// The user can select the items with the 'Space' bar and on 'Enter' the indices of selected items will be returned.
/// The dialog is rendered on stderr.
/// Result contains `Some(Vec<index>)` if user hit 'Enter' or `None` if user cancelled with 'Esc' or 'q'.
#[inline]
pub fn interact_opt(&self) -> io::Result<Option<Vec<usize>>> {
self.interact_on_opt(&Term::stderr())
}
/// Like [interact](#method.interact) but allows a specific terminal to be set.
///
/// ## Examples
///```rust,no_run
/// use dialoguer::MultiSelect;
/// use console::Term;
///
/// fn main() -> std::io::Result<()> {
/// let selections = MultiSelect::new()
/// .item("Option A")
/// .item("Option B")
/// .interact_on(&Term::stderr())?;
///
/// println!("User selected options at indices {:?}", selections);
///
/// Ok(())
/// }
///```
#[inline]
pub fn interact_on(&self, term: &Term) -> io::Result<Vec<usize>> {
self._interact_on(term, false)?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))
}
/// Like [`interact_opt`](Self::interact_opt) but allows a specific terminal to be set.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::MultiSelect;
/// use console::Term;
///
/// fn main() -> std::io::Result<()> {
/// let selections = MultiSelect::new()
/// .item("Option A")
/// .item("Option B")
/// .interact_on_opt(&Term::stdout())?;
///
/// match selections {
/// Some(positions) => println!("User selected options at indices {:?}", positions),
/// None => println!("User exited using Esc or q")
/// }
///
/// Ok(())
/// }
/// ```
#[inline]
pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<Vec<usize>>> {
self._interact_on(term, true)
}
fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<Vec<usize>>> {
if self.items.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"Empty list of items given to `MultiSelect`",
));
}
let mut paging = Paging::new(term, self.items.len(), self.max_length);
let mut render = TermThemeRenderer::new(term, self.theme);
let mut sel = 0;
let mut size_vec = Vec::new();
for items in self
.items
.iter()
.flat_map(|i| i.split('\n'))
.collect::<Vec<_>>()
{
let size = &items.len();
size_vec.push(*size);
}
let mut checked: Vec<bool> = self.defaults.clone();
term.hide_cursor()?;
loop {
if let Some(ref prompt) = self.prompt {
paging
.render_prompt(|paging_info| render.multi_select_prompt(prompt, paging_info))?;
}
for (idx, item) in self
.items
.iter()
.enumerate()
.skip(paging.current_page * paging.capacity)
.take(paging.capacity)
{
render.multi_select_prompt_item(item, checked[idx], sel == idx)?;
}
term.flush()?;
match term.read_key()? {
Key::ArrowDown | Key::Tab | Key::Char('j') => {
if sel == !0 {
sel = 0;
} else {
sel = (sel as u64 + 1).rem(self.items.len() as u64) as usize;
}
}
Key::ArrowUp | Key::BackTab | Key::Char('k') => {
if sel == !0 {
sel = self.items.len() - 1;
} else {
sel = ((sel as i64 - 1 + self.items.len() as i64)
% (self.items.len() as i64)) as usize;
}
}
Key::ArrowLeft | Key::Char('h') => {
if paging.active {
sel = paging.previous_page();
}
}
Key::ArrowRight | Key::Char('l') => {
if paging.active {
sel = paging.next_page();
}
}
Key::Char(' ') => {
checked[sel] = !checked[sel];
}
Key::Char('a') => {
if checked.iter().all(|&item_checked| item_checked) {
checked.fill(false);
} else {
checked.fill(true);
}
}
Key::Escape | Key::Char('q') => {
if allow_quit {
if self.clear {
render.clear()?;
} else {
term.clear_last_lines(paging.capacity)?;
}
term.show_cursor()?;
term.flush()?;
return Ok(None);
}
}
Key::Enter => {
if self.clear {
render.clear()?;
}
if let Some(ref prompt) = self.prompt {
if self.report {
let selections: Vec<_> = checked
.iter()
.enumerate()
.filter_map(|(idx, &checked)| {
if checked {
Some(self.items[idx].as_str())
} else {
None
}
})
.collect();
render.multi_select_prompt_selection(prompt, &selections[..])?;
}
}
term.show_cursor()?;
term.flush()?;
return Ok(Some(
checked
.into_iter()
.enumerate()
.filter_map(|(idx, checked)| if checked { Some(idx) } else { None })
.collect(),
));
}
_ => {}
}
paging.update(sel)?;
if paging.active {
render.clear()?;
} else {
render.clear_preserve_prompt(&size_vec)?;
}
}
}
}
impl<'a> MultiSelect<'a> {
/// Creates a multi select prompt with a specific theme.
pub fn with_theme(theme: &'a dyn Theme) -> Self {
Self {
items: vec![],
defaults: vec![],
clear: true,
prompt: None,
report: true,
max_length: None,
theme,
}
}
}

194
vendor/dialoguer/src/prompts/password.rs vendored Normal file
View File

@ -0,0 +1,194 @@
use std::io;
use crate::{
theme::{SimpleTheme, TermThemeRenderer, Theme},
validate::PasswordValidator,
};
use console::Term;
use zeroize::Zeroizing;
type PasswordValidatorCallback<'a> = Box<dyn Fn(&String) -> Option<String> + 'a>;
/// Renders a password input prompt.
///
/// ## Example usage
///
/// ```rust,no_run
/// # fn test() -> Result<(), Box<std::error::Error>> {
/// use dialoguer::Password;
///
/// let password = Password::new().with_prompt("New Password")
/// .with_confirmation("Confirm password", "Passwords mismatching")
/// .interact()?;
/// println!("Length of the password is: {}", password.len());
/// # Ok(()) } fn main() { test().unwrap(); }
/// ```
pub struct Password<'a> {
prompt: String,
report: bool,
theme: &'a dyn Theme,
allow_empty_password: bool,
confirmation_prompt: Option<(String, String)>,
validator: Option<PasswordValidatorCallback<'a>>,
}
impl Default for Password<'static> {
fn default() -> Password<'static> {
Self::new()
}
}
impl Password<'static> {
/// Creates a password input prompt.
pub fn new() -> Password<'static> {
Self::with_theme(&SimpleTheme)
}
}
impl<'a> Password<'a> {
/// Sets the password input prompt.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
self.prompt = prompt.into();
self
}
/// Indicates whether to report confirmation after interaction.
///
/// The default is to report.
pub fn report(&mut self, val: bool) -> &mut Self {
self.report = val;
self
}
/// Enables confirmation prompting.
pub fn with_confirmation<A, B>(&mut self, prompt: A, mismatch_err: B) -> &mut Self
where
A: Into<String>,
B: Into<String>,
{
self.confirmation_prompt = Some((prompt.into(), mismatch_err.into()));
self
}
/// Allows/Disables empty password.
///
/// By default this setting is set to false (i.e. password is not empty).
pub fn allow_empty_password(&mut self, allow_empty_password: bool) -> &mut Self {
self.allow_empty_password = allow_empty_password;
self
}
/// Registers a validator.
///
/// # Example
///
/// ```no_run
/// # use dialoguer::Password;
/// let password: String = Password::new()
/// .with_prompt("Enter password")
/// .validate_with(|input: &String| -> Result<(), &str> {
/// if input.len() > 8 {
/// Ok(())
/// } else {
/// Err("Password must be longer than 8")
/// }
/// })
/// .interact()
/// .unwrap();
/// ```
pub fn validate_with<V>(&mut self, validator: V) -> &mut Self
where
V: PasswordValidator + 'a,
V::Err: ToString,
{
let old_validator_func = self.validator.take();
self.validator = Some(Box::new(move |value: &String| -> Option<String> {
if let Some(old) = &old_validator_func {
if let Some(err) = old(value) {
return Some(err);
}
}
match validator.validate(value) {
Ok(()) => None,
Err(err) => Some(err.to_string()),
}
}));
self
}
/// Enables user interaction and returns the result.
///
/// If the user confirms the result is `true`, `false` otherwise.
/// The dialog is rendered on stderr.
pub fn interact(&self) -> io::Result<String> {
self.interact_on(&Term::stderr())
}
/// Like `interact` but allows a specific terminal to be set.
pub fn interact_on(&self, term: &Term) -> io::Result<String> {
let mut render = TermThemeRenderer::new(term, self.theme);
render.set_prompts_reset_height(false);
loop {
let password = Zeroizing::new(self.prompt_password(&mut render, &self.prompt)?);
if let Some(ref validator) = self.validator {
if let Some(err) = validator(&password) {
render.error(&err)?;
continue;
}
}
if let Some((ref prompt, ref err)) = self.confirmation_prompt {
let pw2 = Zeroizing::new(self.prompt_password(&mut render, prompt)?);
if *password != *pw2 {
render.error(err)?;
continue;
}
}
render.clear()?;
if self.report {
render.password_prompt_selection(&self.prompt)?;
}
term.flush()?;
return Ok((*password).clone());
}
}
fn prompt_password(&self, render: &mut TermThemeRenderer, prompt: &str) -> io::Result<String> {
loop {
render.password_prompt(prompt)?;
render.term().flush()?;
let input = render.term().read_secure_line()?;
render.add_line();
if !input.is_empty() || self.allow_empty_password {
return Ok(input);
}
}
}
}
impl<'a> Password<'a> {
/// Creates a password input prompt with a specific theme.
pub fn with_theme(theme: &'a dyn Theme) -> Self {
Self {
prompt: "".into(),
report: true,
theme,
allow_empty_password: false,
confirmation_prompt: None,
validator: None,
}
}
}

419
vendor/dialoguer/src/prompts/select.rs vendored Normal file
View File

@ -0,0 +1,419 @@
use std::{io, ops::Rem};
use crate::paging::Paging;
use crate::theme::{SimpleTheme, TermThemeRenderer, Theme};
use console::{Key, Term};
/// Renders a select prompt.
///
/// User can select from one or more options.
/// Interaction returns index of an item selected in the order they appear in `item` invocation or `items` slice.
///
/// ## Examples
///
/// ```rust,no_run
/// use dialoguer::{console::Term, theme::ColorfulTheme, Select};
///
/// fn main() -> std::io::Result<()> {
/// let items = vec!["Item 1", "item 2"];
/// let selection = Select::with_theme(&ColorfulTheme::default())
/// .items(&items)
/// .default(0)
/// .interact_on_opt(&Term::stderr())?;
///
/// match selection {
/// Some(index) => println!("User selected item : {}", items[index]),
/// None => println!("User did not select anything")
/// }
///
/// Ok(())
/// }
/// ```
pub struct Select<'a> {
default: usize,
items: Vec<String>,
prompt: Option<String>,
report: bool,
clear: bool,
theme: &'a dyn Theme,
max_length: Option<usize>,
}
impl Default for Select<'static> {
fn default() -> Self {
Self::new()
}
}
impl Select<'static> {
/// Creates a select prompt builder with default theme.
pub fn new() -> Self {
Self::with_theme(&SimpleTheme)
}
}
impl Select<'_> {
/// Indicates whether select menu should be erased from the screen after interaction.
///
/// The default is to clear the menu.
pub fn clear(&mut self, val: bool) -> &mut Self {
self.clear = val;
self
}
/// Sets initial selected element when select menu is rendered
///
/// Element is indicated by the index at which it appears in `item` method invocation or `items` slice.
pub fn default(&mut self, val: usize) -> &mut Self {
self.default = val;
self
}
/// Sets an optional max length for a page.
///
/// Max length is disabled by None
pub fn max_length(&mut self, val: usize) -> &mut Self {
// Paging subtracts two from the capacity, paging does this to
// make an offset for the page indicator. So to make sure that
// we can show the intended amount of items we need to add two
// to our value.
self.max_length = Some(val + 2);
self
}
/// Add a single item to the selector.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::Select;
///
/// fn main() -> std::io::Result<()> {
/// let selection: usize = Select::new()
/// .item("Item 1")
/// .item("Item 2")
/// .interact()?;
///
/// Ok(())
/// }
/// ```
pub fn item<T: ToString>(&mut self, item: T) -> &mut Self {
self.items.push(item.to_string());
self
}
/// Adds multiple items to the selector.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::Select;
///
/// fn main() -> std::io::Result<()> {
/// let items = vec!["Item 1", "Item 2"];
/// let selection: usize = Select::new()
/// .items(&items)
/// .interact()?;
///
/// println!("{}", items[selection]);
///
/// Ok(())
/// }
/// ```
pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Self {
for item in items {
self.items.push(item.to_string());
}
self
}
/// Sets the select prompt.
///
/// By default, when a prompt is set the system also prints out a confirmation after
/// the selection. You can opt-out of this with [`report`](#method.report).
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::Select;
///
/// fn main() -> std::io::Result<()> {
/// let selection = Select::new()
/// .with_prompt("Which option do you prefer?")
/// .item("Option A")
/// .item("Option B")
/// .interact()?;
///
/// Ok(())
/// }
/// ```
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
self.prompt = Some(prompt.into());
self.report = true;
self
}
/// Indicates whether to report the selected value after interaction.
///
/// The default is to report the selection.
pub fn report(&mut self, val: bool) -> &mut Self {
self.report = val;
self
}
/// Enables user interaction and returns the result.
///
/// The user can select the items with the 'Space' bar or 'Enter' and the index of selected item will be returned.
/// The dialog is rendered on stderr.
/// Result contains `index` if user selected one of items using 'Enter'.
/// This unlike [`interact_opt`](Self::interact_opt) does not allow to quit with 'Esc' or 'q'.
#[inline]
pub fn interact(&self) -> io::Result<usize> {
self.interact_on(&Term::stderr())
}
/// Enables user interaction and returns the result.
///
/// The user can select the items with the 'Space' bar or 'Enter' and the index of selected item will be returned.
/// The dialog is rendered on stderr.
/// Result contains `Some(index)` if user selected one of items using 'Enter' or `None` if user cancelled with 'Esc' or 'q'.
#[inline]
pub fn interact_opt(&self) -> io::Result<Option<usize>> {
self.interact_on_opt(&Term::stderr())
}
/// Like [interact](#method.interact) but allows a specific terminal to be set.
///
/// ## Examples
///```rust,no_run
/// use dialoguer::{console::Term, Select};
///
/// fn main() -> std::io::Result<()> {
/// let selection = Select::new()
/// .item("Option A")
/// .item("Option B")
/// .interact_on(&Term::stderr())?;
///
/// println!("User selected option at index {}", selection);
///
/// Ok(())
/// }
///```
#[inline]
pub fn interact_on(&self, term: &Term) -> io::Result<usize> {
self._interact_on(term, false)?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))
}
/// Like [`interact_opt`](Self::interact_opt) but allows a specific terminal to be set.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::{console::Term, Select};
///
/// fn main() -> std::io::Result<()> {
/// let selection = Select::new()
/// .item("Option A")
/// .item("Option B")
/// .interact_on_opt(&Term::stdout())?;
///
/// match selection {
/// Some(position) => println!("User selected option at index {}", position),
/// None => println!("User did not select anything or exited using Esc or q")
/// }
///
/// Ok(())
/// }
/// ```
#[inline]
pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<usize>> {
self._interact_on(term, true)
}
/// Like `interact` but allows a specific terminal to be set.
fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<usize>> {
if self.items.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"Empty list of items given to `Select`",
));
}
let mut paging = Paging::new(term, self.items.len(), self.max_length);
let mut render = TermThemeRenderer::new(term, self.theme);
let mut sel = self.default;
let mut size_vec = Vec::new();
for items in self
.items
.iter()
.flat_map(|i| i.split('\n'))
.collect::<Vec<_>>()
{
let size = &items.len();
size_vec.push(*size);
}
term.hide_cursor()?;
loop {
if let Some(ref prompt) = self.prompt {
paging.render_prompt(|paging_info| render.select_prompt(prompt, paging_info))?;
}
for (idx, item) in self
.items
.iter()
.enumerate()
.skip(paging.current_page * paging.capacity)
.take(paging.capacity)
{
render.select_prompt_item(item, sel == idx)?;
}
term.flush()?;
match term.read_key()? {
Key::ArrowDown | Key::Tab | Key::Char('j') => {
if sel == !0 {
sel = 0;
} else {
sel = (sel as u64 + 1).rem(self.items.len() as u64) as usize;
}
}
Key::Escape | Key::Char('q') => {
if allow_quit {
if self.clear {
render.clear()?;
} else {
term.clear_last_lines(paging.capacity)?;
}
term.show_cursor()?;
term.flush()?;
return Ok(None);
}
}
Key::ArrowUp | Key::BackTab | Key::Char('k') => {
if sel == !0 {
sel = self.items.len() - 1;
} else {
sel = ((sel as i64 - 1 + self.items.len() as i64)
% (self.items.len() as i64)) as usize;
}
}
Key::ArrowLeft | Key::Char('h') => {
if paging.active {
sel = paging.previous_page();
}
}
Key::ArrowRight | Key::Char('l') => {
if paging.active {
sel = paging.next_page();
}
}
Key::Enter | Key::Char(' ') if sel != !0 => {
if self.clear {
render.clear()?;
}
if let Some(ref prompt) = self.prompt {
if self.report {
render.select_prompt_selection(prompt, &self.items[sel])?;
}
}
term.show_cursor()?;
term.flush()?;
return Ok(Some(sel));
}
_ => {}
}
paging.update(sel)?;
if paging.active {
render.clear()?;
} else {
render.clear_preserve_prompt(&size_vec)?;
}
}
}
}
impl<'a> Select<'a> {
/// Creates a select prompt builder with a specific theme.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::{
/// Select,
/// theme::ColorfulTheme
/// };
///
/// fn main() -> std::io::Result<()> {
/// let selection = Select::with_theme(&ColorfulTheme::default())
/// .item("Option A")
/// .item("Option B")
/// .interact()?;
///
/// Ok(())
/// }
/// ```
pub fn with_theme(theme: &'a dyn Theme) -> Self {
Self {
default: !0,
items: vec![],
prompt: None,
report: false,
clear: true,
max_length: None,
theme,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_str() {
let selections = &[
"Ice Cream",
"Vanilla Cupcake",
"Chocolate Muffin",
"A Pile of sweet, sweet mustard",
];
assert_eq!(
Select::new().default(0).items(&selections[..]).items,
selections
);
}
#[test]
fn test_string() {
let selections = vec!["a".to_string(), "b".to_string()];
assert_eq!(
Select::new().default(0).items(&selections[..]).items,
selections
);
}
#[test]
fn test_ref_str() {
let a = "a";
let b = "b";
let selections = &[a, b];
assert_eq!(
Select::new().default(0).items(&selections[..]).items,
selections
);
}
}

348
vendor/dialoguer/src/prompts/sort.rs vendored Normal file
View File

@ -0,0 +1,348 @@
use std::{io, ops::Rem};
use crate::{
theme::{SimpleTheme, TermThemeRenderer, Theme},
Paging,
};
use console::{Key, Term};
/// Renders a sort prompt.
///
/// Returns list of indices in original items list sorted according to user input.
///
/// ## Example usage
/// ```rust,no_run
/// use dialoguer::Sort;
///
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// let items_to_order = vec!["Item 1", "Item 2", "Item 3"];
/// let ordered = Sort::new()
/// .with_prompt("Order the items")
/// .items(&items_to_order)
/// .interact()?;
/// # Ok(())
/// # }
/// ```
pub struct Sort<'a> {
items: Vec<String>,
prompt: Option<String>,
report: bool,
clear: bool,
max_length: Option<usize>,
theme: &'a dyn Theme,
}
impl Default for Sort<'static> {
fn default() -> Self {
Self::new()
}
}
impl Sort<'static> {
/// Creates a sort prompt.
pub fn new() -> Self {
Self::with_theme(&SimpleTheme)
}
}
impl Sort<'_> {
/// Sets the clear behavior of the menu.
///
/// The default is to clear the menu after user interaction.
pub fn clear(&mut self, val: bool) -> &mut Self {
self.clear = val;
self
}
/// Sets an optional max length for a page
///
/// Max length is disabled by None
pub fn max_length(&mut self, val: usize) -> &mut Self {
// Paging subtracts two from the capacity, paging does this to
// make an offset for the page indicator. So to make sure that
// we can show the intended amount of items we need to add two
// to our value.
self.max_length = Some(val + 2);
self
}
/// Add a single item to the selector.
pub fn item<T: ToString>(&mut self, item: T) -> &mut Self {
self.items.push(item.to_string());
self
}
/// Adds multiple items to the selector.
pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Self {
for item in items {
self.items.push(item.to_string());
}
self
}
/// Prefaces the menu with a prompt.
///
/// By default, when a prompt is set the system also prints out a confirmation after
/// the selection. You can opt-out of this with [`report`](#method.report).
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
self.prompt = Some(prompt.into());
self
}
/// Indicates whether to report the selected order after interaction.
///
/// The default is to report the selected order.
pub fn report(&mut self, val: bool) -> &mut Self {
self.report = val;
self
}
/// Enables user interaction and returns the result.
///
/// The user can order the items with the 'Space' bar and the arrows. On 'Enter' ordered list of the incides of items will be returned.
/// The dialog is rendered on stderr.
/// Result contains `Vec<index>` if user hit 'Enter'.
/// This unlike [`interact_opt`](Self::interact_opt) does not allow to quit with 'Esc' or 'q'.
#[inline]
pub fn interact(&self) -> io::Result<Vec<usize>> {
self.interact_on(&Term::stderr())
}
/// Enables user interaction and returns the result.
///
/// The user can order the items with the 'Space' bar and the arrows. On 'Enter' ordered list of the incides of items will be returned.
/// The dialog is rendered on stderr.
/// Result contains `Some(Vec<index>)` if user hit 'Enter' or `None` if user cancelled with 'Esc' or 'q'.
#[inline]
pub fn interact_opt(&self) -> io::Result<Option<Vec<usize>>> {
self.interact_on_opt(&Term::stderr())
}
/// Like [interact](#method.interact) but allows a specific terminal to be set.
///
/// ## Examples
///```rust,no_run
/// use dialoguer::Sort;
/// use console::Term;
///
/// fn main() -> std::io::Result<()> {
/// let selections = Sort::new()
/// .item("Option A")
/// .item("Option B")
/// .interact_on(&Term::stderr())?;
///
/// println!("User sorted options as indices {:?}", selections);
///
/// Ok(())
/// }
///```
#[inline]
pub fn interact_on(&self, term: &Term) -> io::Result<Vec<usize>> {
self._interact_on(term, false)?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))
}
/// Like [`interact_opt`](Self::interact_opt) but allows a specific terminal to be set.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::Sort;
/// use console::Term;
///
/// fn main() -> std::io::Result<()> {
/// let selections = Sort::new()
/// .item("Option A")
/// .item("Option B")
/// .interact_on_opt(&Term::stdout())?;
///
/// match selections {
/// Some(positions) => println!("User sorted options as indices {:?}", positions),
/// None => println!("User exited using Esc or q")
/// }
///
/// Ok(())
/// }
/// ```
#[inline]
pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<Vec<usize>>> {
self._interact_on(term, true)
}
fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<Vec<usize>>> {
if self.items.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"Empty list of items given to `Sort`",
));
}
let mut paging = Paging::new(term, self.items.len(), self.max_length);
let mut render = TermThemeRenderer::new(term, self.theme);
let mut sel = 0;
let mut size_vec = Vec::new();
for items in self.items.iter().as_slice() {
let size = &items.len();
size_vec.push(*size);
}
let mut order: Vec<_> = (0..self.items.len()).collect();
let mut checked: bool = false;
term.hide_cursor()?;
loop {
if let Some(ref prompt) = self.prompt {
paging.render_prompt(|paging_info| render.sort_prompt(prompt, paging_info))?;
}
for (idx, item) in order
.iter()
.enumerate()
.skip(paging.current_page * paging.capacity)
.take(paging.capacity)
{
render.sort_prompt_item(&self.items[*item], checked, sel == idx)?;
}
term.flush()?;
match term.read_key()? {
Key::ArrowDown | Key::Tab | Key::Char('j') => {
let old_sel = sel;
if sel == !0 {
sel = 0;
} else {
sel = (sel as u64 + 1).rem(self.items.len() as u64) as usize;
}
if checked && old_sel != sel {
order.swap(old_sel, sel);
}
}
Key::ArrowUp | Key::BackTab | Key::Char('k') => {
let old_sel = sel;
if sel == !0 {
sel = self.items.len() - 1;
} else {
sel = ((sel as i64 - 1 + self.items.len() as i64)
% (self.items.len() as i64)) as usize;
}
if checked && old_sel != sel {
order.swap(old_sel, sel);
}
}
Key::ArrowLeft | Key::Char('h') => {
if paging.active {
let old_sel = sel;
let old_page = paging.current_page;
sel = paging.previous_page();
if checked {
let indexes: Vec<_> = if old_page == 0 {
let indexes1: Vec<_> = (0..=old_sel).rev().collect();
let indexes2: Vec<_> = (sel..self.items.len()).rev().collect();
[indexes1, indexes2].concat()
} else {
(sel..=old_sel).rev().collect()
};
for index in 0..(indexes.len() - 1) {
order.swap(indexes[index], indexes[index + 1]);
}
}
}
}
Key::ArrowRight | Key::Char('l') => {
if paging.active {
let old_sel = sel;
let old_page = paging.current_page;
sel = paging.next_page();
if checked {
let indexes: Vec<_> = if old_page == paging.pages - 1 {
let indexes1: Vec<_> = (old_sel..self.items.len()).collect();
let indexes2: Vec<_> = vec![0];
[indexes1, indexes2].concat()
} else {
(old_sel..=sel).collect()
};
for index in 0..(indexes.len() - 1) {
order.swap(indexes[index], indexes[index + 1]);
}
}
}
}
Key::Char(' ') => {
checked = !checked;
}
Key::Escape | Key::Char('q') => {
if allow_quit {
if self.clear {
render.clear()?;
} else {
term.clear_last_lines(paging.capacity)?;
}
term.show_cursor()?;
term.flush()?;
return Ok(None);
}
}
Key::Enter => {
if self.clear {
render.clear()?;
}
if let Some(ref prompt) = self.prompt {
if self.report {
let list: Vec<_> = order
.iter()
.enumerate()
.map(|(_, item)| self.items[*item].as_str())
.collect();
render.sort_prompt_selection(prompt, &list[..])?;
}
}
term.show_cursor()?;
term.flush()?;
return Ok(Some(order));
}
_ => {}
}
paging.update(sel)?;
if paging.active {
render.clear()?;
} else {
render.clear_preserve_prompt(&size_vec)?;
}
}
}
}
impl<'a> Sort<'a> {
/// Creates a sort prompt with a specific theme.
pub fn with_theme(theme: &'a dyn Theme) -> Self {
Self {
items: vec![],
clear: true,
prompt: None,
report: true,
max_length: None,
theme,
}
}
}

976
vendor/dialoguer/src/theme.rs vendored Normal file
View File

@ -0,0 +1,976 @@
//! Customizes the rendering of the elements.
use std::{fmt, io};
use console::{measure_text_width, style, Style, StyledObject, Term};
#[cfg(feature = "fuzzy-select")]
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
/// Implements a theme for dialoguer.
pub trait Theme {
/// Formats a prompt.
#[inline]
fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
write!(f, "{}:", prompt)
}
/// Formats out an error.
#[inline]
fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
write!(f, "error: {}", err)
}
/// Formats a confirm prompt.
fn format_confirm_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<bool>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(f, "{} ", &prompt)?;
}
match default {
None => write!(f, "[y/n] ")?,
Some(true) => write!(f, "[Y/n] ")?,
Some(false) => write!(f, "[y/N] ")?,
}
Ok(())
}
/// Formats a confirm prompt after selection.
fn format_confirm_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selection: Option<bool>,
) -> fmt::Result {
let selection = selection.map(|b| if b { "yes" } else { "no" });
match selection {
Some(selection) if prompt.is_empty() => {
write!(f, "{}", selection)
}
Some(selection) => {
write!(f, "{} {}", &prompt, selection)
}
None if prompt.is_empty() => Ok(()),
None => {
write!(f, "{}", &prompt)
}
}
}
/// Formats an input prompt.
fn format_input_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<&str>,
) -> fmt::Result {
match default {
Some(default) if prompt.is_empty() => write!(f, "[{}]: ", default),
Some(default) => write!(f, "{} [{}]: ", prompt, default),
None => write!(f, "{}: ", prompt),
}
}
/// Formats an input prompt after selection.
#[inline]
fn format_input_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
sel: &str,
) -> fmt::Result {
write!(f, "{}: {}", prompt, sel)
}
/// Formats a password prompt.
#[inline]
#[cfg(feature = "password")]
fn format_password_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
self.format_input_prompt(f, prompt, None)
}
/// Formats a password prompt after selection.
#[inline]
#[cfg(feature = "password")]
fn format_password_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
) -> fmt::Result {
self.format_input_prompt_selection(f, prompt, "[hidden]")
}
/// Formats a select prompt.
#[inline]
fn format_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
self.format_prompt(f, prompt)
}
/// Formats a select prompt after selection.
#[inline]
fn format_select_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
sel: &str,
) -> fmt::Result {
self.format_input_prompt_selection(f, prompt, sel)
}
/// Formats a multi select prompt.
#[inline]
fn format_multi_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
self.format_prompt(f, prompt)
}
/// Formats a sort prompt.
#[inline]
fn format_sort_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
self.format_prompt(f, prompt)
}
/// Formats a multi_select prompt after selection.
fn format_multi_select_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selections: &[&str],
) -> fmt::Result {
write!(f, "{}: ", prompt)?;
for (idx, sel) in selections.iter().enumerate() {
write!(f, "{}{}", if idx == 0 { "" } else { ", " }, sel)?;
}
Ok(())
}
/// Formats a sort prompt after selection.
#[inline]
fn format_sort_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selections: &[&str],
) -> fmt::Result {
self.format_multi_select_prompt_selection(f, prompt, selections)
}
/// Formats a select prompt item.
fn format_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
active: bool,
) -> fmt::Result {
write!(f, "{} {}", if active { ">" } else { " " }, text)
}
/// Formats a multi select prompt item.
fn format_multi_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
checked: bool,
active: bool,
) -> fmt::Result {
write!(
f,
"{} {}",
match (checked, active) {
(true, true) => "> [x]",
(true, false) => " [x]",
(false, true) => "> [ ]",
(false, false) => " [ ]",
},
text
)
}
/// Formats a sort prompt item.
fn format_sort_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
picked: bool,
active: bool,
) -> fmt::Result {
write!(
f,
"{} {}",
match (picked, active) {
(true, true) => "> [x]",
(false, true) => "> [ ]",
(_, false) => " [ ]",
},
text
)
}
/// Formats a fuzzy select prompt item.
#[cfg(feature = "fuzzy-select")]
fn format_fuzzy_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
active: bool,
highlight_matches: bool,
matcher: &SkimMatcherV2,
search_term: &str,
) -> fmt::Result {
write!(f, "{} ", if active { ">" } else { " " })?;
if highlight_matches {
if let Some((_score, indices)) = matcher.fuzzy_indices(text, &search_term) {
for (idx, c) in text.chars().into_iter().enumerate() {
if indices.contains(&idx) {
write!(f, "{}", style(c).for_stderr().bold())?;
} else {
write!(f, "{}", c)?;
}
}
return Ok(());
}
}
write!(f, "{}", text)
}
/// Formats a fuzzy select prompt.
#[cfg(feature = "fuzzy-select")]
fn format_fuzzy_select_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
search_term: &str,
cursor_pos: usize,
) -> fmt::Result {
if !prompt.is_empty() {
write!(f, "{} ", prompt,)?;
}
if cursor_pos < search_term.len() {
let st_head = search_term[0..cursor_pos].to_string();
let st_tail = search_term[cursor_pos..search_term.len()].to_string();
let st_cursor = "|".to_string();
write!(f, "{}{}{}", st_head, st_cursor, st_tail)
} else {
let cursor = "|".to_string();
write!(f, "{}{}", search_term.to_string(), cursor)
}
}
}
/// The default theme.
pub struct SimpleTheme;
impl Theme for SimpleTheme {}
/// A colorful theme
pub struct ColorfulTheme {
/// The style for default values
pub defaults_style: Style,
/// The style for prompt
pub prompt_style: Style,
/// Prompt prefix value and style
pub prompt_prefix: StyledObject<String>,
/// Prompt suffix value and style
pub prompt_suffix: StyledObject<String>,
/// Prompt on success prefix value and style
pub success_prefix: StyledObject<String>,
/// Prompt on success suffix value and style
pub success_suffix: StyledObject<String>,
/// Error prefix value and style
pub error_prefix: StyledObject<String>,
/// The style for error message
pub error_style: Style,
/// The style for hints
pub hint_style: Style,
/// The style for values on prompt success
pub values_style: Style,
/// The style for active items
pub active_item_style: Style,
/// The style for inactive items
pub inactive_item_style: Style,
/// Active item in select prefix value and style
pub active_item_prefix: StyledObject<String>,
/// Inctive item in select prefix value and style
pub inactive_item_prefix: StyledObject<String>,
/// Checked item in multi select prefix value and style
pub checked_item_prefix: StyledObject<String>,
/// Unchecked item in multi select prefix value and style
pub unchecked_item_prefix: StyledObject<String>,
/// Picked item in sort prefix value and style
pub picked_item_prefix: StyledObject<String>,
/// Unpicked item in sort prefix value and style
pub unpicked_item_prefix: StyledObject<String>,
/// Formats the cursor for a fuzzy select prompt
#[cfg(feature = "fuzzy-select")]
pub fuzzy_cursor_style: Style,
// Formats the highlighting if matched characters
#[cfg(feature = "fuzzy-select")]
pub fuzzy_match_highlight_style: Style,
/// Show the selections from certain prompts inline
pub inline_selections: bool,
}
impl Default for ColorfulTheme {
fn default() -> ColorfulTheme {
ColorfulTheme {
defaults_style: Style::new().for_stderr().cyan(),
prompt_style: Style::new().for_stderr().bold(),
prompt_prefix: style("?".to_string()).for_stderr().yellow(),
prompt_suffix: style("".to_string()).for_stderr().black().bright(),
success_prefix: style("".to_string()).for_stderr().green(),
success_suffix: style("·".to_string()).for_stderr().black().bright(),
error_prefix: style("".to_string()).for_stderr().red(),
error_style: Style::new().for_stderr().red(),
hint_style: Style::new().for_stderr().black().bright(),
values_style: Style::new().for_stderr().green(),
active_item_style: Style::new().for_stderr().cyan(),
inactive_item_style: Style::new().for_stderr(),
active_item_prefix: style("".to_string()).for_stderr().green(),
inactive_item_prefix: style(" ".to_string()).for_stderr(),
checked_item_prefix: style("".to_string()).for_stderr().green(),
unchecked_item_prefix: style("".to_string()).for_stderr().black(),
picked_item_prefix: style("".to_string()).for_stderr().green(),
unpicked_item_prefix: style(" ".to_string()).for_stderr(),
#[cfg(feature = "fuzzy-select")]
fuzzy_cursor_style: Style::new().for_stderr().black().on_white(),
#[cfg(feature = "fuzzy-select")]
fuzzy_match_highlight_style: Style::new().for_stderr().bold(),
inline_selections: true,
}
}
}
impl Theme for ColorfulTheme {
/// Formats a prompt.
fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
write!(f, "{}", &self.prompt_suffix)
}
/// Formats an error
fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
write!(
f,
"{} {}",
&self.error_prefix,
self.error_style.apply_to(err)
)
}
/// Formats an input prompt.
fn format_input_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<&str>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
match default {
Some(default) => write!(
f,
"{} {} ",
self.hint_style.apply_to(&format!("({})", default)),
&self.prompt_suffix
),
None => write!(f, "{} ", &self.prompt_suffix),
}
}
/// Formats a confirm prompt.
fn format_confirm_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<bool>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
match default {
None => write!(
f,
"{} {}",
self.hint_style.apply_to("(y/n)"),
&self.prompt_suffix
),
Some(true) => write!(
f,
"{} {} {}",
self.hint_style.apply_to("(y/n)"),
&self.prompt_suffix,
self.defaults_style.apply_to("yes")
),
Some(false) => write!(
f,
"{} {} {}",
self.hint_style.apply_to("(y/n)"),
&self.prompt_suffix,
self.defaults_style.apply_to("no")
),
}
}
/// Formats a confirm prompt after selection.
fn format_confirm_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selection: Option<bool>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.success_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
let selection = selection.map(|b| if b { "yes" } else { "no" });
match selection {
Some(selection) => {
write!(
f,
"{} {}",
&self.success_suffix,
self.values_style.apply_to(selection)
)
}
None => {
write!(f, "{}", &self.success_suffix)
}
}
}
/// Formats an input prompt after selection.
fn format_input_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
sel: &str,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.success_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
write!(
f,
"{} {}",
&self.success_suffix,
self.values_style.apply_to(sel)
)
}
/// Formats a password prompt after selection.
#[cfg(feature = "password")]
fn format_password_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
) -> fmt::Result {
self.format_input_prompt_selection(f, prompt, "********")
}
/// Formats a multi select prompt after selection.
fn format_multi_select_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selections: &[&str],
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.success_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
write!(f, "{} ", &self.success_suffix)?;
if self.inline_selections {
for (idx, sel) in selections.iter().enumerate() {
write!(
f,
"{}{}",
if idx == 0 { "" } else { ", " },
self.values_style.apply_to(sel)
)?;
}
}
Ok(())
}
/// Formats a select prompt item.
fn format_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
active: bool,
) -> fmt::Result {
let details = if active {
(
&self.active_item_prefix,
self.active_item_style.apply_to(text),
)
} else {
(
&self.inactive_item_prefix,
self.inactive_item_style.apply_to(text),
)
};
write!(f, "{} {}", details.0, details.1)
}
/// Formats a multi select prompt item.
fn format_multi_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
checked: bool,
active: bool,
) -> fmt::Result {
let details = match (checked, active) {
(true, true) => (
&self.checked_item_prefix,
self.active_item_style.apply_to(text),
),
(true, false) => (
&self.checked_item_prefix,
self.inactive_item_style.apply_to(text),
),
(false, true) => (
&self.unchecked_item_prefix,
self.active_item_style.apply_to(text),
),
(false, false) => (
&self.unchecked_item_prefix,
self.inactive_item_style.apply_to(text),
),
};
write!(f, "{} {}", details.0, details.1)
}
/// Formats a sort prompt item.
fn format_sort_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
picked: bool,
active: bool,
) -> fmt::Result {
let details = match (picked, active) {
(true, true) => (
&self.picked_item_prefix,
self.active_item_style.apply_to(text),
),
(false, true) => (
&self.unpicked_item_prefix,
self.active_item_style.apply_to(text),
),
(_, false) => (
&self.unpicked_item_prefix,
self.inactive_item_style.apply_to(text),
),
};
write!(f, "{} {}", details.0, details.1)
}
/// Formats a fuzzy select prompt item.
#[cfg(feature = "fuzzy-select")]
fn format_fuzzy_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
active: bool,
highlight_matches: bool,
matcher: &SkimMatcherV2,
search_term: &str,
) -> fmt::Result {
write!(
f,
"{} ",
if active {
&self.active_item_prefix
} else {
&self.inactive_item_prefix
}
)?;
if highlight_matches {
if let Some((_score, indices)) = matcher.fuzzy_indices(text, &search_term) {
for (idx, c) in text.chars().into_iter().enumerate() {
if indices.contains(&idx) {
if active {
write!(
f,
"{}",
self.active_item_style
.apply_to(self.fuzzy_match_highlight_style.apply_to(c))
)?;
} else {
write!(f, "{}", self.fuzzy_match_highlight_style.apply_to(c))?;
}
} else {
if active {
write!(f, "{}", self.active_item_style.apply_to(c))?;
} else {
write!(f, "{}", c)?;
}
}
}
return Ok(());
}
}
write!(f, "{}", text)
}
/// Formats a fuzzy-selectprompt after selection.
#[cfg(feature = "fuzzy-select")]
fn format_fuzzy_select_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
search_term: &str,
cursor_pos: usize,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
if cursor_pos < search_term.len() {
let st_head = search_term[0..cursor_pos].to_string();
let st_tail = search_term[cursor_pos + 1..search_term.len()].to_string();
let st_cursor = self
.fuzzy_cursor_style
.apply_to(search_term.to_string().chars().nth(cursor_pos).unwrap());
write!(
f,
"{} {}{}{}",
&self.prompt_suffix, st_head, st_cursor, st_tail
)
} else {
let cursor = self.fuzzy_cursor_style.apply_to(" ");
write!(
f,
"{} {}{}",
&self.prompt_suffix,
search_term.to_string(),
cursor
)
}
}
}
/// Helper struct to conveniently render a theme of a term.
pub(crate) struct TermThemeRenderer<'a> {
term: &'a Term,
theme: &'a dyn Theme,
height: usize,
prompt_height: usize,
prompts_reset_height: bool,
}
impl<'a> TermThemeRenderer<'a> {
pub fn new(term: &'a Term, theme: &'a dyn Theme) -> TermThemeRenderer<'a> {
TermThemeRenderer {
term,
theme,
height: 0,
prompt_height: 0,
prompts_reset_height: true,
}
}
#[cfg(feature = "password")]
pub fn set_prompts_reset_height(&mut self, val: bool) {
self.prompts_reset_height = val;
}
#[cfg(feature = "password")]
pub fn term(&self) -> &Term {
self.term
}
pub fn add_line(&mut self) {
self.height += 1;
}
fn write_formatted_str<
F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
>(
&mut self,
f: F,
) -> io::Result<usize> {
let mut buf = String::new();
f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
self.height += buf.chars().filter(|&x| x == '\n').count();
self.term.write_str(&buf)?;
Ok(measure_text_width(&buf))
}
fn write_formatted_line<
F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
>(
&mut self,
f: F,
) -> io::Result<()> {
let mut buf = String::new();
f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
self.height += buf.chars().filter(|&x| x == '\n').count() + 1;
self.term.write_line(&buf)
}
fn write_formatted_prompt<
F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
>(
&mut self,
f: F,
) -> io::Result<()> {
self.write_formatted_line(f)?;
if self.prompts_reset_height {
self.prompt_height = self.height;
self.height = 0;
}
Ok(())
}
fn write_paging_info(buf: &mut dyn fmt::Write, paging_info: (usize, usize)) -> fmt::Result {
write!(buf, " [Page {}/{}] ", paging_info.0, paging_info.1)
}
pub fn error(&mut self, err: &str) -> io::Result<()> {
self.write_formatted_line(|this, buf| this.theme.format_error(buf, err))
}
pub fn confirm_prompt(&mut self, prompt: &str, default: Option<bool>) -> io::Result<usize> {
self.write_formatted_str(|this, buf| this.theme.format_confirm_prompt(buf, prompt, default))
}
pub fn confirm_prompt_selection(&mut self, prompt: &str, sel: Option<bool>) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_confirm_prompt_selection(buf, prompt, sel)
})
}
#[cfg(feature = "fuzzy-select")]
pub fn fuzzy_select_prompt(
&mut self,
prompt: &str,
search_term: &str,
cursor_pos: usize,
) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme
.format_fuzzy_select_prompt(buf, prompt, search_term, cursor_pos)
})
}
pub fn input_prompt(&mut self, prompt: &str, default: Option<&str>) -> io::Result<usize> {
self.write_formatted_str(|this, buf| this.theme.format_input_prompt(buf, prompt, default))
}
pub fn input_prompt_selection(&mut self, prompt: &str, sel: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_input_prompt_selection(buf, prompt, sel)
})
}
#[cfg(feature = "password")]
pub fn password_prompt(&mut self, prompt: &str) -> io::Result<usize> {
self.write_formatted_str(|this, buf| {
write!(buf, "\r")?;
this.theme.format_password_prompt(buf, prompt)
})
}
#[cfg(feature = "password")]
pub fn password_prompt_selection(&mut self, prompt: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_password_prompt_selection(buf, prompt)
})
}
pub fn select_prompt(
&mut self,
prompt: &str,
paging_info: Option<(usize, usize)>,
) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_select_prompt(buf, prompt)?;
if let Some(paging_info) = paging_info {
TermThemeRenderer::write_paging_info(buf, paging_info)?;
}
Ok(())
})
}
pub fn select_prompt_selection(&mut self, prompt: &str, sel: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_select_prompt_selection(buf, prompt, sel)
})
}
pub fn select_prompt_item(&mut self, text: &str, active: bool) -> io::Result<()> {
self.write_formatted_line(|this, buf| {
this.theme.format_select_prompt_item(buf, text, active)
})
}
#[cfg(feature = "fuzzy-select")]
pub fn fuzzy_select_prompt_item(
&mut self,
text: &str,
active: bool,
highlight: bool,
matcher: &SkimMatcherV2,
search_term: &str,
) -> io::Result<()> {
self.write_formatted_line(|this, buf| {
this.theme.format_fuzzy_select_prompt_item(
buf,
text,
active,
highlight,
matcher,
search_term,
)
})
}
pub fn multi_select_prompt(
&mut self,
prompt: &str,
paging_info: Option<(usize, usize)>,
) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_multi_select_prompt(buf, prompt)?;
if let Some(paging_info) = paging_info {
TermThemeRenderer::write_paging_info(buf, paging_info)?;
}
Ok(())
})
}
pub fn multi_select_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme
.format_multi_select_prompt_selection(buf, prompt, sel)
})
}
pub fn multi_select_prompt_item(
&mut self,
text: &str,
checked: bool,
active: bool,
) -> io::Result<()> {
self.write_formatted_line(|this, buf| {
this.theme
.format_multi_select_prompt_item(buf, text, checked, active)
})
}
pub fn sort_prompt(
&mut self,
prompt: &str,
paging_info: Option<(usize, usize)>,
) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_sort_prompt(buf, prompt)?;
if let Some(paging_info) = paging_info {
TermThemeRenderer::write_paging_info(buf, paging_info)?;
}
Ok(())
})
}
pub fn sort_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_sort_prompt_selection(buf, prompt, sel)
})
}
pub fn sort_prompt_item(&mut self, text: &str, picked: bool, active: bool) -> io::Result<()> {
self.write_formatted_line(|this, buf| {
this.theme
.format_sort_prompt_item(buf, text, picked, active)
})
}
pub fn clear(&mut self) -> io::Result<()> {
self.term
.clear_last_lines(self.height + self.prompt_height)?;
self.height = 0;
self.prompt_height = 0;
Ok(())
}
pub fn clear_preserve_prompt(&mut self, size_vec: &[usize]) -> io::Result<()> {
let mut new_height = self.height;
let prefix_width = 2;
//Check each item size, increment on finding an overflow
for size in size_vec {
if *size > self.term.size().1 as usize {
new_height += (((*size as f64 + prefix_width as f64) / self.term.size().1 as f64)
.ceil()) as usize
- 1;
}
}
self.term.clear_last_lines(new_height)?;
self.height = 0;
Ok(())
}
}

49
vendor/dialoguer/src/validate.rs vendored Normal file
View File

@ -0,0 +1,49 @@
//! Provides validation for text inputs
/// Trait for input validators.
///
/// A generic implementation for `Fn(&str) -> Result<(), E>` is provided
/// to facilitate development.
pub trait Validator<T> {
type Err;
/// Invoked with the value to validate.
///
/// If this produces `Ok(())` then the value is used and parsed, if
/// an error is returned validation fails with that error.
fn validate(&mut self, input: &T) -> Result<(), Self::Err>;
}
impl<T, F, E> Validator<T> for F
where
F: FnMut(&T) -> Result<(), E>,
{
type Err = E;
fn validate(&mut self, input: &T) -> Result<(), Self::Err> {
self(input)
}
}
/// Trait for password validators.
#[allow(clippy::ptr_arg)]
pub trait PasswordValidator {
type Err;
/// Invoked with the value to validate.
///
/// If this produces `Ok(())` then the value is used and parsed, if
/// an error is returned validation fails with that error.
fn validate(&self, input: &String) -> Result<(), Self::Err>;
}
impl<F, E> PasswordValidator for F
where
F: Fn(&String) -> Result<(), E>,
{
type Err = E;
fn validate(&self, input: &String) -> Result<(), Self::Err> {
self(input)
}
}