dialoguer-0.10.4/.cargo_vcs_info.json0000644000000001360000000000100130710ustar { "git": { "sha1": "fa11422e904be221570cbf2f1460da85355db599" }, "path_in_vcs": "" }dialoguer-0.10.4/.clippy.toml000064400000000000000000000000201046102023000141240ustar 00000000000000msrv = "1.51.0" dialoguer-0.10.4/.github/workflows/ci.yml000064400000000000000000000046261046102023000164040ustar 00000000000000name: CI on: push: branches: [master] pull_request: branches: [master] jobs: test: name: Tests strategy: fail-fast: false matrix: include: - os: macos-latest target: x86_64-apple-darwin rust: 1.56.0 - os: ubuntu-latest target: x86_64-unknown-linux-gnu rust: 1.51.0 - os: ubuntu-latest target: i686-unknown-linux-gnu rust: 1.51.0 - os: windows-latest target: i686-pc-windows-msvc rust: 1.51.0 - os: windows-latest target: x86_64-pc-windows-msvc rust: 1.51.0 - os: ubuntu-latest target: x86_64-unknown-linux-gnu rust: stable - os: ubuntu-latest target: x86_64-unknown-linux-gnu rust: beta runs-on: ${{ matrix.os }} steps: - name: Install rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} override: true - name: Checkout uses: actions/checkout@v2 - name: Install linker if: matrix.target == 'i686-unknown-linux-gnu' run: | sudo apt-get update sudo apt-get install gcc-multilib - name: Test uses: actions-rs/cargo@v1 with: command: test args: --target ${{ matrix.target }} --lib nightly: name: Nightly Tests runs-on: ubuntu-latest steps: - name: Install rust uses: actions-rs/toolchain@v1 with: toolchain: nightly override: true - name: Checkout uses: actions/checkout@v2 - name: Test uses: actions-rs/cargo@v1 with: command: test args: --lib lint: name: Linting (fmt + clippy) runs-on: ubuntu-latest steps: - name: Install rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true components: rustfmt, clippy - name: Checkout uses: actions/checkout@v2 - name: Clippy uses: actions-rs/cargo@v1 with: command: clippy args: --tests --examples - name: Format check uses: actions-rs/cargo@v1 with: command: fmt args: -- --check dialoguer-0.10.4/.gitignore000064400000000000000000000000221046102023000136430ustar 00000000000000target Cargo.lock dialoguer-0.10.4/.vscode/settings.json000064400000000000000000000000651046102023000157560ustar 00000000000000{ "rust-analyzer.checkOnSave.command": "clippy" }dialoguer-0.10.4/CHANGELOG.md000064400000000000000000000053041046102023000134740ustar 00000000000000# 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 dialoguer-0.10.4/Cargo.lock0000644000000234260000000000100110530ustar # 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" dialoguer-0.10.4/Cargo.toml0000644000000033660000000000100110770ustar # 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 ", "Pavan Kumar Sunkara ", ] 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"] dialoguer-0.10.4/Cargo.toml.orig000064400000000000000000000024021046102023000145460ustar 00000000000000[package] name = "dialoguer" description = "A command line prompting library." version = "0.10.4" edition = "2018" authors = [ "Armin Ronacher ", "Pavan Kumar Sunkara " ] keywords = ["cli", "menu", "prompt"] categories = ["command-line-interface"] license = "MIT" homepage = "https://github.com/mitsuhiko/dialoguer" repository = "https://github.com/mitsuhiko/dialoguer" documentation = "https://docs.rs/dialoguer" readme = "README.md" [features] default = ["editor", "password"] editor = ["tempfile"] fuzzy-select = ["fuzzy-matcher"] history = [] password = ["zeroize"] completion = [] [dependencies] console = "0.15.0" tempfile = { version = "3", optional = true } zeroize = { version = "1.1.1", optional = true } fuzzy-matcher = { version = "0.3.7", optional = true } shell-words = "1.1.0" [[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"] [workspace.metadata.workspaces] no_individual_tags = true [package.metadata.docs.rs] all-features = true dialoguer-0.10.4/LICENSE000064400000000000000000000021301046102023000126620ustar 00000000000000The MIT License (MIT) Copyright (c) 2017 Armin Ronacher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dialoguer-0.10.4/Makefile000064400000000000000000000010131046102023000133140ustar 00000000000000all: 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 dialoguer-0.10.4/README.md000064400000000000000000000015271046102023000131450ustar 00000000000000# 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) dialoguer-0.10.4/examples/buffered.rs000064400000000000000000000021131046102023000156240ustar 00000000000000use 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(); } dialoguer-0.10.4/examples/completion.rs000064400000000000000000000021451046102023000162200ustar 00000000000000use 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::::with_theme(&ColorfulTheme::default()) .with_prompt("dialoguer") .completion_with(&completion) .interact_text()?; Ok(()) } struct MyCompletion { options: Vec, } 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 { let matches = self .options .iter() .filter(|option| option.starts_with(input)) .collect::>(); if matches.len() == 1 { Some(matches[0].to_string()) } else { None } } } dialoguer-0.10.4/examples/confirm.rs000064400000000000000000000040331046102023000155020ustar 00000000000000use 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"), } } dialoguer-0.10.4/examples/editor.rs000064400000000000000000000003421046102023000153320ustar 00000000000000use dialoguer::Editor; fn main() { if let Some(rv) = Editor::new().edit("Enter a commit message").unwrap() { println!("Your message:"); println!("{}", rv); } else { println!("Abort!"); } } dialoguer-0.10.4/examples/fuzzyselect.rs000064400000000000000000000017171046102023000164420ustar 00000000000000use 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]); } dialoguer-0.10.4/examples/history.rs000064400000000000000000000023371046102023000155530ustar 00000000000000use 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::::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, } impl Default for MyHistory { fn default() -> Self { MyHistory { max: 4, history: VecDeque::new(), } } } impl History for MyHistory { fn read(&self, pos: usize) -> Option { 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()); } } dialoguer-0.10.4/examples/input.rs000064400000000000000000000024611046102023000152070ustar 00000000000000use 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); } dialoguer-0.10.4/examples/multi_select.rs000064400000000000000000000023251046102023000165400ustar 00000000000000use 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]); } } } dialoguer-0.10.4/examples/paging.rs000064400000000000000000000016321046102023000153140ustar 00000000000000use 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]); } dialoguer-0.10.4/examples/password.rs000064400000000000000000000010761046102023000157130ustar 00000000000000use 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()); } dialoguer-0.10.4/examples/select.rs000064400000000000000000000022271046102023000153270ustar 00000000000000use 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]); } dialoguer-0.10.4/examples/sort.rs000064400000000000000000000016511046102023000150370ustar 00000000000000use 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]]); } dialoguer-0.10.4/examples/wizard.rs000064400000000000000000000037171046102023000153550ustar 00000000000000use 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, cert: Option, } fn init_config() -> Result, Box> { 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), } } dialoguer-0.10.4/src/completion.rs000064400000000000000000000001561046102023000151710ustar 00000000000000/// Trait for completion handling. pub trait Completion { fn get(&self, input: &str) -> Option; } dialoguer-0.10.4/src/edit.rs000064400000000000000000000061701046102023000137470ustar 00000000000000use 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>(&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> { 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)) } } dialoguer-0.10.4/src/history.rs000064400000000000000000000012161046102023000145170ustar 00000000000000/// Trait for history handling. pub trait History { /// 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; /// 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); } dialoguer-0.10.4/src/lib.rs000064400000000000000000000033631046102023000135710ustar 00000000000000//! 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; dialoguer-0.10.4/src/paging.rs000064400000000000000000000072171046102023000142720ustar 00000000000000use 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, 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) -> 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(&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 } } dialoguer-0.10.4/src/prompts/confirm.rs000064400000000000000000000211741046102023000161640ustar 00000000000000use 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> { /// 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, 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>(&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 { 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> { 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 { 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> { self._interact_on(term, true) } fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result> { 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, } } } dialoguer-0.10.4/src/prompts/fuzzy_select.rs000064400000000000000000000253221046102023000172540ustar 00000000000000use 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, items: Vec, prompt: String, report: bool, clear: bool, highlight_matches: bool, max_length: Option, 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(&mut self, item: T) -> &mut Self { self.items.push(item.to_string()); self } /// Adds multiple items to the fuzzy selector. pub fn items(&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>(&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>(&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 { 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> { 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 { 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> { 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> { // 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::>(); // 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(), } } } dialoguer-0.10.4/src/prompts/input.rs000064400000000000000000000640771046102023000156770ustar 00000000000000use 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 Option + 'a>; /// Renders an input prompt. /// /// ## Example usage /// /// ```rust,no_run /// use dialoguer::Input; /// /// # fn test() -> Result<(), Box> { /// 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> { /// # use dialoguer::Input; /// let input = Input::::new() /// .interact_text()?; /// # Ok(()) /// # } /// ``` pub struct Input<'a, T> { prompt: String, post_completion_text: Option, report: bool, default: Option, show_default: bool, initial_text: Option, theme: &'a dyn Theme, permit_empty: bool, validator: Option>, #[cfg(feature = "history")] history: Option<&'a mut dyn History>, #[cfg(feature = "completion")] completion: Option<&'a dyn Completion>, } impl Default for Input<'static, T> { fn default() -> Self { Self::new() } } impl Input<'_, T> { /// Creates an input prompt. pub fn new() -> Self { Self::with_theme(&SimpleTheme) } /// Sets the input prompt. pub fn with_prompt>(&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>( &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>(&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::::new() /// .with_prompt("hist") /// .history_with(&mut history) /// .interact_text() /// { /// // Do something with the input /// } /// } /// # struct MyHistory { /// # history: VecDeque, /// # } /// # /// # impl Default for MyHistory { /// # fn default() -> Self { /// # MyHistory { /// # history: VecDeque::new(), /// # } /// # } /// # } /// # /// # impl History for MyHistory { /// # fn read(&self, pos: usize) -> Option { /// # 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(&mut self, history: &'a mut H) -> &mut Self where H: History, { self.history = Some(history); self } /// Enable completion #[cfg(feature = "completion")] pub fn completion_with(&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(&mut self, mut validator: V) -> &mut Self where V: Validator + 'a, V::Err: ToString, { let mut old_validator_func = self.validator.take(); self.validator = Some(Box::new(move |value: &T| -> Option { 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 Input<'_, T> where T: Clone + ToString + 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 { 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 { 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::().unwrap()); } let mut chars: Vec = 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::(); 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::() { 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 Input<'_, T> where T: Clone + ToString + 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 { 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 { 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::() { 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; } } } } } dialoguer-0.10.4/src/prompts/mod.rs000064400000000000000000000003461046102023000153040ustar 00000000000000#![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; dialoguer-0.10.4/src/prompts/multi_select.rs000064400000000000000000000260131046102023000172150ustar 00000000000000use 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> { /// use dialoguer::MultiSelect; /// /// let items = vec!["Option 1", "Option 2"]; /// let chosen : Vec = MultiSelect::new() /// .items(&items) /// .interact()?; /// # Ok(()) /// # } /// ``` pub struct MultiSelect<'a> { defaults: Vec, items: Vec, prompt: Option, report: bool, clear: bool, max_length: Option, 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(&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(&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(&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(&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>(&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` 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> { 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)` if user hit 'Enter' or `None` if user cancelled with 'Esc' or 'q'. #[inline] pub fn interact_opt(&self) -> io::Result>> { 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> { 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>> { self._interact_on(term, true) } fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result>> { 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::>() { let size = &items.len(); size_vec.push(*size); } let mut checked: Vec = 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, } } } dialoguer-0.10.4/src/prompts/password.rs000064400000000000000000000125521046102023000163710ustar 00000000000000use std::io; use crate::{ theme::{SimpleTheme, TermThemeRenderer, Theme}, validate::PasswordValidator, }; use console::Term; use zeroize::Zeroizing; type PasswordValidatorCallback<'a> = Box Option + 'a>; /// Renders a password input prompt. /// /// ## Example usage /// /// ```rust,no_run /// # fn test() -> Result<(), Box> { /// 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>, } 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>(&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(&mut self, prompt: A, mismatch_err: B) -> &mut Self where A: Into, B: Into, { 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(&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 { 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 { self.interact_on(&Term::stderr()) } /// Like `interact` but allows a specific terminal to be set. pub fn interact_on(&self, term: &Term) -> io::Result { 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 { 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, } } } dialoguer-0.10.4/src/prompts/select.rs000064400000000000000000000277711046102023000160170ustar 00000000000000use 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, prompt: Option, report: bool, clear: bool, theme: &'a dyn Theme, max_length: Option, } 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(&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(&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>(&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 { 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> { 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 { 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> { 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> { 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::>() { 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 ); } } dialoguer-0.10.4/src/prompts/sort.rs000064400000000000000000000261121046102023000155130ustar 00000000000000use 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> { /// 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, prompt: Option, report: bool, clear: bool, max_length: Option, 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(&mut self, item: T) -> &mut Self { self.items.push(item.to_string()); self } /// Adds multiple items to the selector. pub fn items(&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>(&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` 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> { 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)` if user hit 'Enter' or `None` if user cancelled with 'Esc' or 'q'. #[inline] pub fn interact_opt(&self) -> io::Result>> { 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> { 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>> { self._interact_on(term, true) } fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result>> { 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, } } } dialoguer-0.10.4/src/theme.rs000064400000000000000000000714451046102023000141330ustar 00000000000000//! 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, ) -> 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, ) -> 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, /// Prompt suffix value and style pub prompt_suffix: StyledObject, /// Prompt on success prefix value and style pub success_prefix: StyledObject, /// Prompt on success suffix value and style pub success_suffix: StyledObject, /// Error prefix value and style pub error_prefix: StyledObject, /// 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, /// Inctive item in select prefix value and style pub inactive_item_prefix: StyledObject, /// Checked item in multi select prefix value and style pub checked_item_prefix: StyledObject, /// Unchecked item in multi select prefix value and style pub unchecked_item_prefix: StyledObject, /// Picked item in sort prefix value and style pub picked_item_prefix: StyledObject, /// Unpicked item in sort prefix value and style pub unpicked_item_prefix: StyledObject, /// 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, ) -> 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, ) -> 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 { 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) -> io::Result { 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) -> 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 { 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 { 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(()) } } dialoguer-0.10.4/src/validate.rs000064400000000000000000000023021046102023000146040ustar 00000000000000//! 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 { 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 Validator 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 PasswordValidator for F where F: Fn(&String) -> Result<(), E>, { type Err = E; fn validate(&self, input: &String) -> Result<(), Self::Err> { self(input) } }