refactor(cli.rs): drop `dialoguer` and `console` soft fork (#2790)

This commit is contained in:
Lucas Fernandes Nogueira 2021-10-22 10:41:43 -03:00 committed by GitHub
parent da35d55b09
commit b1f5c6d7ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 34 additions and 5494 deletions

View File

@ -0,0 +1,5 @@
---
"cli.rs": patch
---
Drop the `dialoguer` soft fork and use the published version instead.

View File

@ -277,6 +277,21 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "console"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"regex",
"terminal_size",
"unicode-width",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.1" version = "0.9.1"
@ -410,6 +425,18 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "dialoguer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61579ada4ec0c6031cfac3f86fdba0d195a7ebeb5e36693bd53cb5999a25beeb"
dependencies = [
"console",
"lazy_static",
"tempfile",
"zeroize",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.8.1" version = "0.8.1"
@ -1933,6 +1960,7 @@ dependencies = [
"base64", "base64",
"clap", "clap",
"colored", "colored",
"dialoguer",
"encode_unicode", "encode_unicode",
"glob", "glob",
"handlebars", "handlebars",

View File

@ -49,6 +49,7 @@ tempfile = "3"
zeroize = "1.4" zeroize = "1.4"
glob = "0.3" glob = "0.3"
heck = "0.3" heck = "0.3"
dialoguer = "0.9"
[target."cfg(windows)".dependencies] [target."cfg(windows)".dependencies]
winapi = { version = "0.3", features = [ "winbase", "winuser", "consoleapi", "processenv", "wincon" ] } winapi = { version = "0.3", features = [ "winbase", "winuser", "consoleapi", "processenv", "wincon" ] }

View File

@ -1,137 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::borrow::Cow;
use regex::{Matches, Regex};
lazy_static::lazy_static! {
static ref STRIP_ANSI_RE: Regex =
Regex::new(r"[\x1b\x9b]([()][012AB]|[\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><])")
.unwrap();
}
/// Helper function to strip ansi codes.
pub fn strip_ansi_codes(s: &str) -> Cow<'_, str> {
STRIP_ANSI_RE.replace_all(s, "")
}
/// An iterator over ansi codes in a string.
///
/// This type can be used to scan over ansi codes in a string.
/// It yields tuples in the form `(s, is_ansi)` where `s` is a slice of
/// the original string and `is_ansi` indicates if the slice contains
/// ansi codes or string values.
pub struct AnsiCodeIterator<'a> {
s: &'a str,
pending_item: Option<(&'a str, bool)>,
last_idx: usize,
cur_idx: usize,
iter: Matches<'static, 'a>,
}
impl<'a> AnsiCodeIterator<'a> {
/// Creates a new ansi code iterator.
pub fn new(s: &'a str) -> AnsiCodeIterator<'a> {
AnsiCodeIterator {
s,
pending_item: None,
last_idx: 0,
cur_idx: 0,
iter: STRIP_ANSI_RE.find_iter(s),
}
}
/// Returns the string slice up to the current match.
pub fn current_slice(&self) -> &str {
&self.s[..self.cur_idx]
}
/// Returns the string slice from the current match to the end.
pub fn rest_slice(&self) -> &str {
&self.s[self.cur_idx..]
}
}
impl<'a> Iterator for AnsiCodeIterator<'a> {
type Item = (&'a str, bool);
fn next(&mut self) -> Option<(&'a str, bool)> {
if let Some(pending_item) = self.pending_item.take() {
self.cur_idx += pending_item.0.len();
Some(pending_item)
} else if let Some(m) = self.iter.next() {
let s = &self.s[self.last_idx..m.start()];
self.last_idx = m.end();
if s.is_empty() {
self.cur_idx = m.end();
Some((m.as_str(), true))
} else {
self.cur_idx = m.start();
self.pending_item = Some((m.as_str(), true));
Some((s, false))
}
} else if self.last_idx < self.s.len() {
let rv = &self.s[self.last_idx..];
self.cur_idx = self.s.len();
self.last_idx = self.s.len();
Some((rv, false))
} else {
None
}
}
}
#[test]
fn test_ansi_iter_re_vt100() {
let s = "\x1b(0lpq\x1b)Benglish";
let mut iter = AnsiCodeIterator::new(s);
assert_eq!(iter.next(), Some(("\x1b(0", true)));
assert_eq!(iter.next(), Some(("lpq", false)));
assert_eq!(iter.next(), Some(("\x1b)B", true)));
assert_eq!(iter.next(), Some(("english", false)));
}
#[test]
fn test_ansi_iter_re() {
use super::style;
let s = format!("Hello {}!", style("World").red().force_styling(true));
let mut iter = AnsiCodeIterator::new(&s);
assert_eq!(iter.next(), Some(("Hello ", false)));
assert_eq!(iter.current_slice(), "Hello ");
assert_eq!(iter.rest_slice(), "\x1b[31mWorld\x1b[0m!");
assert_eq!(iter.next(), Some(("\x1b[31m", true)));
assert_eq!(iter.current_slice(), "Hello \x1b[31m");
assert_eq!(iter.rest_slice(), "World\x1b[0m!");
assert_eq!(iter.next(), Some(("World", false)));
assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld");
assert_eq!(iter.rest_slice(), "\x1b[0m!");
assert_eq!(iter.next(), Some(("\x1b[0m", true)));
assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m");
assert_eq!(iter.rest_slice(), "!");
assert_eq!(iter.next(), Some(("!", false)));
assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m!");
assert_eq!(iter.rest_slice(), "");
assert_eq!(iter.next(), None);
}
#[test]
fn test_ansi_iter_re_on_multi() {
use super::style;
let s = format!("{}", style("a").red().bold().force_styling(true));
let mut iter = AnsiCodeIterator::new(&s);
assert_eq!(iter.next(), Some(("\x1b[31m", true)));
assert_eq!(iter.current_slice(), "\x1b[31m");
assert_eq!(iter.rest_slice(), "\x1b[1ma\x1b[0m");
assert_eq!(iter.next(), Some(("\x1b[1m", true)));
assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1m");
assert_eq!(iter.rest_slice(), "a\x1b[0m");
assert_eq!(iter.next(), Some(("a", false)));
assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma");
assert_eq!(iter.rest_slice(), "\x1b[0m");
assert_eq!(iter.next(), Some(("\x1b[0m", true)));
assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma\x1b[0m");
assert_eq!(iter.rest_slice(), "");
assert_eq!(iter.next(), None);
}

View File

@ -1,76 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::io;
use super::term::Term;
pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> {
if n > 0 {
out.write_str(&format!("\x1b[{}B", n))
} else {
Ok(())
}
}
pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> {
if n > 0 {
out.write_str(&format!("\x1b[{}A", n))
} else {
Ok(())
}
}
pub fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> {
if n > 0 {
out.write_str(&format!("\x1b[{}D", n))
} else {
Ok(())
}
}
pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> {
if n > 0 {
out.write_str(&format!("\x1b[{}C", n))
} else {
Ok(())
}
}
#[inline]
pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> {
out.write_str(&format!("\x1B[{};{}H", y + 1, x + 1))
}
pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> {
if n > 0 {
out.write_str(&format!("\x1b[{}D\x1b[0K", n))
} else {
Ok(())
}
}
#[inline]
pub fn clear_line(out: &Term) -> io::Result<()> {
out.write_str("\r\x1b[2K")
}
#[inline]
pub fn clear_screen(out: &Term) -> io::Result<()> {
out.write_str("\r\x1b[2J\r\x1b[H")
}
#[inline]
pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> {
out.write_str("\r\x1b[0J")
}
#[inline]
pub fn show_cursor(out: &Term) -> io::Result<()> {
out.write_str("\x1b[?25h")
}
#[inline]
pub fn hide_cursor(out: &Term) -> io::Result<()> {
out.write_str("\x1b[?25l")
}

View File

@ -1,32 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
/// Key mapping
///
/// This is an incomplete mapping of keys that are supported for reading
/// from the keyboard.
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Key {
Unknown,
/// Unrecognized sequence containing Esc and a list of chars
UnknownEscSeq(Vec<char>),
ArrowLeft,
ArrowRight,
ArrowUp,
ArrowDown,
Enter,
Escape,
Backspace,
Home,
End,
Tab,
BackTab,
Del,
Shift,
Insert,
PageUp,
PageDown,
Char(char),
}

View File

@ -1,108 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! console is a library for Rust that provides access to various terminal
//! features so you can build nicer looking command line interfaces. It
//! comes with various tools and utilities for working with Terminals and
//! formatting text.
//!
//! Best paired with other libraries in the family:
//!
//! * [dialoguer](https://docs.rs/dialoguer)
//! * [indicatif](https://docs.rs/indicatif)
//!
//! # Terminal Access
//!
//! The terminal is abstracted through the `console::Term` type. It can
//! either directly provide access to the connected terminal or by buffering
//! up commands. A buffered terminal will however not be completely buffered
//! on windows where cursor movements are currently directly passed through.
//!
//! Example usage:
//!
//! ```
//! # fn test() -> Result<(), Box<dyn std::error::Error>> {
//! use std::thread;
//! use std::time::Duration;
//!
//! use console::Term;
//!
//! let term = Term::stdout();
//! term.write_line("Hello World!")?;
//! thread::sleep(Duration::from_millis(2000));
//! term.clear_line()?;
//! # Ok(()) } test().unwrap();
//! ```
//!
//! # Colors and Styles
//!
//! `console` uses `clicolors-control` to control colors. It also
//! provides higher level wrappers for styling text and other things
//! that can be displayed with the `style` function and utility types.
//!
//! Example usage:
//!
//! ```
//! use console::style;
//!
//! println!("This is {} neat", style("quite").cyan());
//! ```
//!
//! You can also store styles and apply them to text later:
//!
//! ```
//! use console::Style;
//!
//! let cyan = Style::new().cyan();
//! println!("This is {} neat", cyan.apply_to("quite"));
//! ```
//!
//! # Working with ANSI Codes
//!
//! The crate provids the function `strip_ansi_codes` to remove ANSI codes
//! from a string as well as `measure_text_width` to calculate the width of a
//! string as it would be displayed by the terminal. Both of those together
//! are useful for more complex formatting.
//!
//! # Unicode Width Support
//!
//! By default this crate depends on the `unicode-width` crate to calculate
//! the width of terminal characters. If you do not need this you can disable
//! the `unicode-width` feature which will cut down on dependencies.
//!
//! # Features
//!
//! By default all features are enabled. The following features exist:
//!
//! * `unicode-width`: adds support for unicode width calculations
//! * `ansi-parsing`: adds support for parsing ansi codes (this adds support
//! for stripping and taking ansi escape codes into account for length
//! calculations).
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
pub use kb::Key;
pub use term::{user_attended, user_attended_stderr, Term, TermFamily, TermFeatures, TermTarget};
pub use utils::{
colors_enabled, colors_enabled_stderr, measure_text_width, pad_str, pad_str_with,
set_colors_enabled, set_colors_enabled_stderr, style, truncate_str, Alignment, Attribute, Color,
Emoji, Style, StyledObject,
};
pub use ansi::{strip_ansi_codes, AnsiCodeIterator};
mod common_term;
mod kb;
mod term;
#[cfg(unix)]
mod unix_term;
mod utils;
#[cfg(target_arch = "wasm32")]
mod wasm_term;
#[cfg(windows)]
mod windows_term;
mod ansi;

View File

@ -1,624 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::fmt::{Debug, Display};
use std::io::{self, Read, Write};
use std::sync::{Arc, Mutex};
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
use super::{kb::Key, utils::Style};
#[cfg(unix)]
trait TermWrite: Write + Debug + AsRawFd + Send {}
#[cfg(unix)]
impl<T: Write + Debug + AsRawFd + Send> TermWrite for T {}
#[cfg(unix)]
trait TermRead: Read + Debug + AsRawFd + Send {}
#[cfg(unix)]
impl<T: Read + Debug + AsRawFd + Send> TermRead for T {}
#[cfg(unix)]
#[derive(Debug, Clone)]
pub struct ReadWritePair {
read: Arc<Mutex<dyn TermRead>>,
write: Arc<Mutex<dyn TermWrite>>,
style: Style,
}
/// Where the term is writing.
#[derive(Debug, Clone)]
pub enum TermTarget {
Stdout,
Stderr,
#[cfg(unix)]
ReadWritePair(ReadWritePair),
}
#[derive(Debug)]
pub struct TermInner {
target: TermTarget,
buffer: Option<Mutex<Vec<u8>>>,
}
/// The family of the terminal.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TermFamily {
/// Redirected to a file or file like thing.
File,
/// A standard unix terminal.
UnixTerm,
/// A cmd.exe like windows console.
WindowsConsole,
/// A dummy terminal (for instance on wasm)
Dummy,
}
/// Gives access to the terminal features.
#[derive(Debug, Clone)]
pub struct TermFeatures<'a>(&'a Term);
impl<'a> TermFeatures<'a> {
/// Checks if this is a real user attended terminal (`isatty`)
#[inline]
pub fn is_attended(&self) -> bool {
is_a_terminal(self.0)
}
/// Checks if colors are supported by this terminal.
///
/// This does not check if colors are enabled. Currently all terminals
/// are considered to support colors
#[inline]
pub fn colors_supported(&self) -> bool {
is_a_color_terminal(self.0)
}
/// Checks if this terminal is an msys terminal.
///
/// This is sometimes useful to disable features that are known to not
/// work on msys terminals or require special handling.
#[inline]
pub fn is_msys_tty(&self) -> bool {
#[cfg(windows)]
{
msys_tty_on(self.0)
}
#[cfg(not(windows))]
{
false
}
}
/// Checks if this terminal wants emojis.
#[inline]
pub fn wants_emoji(&self) -> bool {
self.is_attended() && wants_emoji()
}
/// Returns the family of the terminal.
#[inline]
pub fn family(&self) -> TermFamily {
if !self.is_attended() {
return TermFamily::File;
}
#[cfg(windows)]
{
TermFamily::WindowsConsole
}
#[cfg(unix)]
{
TermFamily::UnixTerm
}
#[cfg(target_arch = "wasm32")]
{
TermFamily::Dummy
}
}
}
/// Abstraction around a terminal.
///
/// A terminal can be cloned. If a buffer is used it's shared across all
/// clones which means it largely acts as a handle.
#[derive(Clone, Debug)]
pub struct Term {
inner: Arc<TermInner>,
pub(crate) is_msys_tty: bool,
pub(crate) is_tty: bool,
}
impl Term {
fn with_inner(inner: TermInner) -> Term {
let mut term = Term {
inner: Arc::new(inner),
is_msys_tty: false,
is_tty: false,
};
term.is_msys_tty = term.features().is_msys_tty();
term.is_tty = term.features().is_attended();
term
}
/// Return a new unbuffered terminal
#[inline]
pub fn stdout() -> Term {
Term::with_inner(TermInner {
target: TermTarget::Stdout,
buffer: None,
})
}
/// Return a new unbuffered terminal to stderr
#[inline]
pub fn stderr() -> Term {
Term::with_inner(TermInner {
target: TermTarget::Stderr,
buffer: None,
})
}
/// Return a new buffered terminal
pub fn buffered_stdout() -> Term {
Term::with_inner(TermInner {
target: TermTarget::Stdout,
buffer: Some(Mutex::new(vec![])),
})
}
/// Return a new buffered terminal to stderr
pub fn buffered_stderr() -> Term {
Term::with_inner(TermInner {
target: TermTarget::Stderr,
buffer: Some(Mutex::new(vec![])),
})
}
/// Return a terminal for the given Read/Write pair styled-like Stderr.
#[cfg(unix)]
pub fn read_write_pair<R, W>(read: R, write: W) -> Term
where
R: Read + Debug + AsRawFd + Send + 'static,
W: Write + Debug + AsRawFd + Send + 'static,
{
Self::read_write_pair_with_style(read, write, Style::new().for_stderr())
}
/// Return a terminal for the given Read/Write pair.
#[cfg(unix)]
pub fn read_write_pair_with_style<R, W>(read: R, write: W, style: Style) -> Term
where
R: Read + Debug + AsRawFd + Send + 'static,
W: Write + Debug + AsRawFd + Send + 'static,
{
Term::with_inner(TermInner {
target: TermTarget::ReadWritePair(ReadWritePair {
read: Arc::new(Mutex::new(read)),
write: Arc::new(Mutex::new(write)),
style,
}),
buffer: None,
})
}
/// Returns the style for the term
#[inline]
pub fn style(&self) -> Style {
match self.inner.target {
TermTarget::Stderr => Style::new().for_stderr(),
TermTarget::Stdout => Style::new().for_stdout(),
#[cfg(unix)]
TermTarget::ReadWritePair(ReadWritePair { ref style, .. }) => style.clone(),
}
}
/// Returns the target
#[inline]
pub fn target(&self) -> TermTarget {
self.inner.target.clone()
}
#[doc(hidden)]
pub fn write_str(&self, s: &str) -> io::Result<()> {
match self.inner.buffer {
Some(ref buffer) => buffer.lock().unwrap().write_all(s.as_bytes()),
None => self.write_through(s.as_bytes()),
}
}
/// Writes a string to the terminal and adds a newline.
pub fn write_line(&self, s: &str) -> io::Result<()> {
match self.inner.buffer {
Some(ref mutex) => {
let mut buffer = mutex.lock().unwrap();
buffer.extend_from_slice(s.as_bytes());
buffer.push(b'\n');
Ok(())
}
None => self.write_through(format!("{}\n", s).as_bytes()),
}
}
/// Read a single character from the terminal
///
/// This does not echo the character and blocks until a single character
/// is entered.
pub fn read_char(&self) -> io::Result<char> {
loop {
match self.read_key()? {
Key::Char(c) => {
return Ok(c);
}
Key::Enter => {
return Ok('\n');
}
Key::Unknown => {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"Not a terminal",
))
}
_ => {}
}
}
}
/// Read a single key form the terminal.
///
/// This does not echo anything. If the terminal is not user attended
/// the return value will always be the unknown key.
pub fn read_key(&self) -> io::Result<Key> {
if !self.is_tty {
Ok(Key::Unknown)
} else {
read_single_key()
}
}
/// Read one line of input.
///
/// This does not include the trailing newline. If the terminal is not
/// user attended the return value will always be an empty string.
pub fn read_line(&self) -> io::Result<String> {
if !self.is_tty {
return Ok("".into());
}
let mut rv = String::new();
io::stdin().read_line(&mut rv)?;
let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
rv.truncate(len);
Ok(rv)
}
/// Read one line of input with initial text.
///
/// This does not include the trailing newline. If the terminal is not
/// user attended the return value will always be an empty string.
pub fn read_line_initial_text(&self, initial: &str) -> io::Result<String> {
if !self.is_tty {
return Ok("".into());
}
self.write_str(initial)?;
let mut chars: Vec<char> = initial.chars().collect();
loop {
match self.read_key()? {
Key::Backspace => {
if chars.pop().is_some() {
self.clear_chars(1)?;
}
self.flush()?;
}
Key::Char(chr) => {
chars.push(chr);
let mut bytes_char = [0; 4];
chr.encode_utf8(&mut bytes_char);
self.write_str(chr.encode_utf8(&mut bytes_char))?;
self.flush()?;
}
Key::Enter => break,
Key::Unknown => {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"Not a terminal",
))
}
_ => (),
}
}
Ok(chars.iter().collect::<String>())
}
/// Read securely a line of input.
///
/// This is similar to `read_line` but will not echo the output. This
/// also switches the terminal into a different mode where not all
/// characters might be accepted.
pub fn read_secure_line(&self) -> io::Result<String> {
if !self.is_tty {
return Ok("".into());
}
match read_secure() {
Ok(rv) => {
self.write_line("")?;
Ok(rv)
}
Err(err) => Err(err),
}
}
/// Flushes internal buffers.
///
/// This forces the contents of the internal buffer to be written to
/// the terminal. This is unnecessary for unbuffered terminals which
/// will automatically flush.
pub fn flush(&self) -> io::Result<()> {
if let Some(ref buffer) = self.inner.buffer {
let mut buffer = buffer.lock().unwrap();
if !buffer.is_empty() {
self.write_through(&buffer[..])?;
buffer.clear();
}
}
Ok(())
}
/// Checks if the terminal is indeed a terminal.
#[inline]
pub fn is_term(&self) -> bool {
self.is_tty
}
/// Checks for common terminal features.
#[inline]
pub fn features(&self) -> TermFeatures<'_> {
TermFeatures(self)
}
/// Returns the terminal size in rows and columns or gets sensible defaults.
#[inline]
pub fn size(&self) -> (u16, u16) {
self.size_checked().unwrap_or((24, DEFAULT_WIDTH))
}
/// Returns the terminal size in rows and columns.
///
/// If the size cannot be reliably determined None is returned.
#[inline]
pub fn size_checked(&self) -> Option<(u16, u16)> {
terminal_size(self)
}
/// Moves the cursor to `x` and `y`
#[inline]
pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> {
move_cursor_to(self, x, y)
}
/// Moves the cursor up `n` lines
#[inline]
pub fn move_cursor_up(&self, n: usize) -> io::Result<()> {
move_cursor_up(self, n)
}
/// Moves the cursor down `n` lines
#[inline]
pub fn move_cursor_down(&self, n: usize) -> io::Result<()> {
move_cursor_down(self, n)
}
/// Moves the cursor left `n` lines
#[inline]
pub fn move_cursor_left(&self, n: usize) -> io::Result<()> {
move_cursor_left(self, n)
}
/// Moves the cursor down `n` lines
#[inline]
pub fn move_cursor_right(&self, n: usize) -> io::Result<()> {
move_cursor_right(self, n)
}
/// Clears the current line.
///
/// The positions the cursor at the beginning of the line again.
#[inline]
pub fn clear_line(&self) -> io::Result<()> {
clear_line(self)
}
/// Clear the last `n` lines.
///
/// This positions the cursor at the beginning of the first line
/// that was cleared.
pub fn clear_last_lines(&self, n: usize) -> io::Result<()> {
self.move_cursor_up(n)?;
for _ in 0..n {
self.clear_line()?;
self.move_cursor_down(1)?;
}
self.move_cursor_up(n)?;
Ok(())
}
/// Clears the entire screen.
#[inline]
pub fn clear_screen(&self) -> io::Result<()> {
clear_screen(self)
}
/// Clears the entire screen.
#[inline]
pub fn clear_to_end_of_screen(&self) -> io::Result<()> {
clear_to_end_of_screen(self)
}
/// Clears the last char in the the current line.
#[inline]
pub fn clear_chars(&self, n: usize) -> io::Result<()> {
clear_chars(self, n)
}
/// Set the terminal title
pub fn set_title<T: Display>(&self, title: T) {
if !self.is_tty {
return;
}
set_title(title);
}
/// Makes cursor visible again
#[inline]
pub fn show_cursor(&self) -> io::Result<()> {
show_cursor(self)
}
/// Hides cursor
#[inline]
pub fn hide_cursor(&self) -> io::Result<()> {
hide_cursor(self)
}
// helpers
#[cfg(all(windows, feature = "windows-console-colors"))]
fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
if self.is_msys_tty || !self.is_tty {
self.write_through_common(bytes)
} else {
use winapi_util::console::Console;
match self.inner.target {
TermTarget::Stdout => console_colors(self, Console::stdout()?, bytes),
TermTarget::Stderr => console_colors(self, Console::stderr()?, bytes),
}
}
}
#[cfg(not(all(windows, feature = "windows-console-colors")))]
fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
self.write_through_common(bytes)
}
pub(crate) fn write_through_common(&self, bytes: &[u8]) -> io::Result<()> {
match self.inner.target {
TermTarget::Stdout => {
io::stdout().write_all(bytes)?;
io::stdout().flush()?;
}
TermTarget::Stderr => {
io::stderr().write_all(bytes)?;
io::stderr().flush()?;
}
#[cfg(unix)]
TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
let mut write = write.lock().unwrap();
write.write_all(bytes)?;
write.flush()?;
}
}
Ok(())
}
}
/// A fast way to check if the application has a user attended for stdout.
///
/// This means that stdout is connected to a terminal instead of a
/// file or redirected by other means. This is a shortcut for
/// checking the `is_attended` feature on the stdout terminal.
#[inline]
pub fn user_attended() -> bool {
Term::stdout().features().is_attended()
}
/// A fast way to check if the application has a user attended for stderr.
///
/// This means that stderr is connected to a terminal instead of a
/// file or redirected by other means. This is a shortcut for
/// checking the `is_attended` feature on the stderr terminal.
#[inline]
pub fn user_attended_stderr() -> bool {
Term::stderr().features().is_attended()
}
#[cfg(unix)]
impl AsRawFd for Term {
fn as_raw_fd(&self) -> RawFd {
match self.inner.target {
TermTarget::Stdout => libc::STDOUT_FILENO,
TermTarget::Stderr => libc::STDERR_FILENO,
TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
write.lock().unwrap().as_raw_fd()
}
}
}
}
#[cfg(windows)]
impl AsRawHandle for Term {
fn as_raw_handle(&self) -> RawHandle {
use winapi::um::processenv::GetStdHandle;
use winapi::um::winbase::{STD_ERROR_HANDLE, STD_OUTPUT_HANDLE};
unsafe {
GetStdHandle(match self.inner.target {
TermTarget::Stdout => STD_OUTPUT_HANDLE,
TermTarget::Stderr => STD_ERROR_HANDLE,
}) as RawHandle
}
}
}
impl Write for Term {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self.inner.buffer {
Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
None => self.write_through(buf),
}?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Term::flush(self)
}
}
impl<'a> Write for &'a Term {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self.inner.buffer {
Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
None => self.write_through(buf),
}?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Term::flush(self)
}
}
impl Read for Term {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
io::stdin().read(buf)
}
}
impl<'a> Read for &'a Term {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
io::stdin().read(buf)
}
}
#[cfg(unix)]
pub use super::unix_term::*;
#[cfg(target_arch = "wasm32")]
pub use super::wasm_term::*;
#[cfg(windows)]
pub use super::windows_term::*;

View File

@ -1,299 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::env;
use std::fmt::Display;
use std::fs;
use std::io;
use std::io::{BufRead, BufReader};
use std::os::unix::io::AsRawFd;
use std::str;
use super::kb::Key;
use super::term::Term;
pub use super::common_term::*;
pub const DEFAULT_WIDTH: u16 = 80;
#[inline]
pub fn is_a_terminal(out: &Term) -> bool {
unsafe { libc::isatty(out.as_raw_fd()) != 0 }
}
pub fn is_a_color_terminal(out: &Term) -> bool {
if !is_a_terminal(out) {
return false;
}
if env::var("NO_COLOR").is_ok() {
return false;
}
match env::var("TERM") {
Ok(term) => term != "dumb",
Err(_) => false,
}
}
pub fn c_result<F: FnOnce() -> libc::c_int>(f: F) -> io::Result<()> {
let res = f();
if res != 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
#[inline]
pub fn terminal_size(out: &Term) -> Option<(u16, u16)> {
terminal_size::terminal_size_using_fd(out.as_raw_fd()).map(|x| ((x.1).0, (x.0).0))
}
pub fn read_secure() -> io::Result<String> {
let f_tty;
let fd = unsafe {
if libc::isatty(libc::STDIN_FILENO) == 1 {
f_tty = None;
libc::STDIN_FILENO
} else {
let f = fs::File::open("/dev/tty")?;
let fd = f.as_raw_fd();
f_tty = Some(BufReader::new(f));
fd
}
};
let mut termios = core::mem::MaybeUninit::uninit();
c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
let mut termios = unsafe { termios.assume_init() };
let original = termios;
termios.c_lflag &= !libc::ECHO;
c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &termios) })?;
let mut rv = String::new();
let read_rv = if let Some(mut f) = f_tty {
f.read_line(&mut rv)
} else {
io::stdin().read_line(&mut rv)
};
c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &original) })?;
read_rv.map(|_| {
let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
rv.truncate(len);
rv
})
}
fn read_single_char(fd: i32) -> io::Result<Option<char>> {
let mut pollfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
// timeout of zero means that it will not block
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, 0) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
let is_ready = pollfd.revents & libc::POLLIN != 0;
if is_ready {
// if there is something to be read, take 1 byte from it
let mut buf: [u8; 1] = [0];
read_bytes(fd, &mut buf, 1)?;
Ok(Some(buf[0] as char))
} else {
//there is nothing to be read
Ok(None)
}
}
// Similar to libc::read. Read count bytes into slice buf from descriptor fd.
// If successful, return the number of bytes read.
// Will return an error if nothing was read, i.e when called at end of file.
fn read_bytes(fd: i32, buf: &mut [u8], count: u8) -> io::Result<u8> {
let read = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, count as usize) };
if read < 0 {
Err(io::Error::last_os_error())
} else if read == 0 {
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Reached end of file",
))
} else if buf[0] == b'\x03' {
Err(io::Error::new(
io::ErrorKind::Interrupted,
"read interrupted",
))
} else {
Ok(read as u8)
}
}
pub fn read_single_key() -> io::Result<Key> {
let tty_f;
let fd = unsafe {
if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
tty_f = fs::File::open("/dev/tty")?;
tty_f.as_raw_fd()
}
};
let mut termios = core::mem::MaybeUninit::uninit();
c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
let mut termios = unsafe { termios.assume_init() };
let original = termios;
unsafe { libc::cfmakeraw(&mut termios) };
c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?;
let rv = match read_single_char(fd)? {
Some('\x1b') => {
// Escape was read, keep reading in case we find a familiar key
if let Some(c1) = read_single_char(fd)? {
if c1 == '[' {
if let Some(c2) = read_single_char(fd)? {
match c2 {
'A' => Ok(Key::ArrowUp),
'B' => Ok(Key::ArrowDown),
'C' => Ok(Key::ArrowRight),
'D' => Ok(Key::ArrowLeft),
'H' => Ok(Key::Home),
'F' => Ok(Key::End),
'Z' => Ok(Key::BackTab),
_ => {
let c3 = read_single_char(fd)?;
if let Some(c3) = c3 {
if c3 == '~' {
match c2 {
'1' => Ok(Key::Home), // tmux
'2' => Ok(Key::Insert),
'3' => Ok(Key::Del),
'4' => Ok(Key::End), // tmux
'5' => Ok(Key::PageUp),
'6' => Ok(Key::PageDown),
'7' => Ok(Key::Home), // xrvt
'8' => Ok(Key::End), // xrvt
_ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
}
} else {
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
}
} else {
// \x1b[ and 1 more char
Ok(Key::UnknownEscSeq(vec![c1, c2]))
}
}
}
} else {
// \x1b[ and no more input
Ok(Key::UnknownEscSeq(vec![c1]))
}
} else {
// char after escape is not [
Ok(Key::UnknownEscSeq(vec![c1]))
}
} else {
//nothing after escape
Ok(Key::Escape)
}
}
Some(c) => {
let byte = c as u8;
let mut buf: [u8; 4] = [byte, 0, 0, 0];
if byte & 224u8 == 192u8 {
// a two byte unicode character
read_bytes(fd, &mut buf[1..], 1)?;
Ok(key_from_utf8(&buf[..2]))
} else if byte & 240u8 == 224u8 {
// a three byte unicode character
read_bytes(fd, &mut buf[1..], 2)?;
Ok(key_from_utf8(&buf[..3]))
} else if byte & 248u8 == 240u8 {
// a four byte unicode character
read_bytes(fd, &mut buf[1..], 3)?;
Ok(key_from_utf8(&buf[..4]))
} else {
Ok(match c {
'\n' | '\r' => Key::Enter,
'\x7f' => Key::Backspace,
'\t' => Key::Tab,
'\x01' => Key::Home, // Control-A (home)
'\x05' => Key::End, // Control-E (end)
'\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
_ => Key::Char(c),
})
}
}
None => {
// there is no subsequent byte ready to be read, block and wait for input
let mut pollfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
// negative timeout means that it will block indefinitely
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, -1) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
read_single_key()
}
};
c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &original) })?;
// if the user hit ^C we want to signal SIGINT to outselves.
if let Err(ref err) = rv {
if err.kind() == io::ErrorKind::Interrupted {
unsafe {
libc::raise(libc::SIGINT);
}
}
}
rv
}
pub fn key_from_utf8(buf: &[u8]) -> Key {
if let Ok(s) = str::from_utf8(buf) {
if let Some(c) = s.chars().next() {
return Key::Char(c);
}
}
Key::Unknown
}
#[cfg(not(target_os = "macos"))]
lazy_static::lazy_static! {
static ref IS_LANG_UTF8: bool = {
match std::env::var("LANG") {
Ok(lang) => lang.to_uppercase().ends_with("UTF-8"),
_ => false,
}
};
}
#[cfg(target_os = "macos")]
pub fn wants_emoji() -> bool {
true
}
#[cfg(not(target_os = "macos"))]
pub fn wants_emoji() -> bool {
*IS_LANG_UTF8
}
pub fn set_title<T: Display>(title: T) {
print!("\x1b]0;{}\x07", title);
}

View File

@ -1,900 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::env;
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use super::term::{wants_emoji, Term};
use lazy_static::lazy_static;
use super::ansi::{strip_ansi_codes, AnsiCodeIterator};
fn default_colors_enabled(out: &Term) -> bool {
(out.features().colors_supported() && &env::var("CLICOLOR").unwrap_or_else(|_| "1".into()) != "0")
|| &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0"
}
lazy_static! {
static ref STDOUT_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stdout()));
static ref STDERR_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stderr()));
}
/// Returns `true` if colors should be enabled for stdout.
///
/// This honors the [clicolors spec](http://bixense.com/clicolors/).
///
/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped.
/// * `CLICOLOR == 0`: Don't output ANSI color escape codes.
/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
#[inline]
pub fn colors_enabled() -> bool {
STDOUT_COLORS.load(Ordering::Relaxed)
}
/// Forces colorization on or off for stdout.
///
/// This overrides the default for the current process and changes the return value of the
/// `colors_enabled` function.
#[inline]
pub fn set_colors_enabled(val: bool) {
STDOUT_COLORS.store(val, Ordering::Relaxed)
}
/// Returns `true` if colors should be enabled for stderr.
///
/// This honors the [clicolors spec](http://bixense.com/clicolors/).
///
/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped.
/// * `CLICOLOR == 0`: Don't output ANSI color escape codes.
/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
#[inline]
pub fn colors_enabled_stderr() -> bool {
STDERR_COLORS.load(Ordering::Relaxed)
}
/// Forces colorization on or off for stderr.
///
/// This overrides the default for the current process and changes the return value of the
/// `colors_enabled` function.
#[inline]
pub fn set_colors_enabled_stderr(val: bool) {
STDERR_COLORS.store(val, Ordering::Relaxed)
}
/// Measure the width of a string in terminal characters.
pub fn measure_text_width(s: &str) -> usize {
str_width(&strip_ansi_codes(s))
}
/// A terminal color.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Color256(u8),
}
impl Color {
#[inline]
fn ansi_num(self) -> usize {
match self {
Color::Black => 0,
Color::Red => 1,
Color::Green => 2,
Color::Yellow => 3,
Color::Blue => 4,
Color::Magenta => 5,
Color::Cyan => 6,
Color::White => 7,
Color::Color256(x) => x as usize,
}
}
#[inline]
fn is_color256(self) -> bool {
#[allow(clippy::match_like_matches_macro)]
match self {
Color::Color256(_) => true,
_ => false,
}
}
}
/// A terminal style attribute.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub enum Attribute {
Bold,
Dim,
Italic,
Underlined,
Blink,
Reverse,
Hidden,
}
impl Attribute {
#[inline]
fn ansi_num(self) -> usize {
match self {
Attribute::Bold => 1,
Attribute::Dim => 2,
Attribute::Italic => 3,
Attribute::Underlined => 4,
Attribute::Blink => 5,
Attribute::Reverse => 7,
Attribute::Hidden => 8,
}
}
}
/// Defines the alignment for padding operations.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Alignment {
Left,
Center,
Right,
}
/// A stored style that can be applied.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Style {
fg: Option<Color>,
bg: Option<Color>,
fg_bright: bool,
bg_bright: bool,
attrs: BTreeSet<Attribute>,
force: Option<bool>,
for_stderr: bool,
}
impl Default for Style {
fn default() -> Style {
Style::new()
}
}
impl Style {
/// Returns an empty default style.
pub fn new() -> Style {
Style {
fg: None,
bg: None,
fg_bright: false,
bg_bright: false,
attrs: BTreeSet::new(),
force: None,
for_stderr: false,
}
}
/// Creates a style from a dotted string.
///
/// Effectively the string is split at each dot and then the
/// terms in between are applied. For instance `red.on_blue` will
/// create a string that is red on blue background. Unknown terms
/// are ignored.
pub fn from_dotted_str(s: &str) -> Style {
let mut rv = Style::new();
for part in s.split('.') {
rv = match part {
"black" => rv.black(),
"red" => rv.red(),
"green" => rv.green(),
"yellow" => rv.yellow(),
"blue" => rv.blue(),
"magenta" => rv.magenta(),
"cyan" => rv.cyan(),
"white" => rv.white(),
"bright" => rv.bright(),
"on_black" => rv.on_black(),
"on_red" => rv.on_red(),
"on_green" => rv.on_green(),
"on_yellow" => rv.on_yellow(),
"on_blue" => rv.on_blue(),
"on_magenta" => rv.on_magenta(),
"on_cyan" => rv.on_cyan(),
"on_white" => rv.on_white(),
"on_bright" => rv.on_bright(),
"bold" => rv.bold(),
"dim" => rv.dim(),
"underlined" => rv.underlined(),
"blink" => rv.blink(),
"reverse" => rv.reverse(),
"hidden" => rv.hidden(),
_ => {
continue;
}
};
}
rv
}
/// Apply the style to something that can be displayed.
pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
StyledObject {
style: self.clone(),
val,
}
}
/// Forces styling on or off.
///
/// This overrides the detection from `clicolors-control`.
#[inline]
pub fn force_styling(mut self, value: bool) -> Style {
self.force = Some(value);
self
}
/// Specifies that style is applying to something being written on stderr.
#[inline]
pub fn for_stderr(mut self) -> Style {
self.for_stderr = true;
self
}
/// Specifies that style is applying to something being written on stdout.
///
/// This is the default behaviour.
#[inline]
pub fn for_stdout(mut self) -> Style {
self.for_stderr = false;
self
}
/// Sets a foreground color.
#[inline]
pub fn fg(mut self, color: Color) -> Style {
self.fg = Some(color);
self
}
/// Sets a background color.
#[inline]
pub fn bg(mut self, color: Color) -> Style {
self.bg = Some(color);
self
}
/// Adds a attr.
#[inline]
pub fn attr(mut self, attr: Attribute) -> Style {
self.attrs.insert(attr);
self
}
#[inline]
pub fn black(self) -> Style {
self.fg(Color::Black)
}
#[inline]
pub fn red(self) -> Style {
self.fg(Color::Red)
}
#[inline]
pub fn green(self) -> Style {
self.fg(Color::Green)
}
#[inline]
pub fn yellow(self) -> Style {
self.fg(Color::Yellow)
}
#[inline]
pub fn blue(self) -> Style {
self.fg(Color::Blue)
}
#[inline]
pub fn magenta(self) -> Style {
self.fg(Color::Magenta)
}
#[inline]
pub fn cyan(self) -> Style {
self.fg(Color::Cyan)
}
#[inline]
pub fn white(self) -> Style {
self.fg(Color::White)
}
#[inline]
pub fn color256(self, color: u8) -> Style {
self.fg(Color::Color256(color))
}
#[inline]
pub fn bright(mut self) -> Style {
self.fg_bright = true;
self
}
#[inline]
pub fn on_black(self) -> Style {
self.bg(Color::Black)
}
#[inline]
pub fn on_red(self) -> Style {
self.bg(Color::Red)
}
#[inline]
pub fn on_green(self) -> Style {
self.bg(Color::Green)
}
#[inline]
pub fn on_yellow(self) -> Style {
self.bg(Color::Yellow)
}
#[inline]
pub fn on_blue(self) -> Style {
self.bg(Color::Blue)
}
#[inline]
pub fn on_magenta(self) -> Style {
self.bg(Color::Magenta)
}
#[inline]
pub fn on_cyan(self) -> Style {
self.bg(Color::Cyan)
}
#[inline]
pub fn on_white(self) -> Style {
self.bg(Color::White)
}
#[inline]
pub fn on_color256(self, color: u8) -> Style {
self.bg(Color::Color256(color))
}
#[inline]
pub fn on_bright(mut self) -> Style {
self.bg_bright = true;
self
}
#[inline]
pub fn bold(self) -> Style {
self.attr(Attribute::Bold)
}
#[inline]
pub fn dim(self) -> Style {
self.attr(Attribute::Dim)
}
#[inline]
pub fn italic(self) -> Style {
self.attr(Attribute::Italic)
}
#[inline]
pub fn underlined(self) -> Style {
self.attr(Attribute::Underlined)
}
#[inline]
pub fn blink(self) -> Style {
self.attr(Attribute::Blink)
}
#[inline]
pub fn reverse(self) -> Style {
self.attr(Attribute::Reverse)
}
#[inline]
pub fn hidden(self) -> Style {
self.attr(Attribute::Hidden)
}
}
/// Wraps an object for formatting for styling.
///
/// Example:
///
/// ```rust,no_run
/// # use console::style;
/// format!("Hello {}", style("World").cyan());
/// ```
///
/// This is a shortcut for making a new style and applying it
/// to a value:
///
/// ```rust,no_run
/// # use console::Style;
/// format!("Hello {}", Style::new().cyan().apply_to("World"));
/// ```
pub fn style<D>(val: D) -> StyledObject<D> {
Style::new().apply_to(val)
}
/// A formatting wrapper that can be styled for a terminal.
#[derive(Clone)]
pub struct StyledObject<D> {
style: Style,
val: D,
}
impl<D> StyledObject<D> {
/// Forces styling on or off.
///
/// This overrides the detection from `clicolors-control`.
#[inline]
pub fn force_styling(mut self, value: bool) -> StyledObject<D> {
self.style = self.style.force_styling(value);
self
}
/// Specifies that style is applying to something being written on stderr
#[inline]
pub fn for_stderr(mut self) -> StyledObject<D> {
self.style = self.style.for_stderr();
self
}
/// Specifies that style is applying to something being written on stdout
///
/// This is the default
#[inline]
pub fn for_stdout(mut self) -> StyledObject<D> {
self.style = self.style.for_stdout();
self
}
/// Sets a foreground color.
#[inline]
pub fn fg(mut self, color: Color) -> StyledObject<D> {
self.style = self.style.fg(color);
self
}
/// Sets a background color.
#[inline]
pub fn bg(mut self, color: Color) -> StyledObject<D> {
self.style = self.style.bg(color);
self
}
/// Adds a attr.
#[inline]
pub fn attr(mut self, attr: Attribute) -> StyledObject<D> {
self.style = self.style.attr(attr);
self
}
#[inline]
pub fn black(self) -> StyledObject<D> {
self.fg(Color::Black)
}
#[inline]
pub fn red(self) -> StyledObject<D> {
self.fg(Color::Red)
}
#[inline]
pub fn green(self) -> StyledObject<D> {
self.fg(Color::Green)
}
#[inline]
pub fn yellow(self) -> StyledObject<D> {
self.fg(Color::Yellow)
}
#[inline]
pub fn blue(self) -> StyledObject<D> {
self.fg(Color::Blue)
}
#[inline]
pub fn magenta(self) -> StyledObject<D> {
self.fg(Color::Magenta)
}
#[inline]
pub fn cyan(self) -> StyledObject<D> {
self.fg(Color::Cyan)
}
#[inline]
pub fn white(self) -> StyledObject<D> {
self.fg(Color::White)
}
#[inline]
pub fn color256(self, color: u8) -> StyledObject<D> {
self.fg(Color::Color256(color))
}
#[inline]
pub fn bright(mut self) -> StyledObject<D> {
self.style = self.style.bright();
self
}
#[inline]
pub fn on_black(self) -> StyledObject<D> {
self.bg(Color::Black)
}
#[inline]
pub fn on_red(self) -> StyledObject<D> {
self.bg(Color::Red)
}
#[inline]
pub fn on_green(self) -> StyledObject<D> {
self.bg(Color::Green)
}
#[inline]
pub fn on_yellow(self) -> StyledObject<D> {
self.bg(Color::Yellow)
}
#[inline]
pub fn on_blue(self) -> StyledObject<D> {
self.bg(Color::Blue)
}
#[inline]
pub fn on_magenta(self) -> StyledObject<D> {
self.bg(Color::Magenta)
}
#[inline]
pub fn on_cyan(self) -> StyledObject<D> {
self.bg(Color::Cyan)
}
#[inline]
pub fn on_white(self) -> StyledObject<D> {
self.bg(Color::White)
}
#[inline]
pub fn on_color256(self, color: u8) -> StyledObject<D> {
self.bg(Color::Color256(color))
}
#[inline]
pub fn on_bright(mut self) -> StyledObject<D> {
self.style = self.style.on_bright();
self
}
#[inline]
pub fn bold(self) -> StyledObject<D> {
self.attr(Attribute::Bold)
}
#[inline]
pub fn dim(self) -> StyledObject<D> {
self.attr(Attribute::Dim)
}
#[inline]
pub fn italic(self) -> StyledObject<D> {
self.attr(Attribute::Italic)
}
#[inline]
pub fn underlined(self) -> StyledObject<D> {
self.attr(Attribute::Underlined)
}
#[inline]
pub fn blink(self) -> StyledObject<D> {
self.attr(Attribute::Blink)
}
#[inline]
pub fn reverse(self) -> StyledObject<D> {
self.attr(Attribute::Reverse)
}
#[inline]
pub fn hidden(self) -> StyledObject<D> {
self.attr(Attribute::Hidden)
}
}
macro_rules! impl_fmt {
($name:ident) => {
impl<D: fmt::$name> fmt::$name for StyledObject<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut reset = false;
if self
.style
.force
.unwrap_or_else(|| match self.style.for_stderr {
true => colors_enabled_stderr(),
false => colors_enabled(),
})
{
if let Some(fg) = self.style.fg {
if fg.is_color256() {
write!(f, "\x1b[38;5;{}m", fg.ansi_num())?;
} else if self.style.fg_bright {
write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?;
} else {
write!(f, "\x1b[{}m", fg.ansi_num() + 30)?;
}
reset = true;
}
if let Some(bg) = self.style.bg {
if bg.is_color256() {
write!(f, "\x1b[48;5;{}m", bg.ansi_num())?;
} else if self.style.bg_bright {
write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?;
} else {
write!(f, "\x1b[{}m", bg.ansi_num() + 40)?;
}
reset = true;
}
for attr in &self.style.attrs {
write!(f, "\x1b[{}m", attr.ansi_num())?;
reset = true;
}
}
fmt::$name::fmt(&self.val, f)?;
if reset {
write!(f, "\x1b[0m")?;
}
Ok(())
}
}
};
}
impl_fmt!(Binary);
impl_fmt!(Debug);
impl_fmt!(Display);
impl_fmt!(LowerExp);
impl_fmt!(LowerHex);
impl_fmt!(Octal);
impl_fmt!(Pointer);
impl_fmt!(UpperExp);
impl_fmt!(UpperHex);
/// "Intelligent" emoji formatter.
///
/// This struct intelligently wraps an emoji so that it is rendered
/// only on systems that want emojis and renders a fallback on others.
///
/// Example:
///
/// ```rust
/// use console::Emoji;
/// println!("[3/4] {}Downloading ...", Emoji("🚚 ", ""));
/// println!("[4/4] {} Done!", Emoji("✨", ":-)"));
/// ```
#[derive(Copy, Clone)]
pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str);
impl<'a, 'b> Emoji<'a, 'b> {
pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> {
Emoji(emoji, fallback)
}
}
impl<'a, 'b> fmt::Display for Emoji<'a, 'b> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if wants_emoji() {
write!(f, "{}", self.0)
} else {
write!(f, "{}", self.1)
}
}
}
fn str_width(s: &str) -> usize {
#[cfg(feature = "unicode-width")]
{
use unicode_width::UnicodeWidthStr;
s.width()
}
#[cfg(not(feature = "unicode-width"))]
{
s.chars().count()
}
}
fn char_width(c: char) -> usize {
#[cfg(feature = "unicode-width")]
{
use unicode_width::UnicodeWidthChar;
c.width().unwrap_or(0)
}
#[cfg(not(feature = "unicode-width"))]
{
let _c = c;
1
}
}
/// Truncates a string to a certain number of characters.
///
/// This ensures that escape codes are not screwed up in the process.
/// If the maximum length is hit the string will be truncated but
/// escapes code will still be honored. If truncation takes place
/// the tail string will be appended.
pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
{
use std::cmp::Ordering;
let mut iter = AnsiCodeIterator::new(s);
let mut length = 0;
let mut rv = None;
while let Some(item) = iter.next() {
match item {
(s, false) => {
if rv.is_none() {
if str_width(s) + length > width - str_width(tail) {
let ts = iter.current_slice();
let mut s_byte = 0;
let mut s_width = 0;
let rest_width = width - str_width(tail) - length;
for c in s.chars() {
s_byte += c.len_utf8();
s_width += char_width(c);
match s_width.cmp(&rest_width) {
Ordering::Equal => break,
Ordering::Greater => {
s_byte -= c.len_utf8();
break;
}
Ordering::Less => continue,
}
}
let idx = ts.len() - s.len() + s_byte;
let mut buf = ts[..idx].to_string();
buf.push_str(tail);
rv = Some(buf);
}
length += str_width(s);
}
}
(s, true) => {
if rv.is_some() {
rv.as_mut().unwrap().push_str(s);
}
}
}
}
if let Some(buf) = rv {
Cow::Owned(buf)
} else {
Cow::Borrowed(s)
}
}
}
/// Pads a string to fill a certain number of characters.
///
/// This will honor ansi codes correctly and allows you to align a string
/// on the left, right or centered. Additionally truncation can be enabled
/// by setting `truncate` to a string that should be used as a truncation
/// marker.
pub fn pad_str<'a>(
s: &'a str,
width: usize,
align: Alignment,
truncate: Option<&str>,
) -> Cow<'a, str> {
pad_str_with(s, width, align, truncate, ' ')
}
/// Pads a string with specific padding to fill a certain number of characters.
///
/// This will honor ansi codes correctly and allows you to align a string
/// on the left, right or centered. Additionally truncation can be enabled
/// by setting `truncate` to a string that should be used as a truncation
/// marker.
pub fn pad_str_with<'a>(
s: &'a str,
width: usize,
align: Alignment,
truncate: Option<&str>,
pad: char,
) -> Cow<'a, str> {
let cols = measure_text_width(s);
if cols >= width {
return match truncate {
None => Cow::Borrowed(s),
Some(tail) => truncate_str(s, width, tail),
};
}
let diff = width - cols;
let (left_pad, right_pad) = match align {
Alignment::Left => (0, diff),
Alignment::Right => (diff, 0),
Alignment::Center => (diff / 2, diff - diff / 2),
};
let mut rv = String::new();
for _ in 0..left_pad {
rv.push(pad);
}
rv.push_str(s);
for _ in 0..right_pad {
rv.push(pad);
}
Cow::Owned(rv)
}
#[test]
fn test_text_width() {
let s = style("foo")
.red()
.on_black()
.bold()
.force_styling(true)
.to_string();
assert_eq!(measure_text_width(&s), 3);
}
#[test]
#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))]
fn test_truncate_str() {
let s = format!("foo {}", style("bar").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 5, ""),
&format!("foo {}", style("b").red().force_styling(true))
);
let s = format!("foo {}", style("bar").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 5, "!"),
&format!("foo {}", style("!").red().force_styling(true))
);
let s = format!("foo {} baz", style("bar").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 10, "..."),
&format!("foo {}...", style("bar").red().force_styling(true))
);
let s = format!("foo {}", style("バー").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 5, ""),
&format!("foo {}", style("").red().force_styling(true))
);
let s = format!("foo {}", style("バー").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 6, ""),
&format!("foo {}", style("").red().force_styling(true))
);
}
#[test]
fn test_truncate_str_no_ansi() {
assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
}
#[test]
fn test_pad_str() {
assert_eq!(pad_str("foo", 7, Alignment::Center, None), " foo ");
assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo ");
assert_eq!(pad_str("foo", 7, Alignment::Right, None), " foo");
assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo");
assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar");
assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo");
assert_eq!(
pad_str("foobarbaz", 6, Alignment::Left, Some("...")),
"foo..."
);
}
#[test]
fn test_pad_str_with() {
assert_eq!(
pad_str_with("foo", 7, Alignment::Center, None, '#'),
"##foo##"
);
assert_eq!(
pad_str_with("foo", 7, Alignment::Left, None, '#'),
"foo####"
);
assert_eq!(
pad_str_with("foo", 7, Alignment::Right, None, '#'),
"####foo"
);
assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo");
assert_eq!(
pad_str_with("foobar", 3, Alignment::Left, None, '#'),
"foobar"
);
assert_eq!(
pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'),
"foo"
);
assert_eq!(
pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'),
"foo..."
);
}

View File

@ -1,49 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::fmt::Display;
use std::io;
use super::kb::Key;
use super::term::Term;
pub use super::common_term::*;
pub const DEFAULT_WIDTH: u16 = 80;
#[inline]
pub fn is_a_terminal(_out: &Term) -> bool {
false
}
#[inline]
pub fn is_a_color_terminal(_out: &Term) -> bool {
false
}
#[inline]
pub fn terminal_size(_out: &Term) -> Option<(u16, u16)> {
None
}
pub fn read_secure() -> io::Result<String> {
Err(io::Error::new(
io::ErrorKind::Other,
"unsupported operation",
))
}
pub fn read_single_key() -> io::Result<Key> {
Err(io::Error::new(
io::ErrorKind::Other,
"unsupported operation",
))
}
#[inline]
pub fn wants_emoji() -> bool {
false
}
pub fn set_title<T: Display>(_title: T) {}

View File

@ -1,600 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::cmp;
use std::env;
use std::ffi::OsStr;
use std::fmt::Display;
use std::io;
use std::iter::once;
use std::mem;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::AsRawHandle;
use std::slice;
use std::{char, mem::MaybeUninit};
use encode_unicode::error::InvalidUtf16Tuple;
use encode_unicode::CharExt;
#[cfg(feature = "windows-console-colors")]
use regex::Regex;
use winapi::ctypes::c_void;
use winapi::shared::minwindef::DWORD;
use winapi::shared::minwindef::MAX_PATH;
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
use winapi::um::consoleapi::{GetNumberOfConsoleInputEvents, ReadConsoleInputW};
use winapi::um::fileapi::FILE_NAME_INFO;
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::minwinbase::FileNameInfo;
use winapi::um::processenv::GetStdHandle;
use winapi::um::winbase::GetFileInformationByHandleEx;
use winapi::um::winbase::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE};
use winapi::um::wincon::{
FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetConsoleCursorInfo,
GetConsoleScreenBufferInfo, SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleTitleW,
CONSOLE_CURSOR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, KEY_EVENT,
KEY_EVENT_RECORD,
};
use winapi::um::winnt::{CHAR, HANDLE, INT, WCHAR};
#[cfg(feature = "windows-console-colors")]
use winapi_util::console::{Color, Console, Intense};
use super::common_term;
use super::kb::Key;
use super::term::{Term, TermTarget};
#[cfg(feature = "windows-console-colors")]
lazy_static::lazy_static! {
static ref INTENSE_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)8;5;(8|9|1[0-5])m").unwrap();
static ref NORMAL_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)([0-7])m").unwrap();
static ref ATTR_RE: Regex = Regex::new(r"\x1b\[([1-8])m").unwrap();
}
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x4;
pub const DEFAULT_WIDTH: u16 = 79;
pub fn as_handle(term: &Term) -> HANDLE {
// convert between winapi::um::winnt::HANDLE and std::os::windows::raw::HANDLE
// which are both c_void. would be nice to find a better way to do this
term.as_raw_handle() as HANDLE
}
pub fn is_a_terminal(out: &Term) -> bool {
let (fd, others) = match out.target() {
TermTarget::Stdout => (STD_OUTPUT_HANDLE, [STD_INPUT_HANDLE, STD_ERROR_HANDLE]),
TermTarget::Stderr => (STD_ERROR_HANDLE, [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE]),
};
if unsafe { console_on_any(&[fd]) } {
// False positives aren't possible. If we got a console then
// we definitely have a tty on stdin.
return true;
}
// At this point, we *could* have a false negative. We can determine that
// this is true negative if we can detect the presence of a console on
// any of the other streams. If another stream has a console, then we know
// we're in a Windows console and can therefore trust the negative.
if unsafe { console_on_any(&others) } {
return false;
}
msys_tty_on(out)
}
pub fn is_a_color_terminal(out: &Term) -> bool {
if !is_a_terminal(out) {
return false;
}
if msys_tty_on(out) {
return match env::var("TERM") {
Ok(term) => term != "dumb",
Err(_) => true,
};
}
enable_ansi_on(out)
}
fn enable_ansi_on(out: &Term) -> bool {
unsafe {
let handle = as_handle(out);
let mut dw_mode = 0;
if GetConsoleMode(handle, &mut dw_mode) == 0 {
return false;
}
dw_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if SetConsoleMode(handle, dw_mode) == 0 {
return false;
}
true
}
}
unsafe fn console_on_any(fds: &[DWORD]) -> bool {
for &fd in fds {
let mut out = 0;
let handle = GetStdHandle(fd);
if GetConsoleMode(handle, &mut out) != 0 {
return true;
}
}
false
}
#[inline]
pub fn terminal_size(out: &Term) -> Option<(u16, u16)> {
terminal_size::terminal_size_using_handle(out.as_raw_handle()).map(|x| ((x.1).0, (x.0).0))
}
pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_to(out, x, y);
}
if let Some((hand, _)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
SetConsoleCursorPosition(
hand,
COORD {
X: x as i16,
Y: y as i16,
},
);
}
}
Ok(())
}
pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_up(out, n);
}
if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize - n)?;
}
Ok(())
}
pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_down(out, n);
}
if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize + n)?;
}
Ok(())
}
pub fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_left(out, n);
}
if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
move_cursor_to(
out,
csbi.dwCursorPosition.X as usize - n,
csbi.dwCursorPosition.Y as usize,
)?;
}
Ok(())
}
pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_right(out, n);
}
if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
move_cursor_to(
out,
csbi.dwCursorPosition.X as usize + n,
csbi.dwCursorPosition.Y as usize,
)?;
}
Ok(())
}
pub fn clear_line(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::clear_line(out);
}
if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
let width = csbi.srWindow.Right - csbi.srWindow.Left;
let pos = COORD {
X: 0,
Y: csbi.dwCursorPosition.Y,
};
let mut written = 0;
FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written);
FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written);
SetConsoleCursorPosition(hand, pos);
}
}
Ok(())
}
pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::clear_chars(out, n);
}
if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
let width = cmp::min(csbi.dwCursorPosition.X, n as i16);
let pos = COORD {
X: csbi.dwCursorPosition.X - width,
Y: csbi.dwCursorPosition.Y,
};
let mut written = 0;
FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written);
FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written);
SetConsoleCursorPosition(hand, pos);
}
}
Ok(())
}
pub fn clear_screen(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::clear_screen(out);
}
if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
let cells = csbi.dwSize.X as DWORD * csbi.dwSize.Y as DWORD; // as DWORD, or else this causes stack overflows.
let pos = COORD { X: 0, Y: 0 };
let mut written = 0;
FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as DWORD no longer needed.
FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written);
SetConsoleCursorPosition(hand, pos);
}
}
Ok(())
}
pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::clear_to_end_of_screen(out);
}
if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
let bottom = csbi.srWindow.Right as DWORD * csbi.srWindow.Bottom as DWORD;
let cells = bottom - (csbi.dwCursorPosition.X as DWORD * csbi.dwCursorPosition.Y as DWORD); // as DWORD, or else this causes stack overflows.
let pos = COORD {
X: 0,
Y: csbi.dwCursorPosition.Y,
};
let mut written = 0;
FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as DWORD no longer needed.
FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written);
SetConsoleCursorPosition(hand, pos);
}
}
Ok(())
}
pub fn show_cursor(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::show_cursor(out);
}
if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) {
unsafe {
cci.bVisible = 1;
SetConsoleCursorInfo(hand, &cci);
}
}
Ok(())
}
pub fn hide_cursor(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::hide_cursor(out);
}
if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) {
unsafe {
cci.bVisible = 0;
SetConsoleCursorInfo(hand, &cci);
}
}
Ok(())
}
fn get_console_screen_buffer_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_SCREEN_BUFFER_INFO)> {
let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() };
match unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } {
0 => None,
_ => Some((hand, csbi)),
}
}
fn get_console_cursor_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_CURSOR_INFO)> {
let mut cci: CONSOLE_CURSOR_INFO = unsafe { mem::zeroed() };
match unsafe { GetConsoleCursorInfo(hand, &mut cci) } {
0 => None,
_ => Some((hand, cci)),
}
}
pub fn key_from_key_code(code: INT) -> Key {
match code {
winapi::um::winuser::VK_LEFT => Key::ArrowLeft,
winapi::um::winuser::VK_RIGHT => Key::ArrowRight,
winapi::um::winuser::VK_UP => Key::ArrowUp,
winapi::um::winuser::VK_DOWN => Key::ArrowDown,
winapi::um::winuser::VK_RETURN => Key::Enter,
winapi::um::winuser::VK_ESCAPE => Key::Escape,
winapi::um::winuser::VK_BACK => Key::Backspace,
winapi::um::winuser::VK_TAB => Key::Tab,
winapi::um::winuser::VK_HOME => Key::Home,
winapi::um::winuser::VK_END => Key::End,
winapi::um::winuser::VK_DELETE => Key::Del,
winapi::um::winuser::VK_SHIFT => Key::Shift,
_ => Key::Unknown,
}
}
pub fn read_secure() -> io::Result<String> {
let mut rv = String::new();
loop {
match read_single_key()? {
Key::Enter => {
break;
}
Key::Char('\x08') => {
if !rv.is_empty() {
let new_len = rv.len() - 1;
rv.truncate(new_len);
}
}
Key::Char(c) => {
rv.push(c);
}
_ => {}
}
}
Ok(rv)
}
pub fn read_single_key() -> io::Result<Key> {
let key_event = read_key_event()?;
let unicode_char = unsafe { *key_event.uChar.UnicodeChar() };
if unicode_char == 0 {
Ok(key_from_key_code(key_event.wVirtualKeyCode as INT))
} else {
// This is a unicode character, in utf-16. Try to decode it by itself.
match char::from_utf16_tuple((unicode_char, None)) {
Ok(c) => {
// Maintain backward compatibility. The previous implementation (_getwch()) would return
// a special keycode for `Enter`, while ReadConsoleInputW() prefers to use '\r'.
if c == '\r' {
Ok(Key::Enter)
} else if c == '\x08' {
Ok(Key::Backspace)
} else if c == '\x1B' {
Ok(Key::Escape)
} else {
Ok(Key::Char(c))
}
}
// This is part of a surrogate pair. Try to read the second half.
Err(InvalidUtf16Tuple::MissingSecond) => {
// Confirm that there is a next character to read.
if get_key_event_count()? == 0 {
let message = format!(
"Read invlid utf16 {}: {}",
unicode_char,
InvalidUtf16Tuple::MissingSecond
);
return Err(io::Error::new(io::ErrorKind::InvalidData, message));
}
// Read the next character.
let next_event = read_key_event()?;
let next_surrogate = unsafe { *next_event.uChar.UnicodeChar() };
// Attempt to decode it.
match char::from_utf16_tuple((unicode_char, Some(next_surrogate))) {
Ok(c) => Ok(Key::Char(c)),
// Return an InvalidData error. This is the recommended value for UTF-related I/O errors.
// (This error is given when reading a non-UTF8 file into a String, for example.)
Err(e) => {
let message = format!(
"Read invalid surrogate pair ({}, {}): {}",
unicode_char, next_surrogate, e
);
Err(io::Error::new(io::ErrorKind::InvalidData, message))
}
}
}
// Return an InvalidData error. This is the recommended value for UTF-related I/O errors.
// (This error is given when reading a non-UTF8 file into a String, for example.)
Err(e) => {
let message = format!("Read invalid utf16 {}: {}", unicode_char, e);
Err(io::Error::new(io::ErrorKind::InvalidData, message))
}
}
}
}
fn get_stdin_handle() -> io::Result<HANDLE> {
let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
if handle == INVALID_HANDLE_VALUE {
Err(io::Error::last_os_error())
} else {
Ok(handle)
}
}
/// Get the number of pending events in the ReadConsoleInput queue. Note that while
/// these aren't necessarily key events, the only way that multiple events can be
/// put into the queue simultaneously is if a unicode character spanning multiple u16's
/// is read.
///
/// Therefore, this is accurate as long as at least one KEY_EVENT has already been read.
fn get_key_event_count() -> io::Result<DWORD> {
let handle = get_stdin_handle()?;
let mut event_count: DWORD = unsafe { mem::zeroed() };
let success = unsafe { GetNumberOfConsoleInputEvents(handle, &mut event_count) };
if success == 0 {
Err(io::Error::last_os_error())
} else {
Ok(event_count)
}
}
fn read_key_event() -> io::Result<KEY_EVENT_RECORD> {
let handle = get_stdin_handle()?;
let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() };
let mut events_read: DWORD = unsafe { mem::zeroed() };
let mut key_event: KEY_EVENT_RECORD;
loop {
let success = unsafe { ReadConsoleInputW(handle, &mut buffer, 1, &mut events_read) };
if success == 0 {
return Err(io::Error::last_os_error());
}
if events_read == 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
"ReadConsoleInput returned no events, instead of waiting for an event",
));
}
if events_read == 1 && buffer.EventType != KEY_EVENT {
// This isn't a key event; ignore it.
continue;
}
key_event = unsafe { mem::transmute(buffer.Event) };
if key_event.bKeyDown == 0 {
// This is a key being released; ignore it.
continue;
}
return Ok(key_event);
}
}
pub fn wants_emoji() -> bool {
// If WT_SESSION is set, we can assume we're running in the nne
// Windows Terminal. The correct way to detect this is not available
// yet. See https://github.com/microsoft/terminal/issues/1040
env::var("WT_SESSION").is_ok()
}
/// Returns true if there is an MSYS tty on the given handle.
pub fn msys_tty_on(term: &Term) -> bool {
let handle = term.as_raw_handle();
unsafe {
// Check whether the Windows 10 native pty is enabled
{
let mut out = MaybeUninit::uninit();
let res = GetConsoleMode(handle as *mut _, out.as_mut_ptr());
if res != 0 // If res is true then out was initialized.
&& (out.assume_init() & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
== ENABLE_VIRTUAL_TERMINAL_PROCESSING
{
return true;
}
}
let size = mem::size_of::<FILE_NAME_INFO>();
let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()];
let res = GetFileInformationByHandleEx(
handle as *mut _,
FileNameInfo,
&mut *name_info_bytes as *mut _ as *mut c_void,
name_info_bytes.len() as u32,
);
if res == 0 {
return false;
}
let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO);
let s = slice::from_raw_parts(
name_info.FileName.as_ptr(),
name_info.FileNameLength as usize / 2,
);
let name = String::from_utf16_lossy(s);
// This checks whether 'pty' exists in the file name, which indicates that
// a pseudo-terminal is attached. To mitigate against false positives
// (e.g., an actual file name that contains 'pty'), we also require that
// either the strings 'msys-' or 'cygwin-' are in the file name as well.)
let is_msys = name.contains("msys-") || name.contains("cygwin-");
let is_pty = name.contains("-pty");
is_msys && is_pty
}
}
pub fn set_title<T: Display>(title: T) {
let buffer: Vec<u16> = OsStr::new(&format!("{}", title))
.encode_wide()
.chain(once(0))
.collect();
unsafe {
SetConsoleTitleW(buffer.as_ptr());
}
}
#[cfg(feature = "windows-console-colors")]
pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<()> {
use super::ansi::AnsiCodeIterator;
use std::str::from_utf8;
let s = from_utf8(bytes).expect("data to be printed is not an ansi string");
let mut iter = AnsiCodeIterator::new(s);
while !iter.rest_slice().is_empty() {
if let Some((part, is_esc)) = iter.next() {
if !is_esc {
out.write_through_common(part.as_bytes())?;
} else if part == "\x1b[0m" {
con.reset()?;
} else if let Some(cap) = INTENSE_COLOR_RE.captures(part) {
let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
match cap.get(1).unwrap().as_str() {
"3" => con.fg(Intense::Yes, color)?,
"4" => con.bg(Intense::Yes, color)?,
_ => unreachable!(),
};
} else if let Some(cap) = NORMAL_COLOR_RE.captures(part) {
let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
match cap.get(1).unwrap().as_str() {
"3" => con.fg(Intense::No, color)?,
"4" => con.bg(Intense::No, color)?,
_ => unreachable!(),
};
} else if !ATTR_RE.is_match(part) {
out.write_through_common(part.as_bytes())?;
}
}
}
Ok(())
}
#[cfg(feature = "windows-console-colors")]
fn get_color_from_ansi(ansi: &str) -> Color {
match ansi {
"0" | "8" => Color::Black,
"1" | "9" => Color::Red,
"2" | "10" => Color::Green,
"3" | "11" => Color::Yellow,
"4" | "12" => Color::Blue,
"5" | "13" => Color::Magenta,
"6" | "14" => Color::Cyan,
"7" | "15" => Color::White,
_ => unreachable!(),
}
}

View File

@ -1,131 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
env,
ffi::{OsStr, OsString},
fs, io,
io::{Read, Write},
process,
};
/// Launches the default editor to edit a string.
///
/// ## Example
///
/// ```rust,no_run
/// use dialoguer::Editor;
///
/// if let Some(rv) = Editor::new().edit("Enter a commit message").unwrap() {
/// println!("Your message:");
/// println!("{}", rv);
/// } else {
/// println!("Abort!");
/// }
/// ```
pub struct Editor {
editor: OsString,
extension: String,
require_save: bool,
trim_newlines: bool,
}
fn get_default_editor() -> OsString {
if let Some(prog) = env::var_os("VISUAL") {
return prog;
}
if let Some(prog) = env::var_os("EDITOR") {
return prog;
}
if cfg!(windows) {
"notepad.exe".into()
} else {
"vi".into()
}
}
impl Default for Editor {
fn default() -> Editor {
Editor::new()
}
}
impl Editor {
/// Creates a new editor.
pub fn new() -> Editor {
Editor {
editor: get_default_editor(),
extension: ".txt".into(),
require_save: true,
trim_newlines: true,
}
}
/// Sets a specific editor executable.
pub fn executable<S: AsRef<OsStr>>(&mut self, val: S) -> &mut Editor {
self.editor = val.as_ref().into();
self
}
/// Sets a specific extension
pub fn extension(&mut self, val: &str) -> &mut Editor {
self.extension = val.into();
self
}
/// Enables or disables the save requirement.
pub fn require_save(&mut self, val: bool) -> &mut Editor {
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 Editor {
self.trim_newlines = val;
self
}
/// Launches the editor to edit a string.
///
/// Returns `None` if the file was not saved or otherwise the
/// entered text.
pub fn edit(&self, s: &str) -> io::Result<Option<String>> {
let mut f = tempfile::Builder::new()
.prefix("edit-")
.suffix(&self.extension)
.rand_bytes(12)
.tempfile()?;
f.write_all(s.as_bytes())?;
f.flush()?;
let ts = fs::metadata(f.path())?.modified()?;
let s: String = self.editor.clone().into_string().unwrap();
let mut iterator = s.split(' ');
let cmd = iterator.next().unwrap();
let args: Vec<&str> = iterator.collect();
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))
}
}

View File

@ -1,34 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! 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)
//! * Other kind of prompts
//! * Editor launching
pub use edit::Editor;
pub use prompts::{
confirm::Confirm, input::Input, multi_select::MultiSelect, password::Password, select::Select,
sort::Sort,
};
pub use validate::Validator;
mod edit;
mod prompts;
pub mod theme;
mod validate;

View File

@ -1,259 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::io;
use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
use crate::console::{Key, Term};
/// Renders a confirm prompt.
///
/// ## Example usage
///
/// ```rust,no_run
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// use dialoguer::Confirm;
///
/// if Confirm::new().with_prompt("Do you want to continue?").interact()? {
/// println!("Looks like you want to continue");
/// } else {
/// println!("nevermind then :(");
/// }
/// # Ok(()) } fn main() { test().unwrap(); }
/// ```
pub struct Confirm<'a> {
prompt: String,
default: Option<bool>,
show_default: bool,
wait_for_newline: bool,
theme: &'a dyn Theme,
}
impl<'a> Default for Confirm<'a> {
fn default() -> Confirm<'a> {
Confirm::new()
}
}
impl<'a> Confirm<'a> {
/// Creates a confirm prompt.
pub fn new() -> Confirm<'static> {
Confirm::with_theme(&SimpleTheme)
}
/// 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) -> Confirm<'a> {
Confirm {
prompt: "".into(),
default: None,
show_default: true,
wait_for_newline: false,
theme,
}
}
/// Sets the confirm prompt.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Confirm<'a> {
self.prompt = prompt.into();
self
}
#[deprecated(note = "Use with_prompt() instead", since = "0.6.0")]
#[inline]
pub fn with_text(&mut self, text: &str) -> &mut Confirm<'a> {
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 Confirm<'a> {
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 Confirm<'a> {
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 Confirm<'a> {
self.show_default = val;
self
}
/// Enables user interaction and returns the result.
///
/// If the user confirms the result is `true`, `false` if declines or default (configured in [default](#method.default)) if pushes enter.
/// Otherwise function discards input waiting for valid one.
///
/// The dialog is rendered on stderr.
pub fn interact(&self) -> io::Result<bool> {
self.interact_on(&Term::stderr())
}
/// Enables user interaction and returns the result.
///
/// This method is similar to [interact_on_opt](#method.interact_on_opt) except for the fact that it does not allow selection of the terminal.
/// The dialog is rendered on stderr.
/// Result contains `Some(bool)` if user answered "yes" or "no" or `None` if user cancelled with 'Esc' or 'q'.
pub fn interact_opt(&self) -> io::Result<Option<bool>> {
self.interact_on_opt(&Term::stderr())
}
/// Like [interact](#method.interact) but allows a specific terminal to be set.
///
/// ## Examples
///
/// ```rust,no_run
/// use dialoguer::Confirm;
/// use console::Term;
///
/// # fn main() -> std::io::Result<()> {
/// let proceed = Confirm::new()
/// .with_prompt("Do you wish to continue?")
/// .interact_on(&Term::stderr())?;
/// # Ok(())
/// # }
/// ```
pub fn interact_on(&self, term: &Term) -> io::Result<bool> {
self
._interact_on(term, false)?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))
}
/// Like [interact_opt](#method.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(())
/// }
/// ```
pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<bool>> {
self._interact_on(term, true)
}
fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<bool>> {
let mut render = TermThemeRenderer::new(term, self.theme);
let default_if_show = if self.show_default {
self.default
} else {
None
};
render.confirm_prompt(&self.prompt, default_if_show)?;
term.hide_cursor()?;
term.flush()?;
let rv;
if self.wait_for_newline {
// Waits for user input and for the user to hit the Enter key
// before validation.
let mut value = default_if_show;
loop {
let input = term.read_key()?;
match input {
Key::Char('y') | Key::Char('Y') => {
value = Some(true);
}
Key::Char('n') | Key::Char('N') => {
value = Some(false);
}
Key::Enter => {
if !allow_quit {
value = value.or(self.default);
}
if value.is_some() || allow_quit {
rv = value;
break;
}
continue;
}
Key::Escape | Key::Char('q') if allow_quit => {
value = None;
}
_ => {
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,
_ => {
continue;
}
};
rv = value;
break;
}
}
term.clear_line()?;
render.confirm_prompt_selection(&self.prompt, rv)?;
term.show_cursor()?;
term.flush()?;
Ok(rv)
}
}

View File

@ -1,361 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
fmt::{Debug, Display},
io, iter,
str::FromStr,
};
use super::super::{
theme::{SimpleTheme, TermThemeRenderer, Theme},
validate::Validator,
};
use crate::console::{Key, Term};
/// Renders an input prompt.
///
/// ## Example usage
///
/// ```rust,no_run
/// use dialoguer::Input;
///
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// let input : String = Input::new()
/// .with_prompt("Tea or coffee?")
/// .with_initial_text("Yes")
/// .default("No".into())
/// .interact_text()?;
/// # Ok(())
/// # }
/// ```
/// It can also be used with turbofish notation:
///
/// ```rust,no_run
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// # use dialoguer::Input;
/// let input = Input::<String>::new()
/// .interact_text()?;
/// # Ok(())
/// # }
/// ```
pub struct Input<'a, T> {
prompt: String,
default: Option<T>,
show_default: bool,
initial_text: Option<String>,
theme: &'a dyn Theme,
permit_empty: bool,
validator: Option<Box<dyn FnMut(&T) -> Option<String> + 'a>>,
}
impl<'a, T> Default for Input<'a, T>
where
T: Clone + FromStr + Display,
T::Err: Display + Debug,
{
fn default() -> Input<'a, T> {
Input::new()
}
}
impl<'a, T> Input<'a, T>
where
T: Clone + FromStr + Display,
T::Err: Display + Debug,
{
/// Creates an input prompt.
pub fn new() -> Input<'a, T> {
Input::with_theme(&SimpleTheme)
}
/// Creates an input prompt with a specific theme.
pub fn with_theme(theme: &'a dyn Theme) -> Input<'a, T> {
Input {
prompt: "".into(),
default: None,
show_default: true,
initial_text: None,
theme,
permit_empty: false,
validator: None,
}
}
/// Sets the input prompt.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Input<'a, T> {
self.prompt = prompt.into();
self
}
/// Sets initial text that user can accept or erase.
pub fn with_initial_text<S: Into<String>>(&mut self, val: S) -> &mut Input<'a, T> {
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 Input<'a, T> {
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 Input<'a, T> {
self.permit_empty = val;
self
}
/// Disables or enables the default value display.
///
/// The default behaviour is to append [`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 Input<'a, T> {
self.show_default = val;
self
}
/// Registers a validator.
///
/// # Example
///
/// ```no_run
/// # use dialoguer::Input;
/// let mail: String = Input::new()
/// .with_prompt("Enter email")
/// .validate_with(|input: &String| -> Result<(), &str> {
/// if input.contains('@') {
/// Ok(())
/// } else {
/// Err("This is not a mail address")
/// }
/// })
/// .interact()
/// .unwrap();
/// ```
pub fn validate_with<V>(&mut self, mut validator: V) -> &mut Input<'a, T>
where
V: Validator<T> + 'a,
T: 'a,
{
let mut old_validator_func = self.validator.take();
self.validator = Some(Box::new(move |value: &T| -> Option<String> {
if let Some(old) = old_validator_func.as_mut() {
if let Some(err) = old(value) {
return Some(err);
}
}
match validator.validate(value) {
Ok(()) => None,
Err(err) => Some(err.to_string()),
}
}));
self
}
/// Enables the user to enter a printable ascii sequence and returns the result.
///
/// Its difference from [`interact`](#method.interact) is that it only allows ascii characters for string,
/// while [`interact`](#method.interact) allows virtually any character to be used e.g arrow keys.
///
/// The dialog is rendered on stderr.
pub fn interact_text(&mut self) -> io::Result<T> {
self.interact_text_on(&Term::stderr())
}
/// Like [`interact_text`](#method.interact_text) but allows a specific terminal to be set.
pub fn interact_text_on(&mut self, term: &Term) -> io::Result<T> {
let mut render = TermThemeRenderer::new(term, self.theme);
loop {
let default_string = self.default.as_ref().map(|x| x.to_string());
render.input_prompt(
&self.prompt,
if self.show_default {
default_string.as_deref()
} else {
None
},
)?;
term.flush()?;
// Read input by keystroke so that we can suppress ascii control characters
if !term.features().is_attended() {
return Ok("".to_owned().parse::<T>().unwrap());
}
let mut chars: Vec<char> = Vec::new();
let mut position = 0;
if let Some(initial) = self.initial_text.as_ref() {
term.write_str(initial)?;
chars = initial.chars().collect();
position = chars.len();
}
loop {
match term.read_key()? {
Key::Backspace if position > 0 => {
position -= 1;
chars.remove(position);
term.clear_chars(1)?;
let tail: String = chars[position..].iter().collect();
if !tail.is_empty() {
term.write_str(&tail)?;
term.move_cursor_left(tail.len())?;
}
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 => {
term.move_cursor_left(1)?;
position -= 1;
term.flush()?;
}
Key::ArrowRight if position < chars.len() => {
term.move_cursor_right(1)?;
position += 1;
term.flush()?;
}
Key::Enter => break,
Key::Unknown => {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"Not a terminal",
))
}
_ => (),
}
}
let input = chars.iter().collect::<String>();
term.clear_line()?;
render.clear()?;
if chars.is_empty() {
if let Some(ref default) = self.default {
render.input_prompt_selection(&self.prompt, &default.to_string())?;
term.flush()?;
return Ok(default.clone());
} else if !self.permit_empty {
continue;
}
}
match input.parse::<T>() {
Ok(value) => {
if let Some(ref mut validator) = self.validator {
if let Some(err) = validator(&value) {
render.error(&err)?;
continue;
}
}
render.input_prompt_selection(&self.prompt, &input)?;
term.flush()?;
return Ok(value);
}
Err(err) => {
render.error(&err.to_string())?;
continue;
}
}
}
}
/// Enables user interaction and returns the result.
///
/// Allows any characters as input, including e.g arrow keys.
/// Some of the keys might have undesired behavior.
/// For more limited version, see [`interact_text`](#method.interact_text).
///
/// If the user confirms the result is `true`, `false` otherwise.
/// The dialog is rendered on stderr.
pub fn interact(&mut self) -> io::Result<T> {
self.interact_on(&Term::stderr())
}
/// Like [`interact`](#method.interact) but allows a specific terminal to be set.
pub fn interact_on(&mut self, term: &Term) -> io::Result<T> {
let mut render = TermThemeRenderer::new(term, self.theme);
loop {
let default_string = self.default.as_ref().map(|x| x.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 {
render.input_prompt_selection(&self.prompt, &default.to_string())?;
term.flush()?;
return Ok(default.clone());
} else if !self.permit_empty {
continue;
}
}
match input.parse::<T>() {
Ok(value) => {
if let Some(ref mut validator) = self.validator {
if let Some(err) = validator(&value) {
render.error(&err)?;
continue;
}
}
render.input_prompt_selection(&self.prompt, &input)?;
term.flush()?;
return Ok(value);
}
Err(err) => {
render.error(&err.to_string())?;
continue;
}
}
}
}
}

View File

@ -1,12 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(clippy::needless_doctest_main)]
pub mod confirm;
pub mod input;
pub mod multi_select;
pub mod password;
pub mod select;
pub mod sort;

View File

@ -1,290 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{io, iter::repeat, ops::Rem};
use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
use crate::console::{Key, Term};
/// Renders a multi select prompt.
///
/// ## Example usage
/// ```rust,no_run
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// use dialoguer::MultiSelect;
///
/// let items = vec!["Option 1", "Option 2"];
/// let chosen : Vec<usize> = MultiSelect::new()
/// .items(&items)
/// .interact()?;
/// # Ok(())
/// # }
/// ```
pub struct MultiSelect<'a> {
defaults: Vec<bool>,
items: Vec<String>,
prompt: Option<String>,
clear: bool,
theme: &'a dyn Theme,
paged: bool,
}
impl<'a> Default for MultiSelect<'a> {
fn default() -> MultiSelect<'a> {
MultiSelect::new()
}
}
impl<'a> MultiSelect<'a> {
/// Creates a multi select prompt.
pub fn new() -> MultiSelect<'static> {
MultiSelect::with_theme(&SimpleTheme)
}
/// Creates a multi select prompt with a specific theme.
pub fn with_theme(theme: &'a dyn Theme) -> MultiSelect<'a> {
MultiSelect {
items: vec![],
defaults: vec![],
clear: true,
prompt: None,
theme,
paged: false,
}
}
/// Enables or disables paging
pub fn paged(&mut self, val: bool) -> &mut MultiSelect<'a> {
self.paged = val;
self
}
/// Sets the clear behavior of the menu.
///
/// The default is to clear the menu.
pub fn clear(&mut self, val: bool) -> &mut MultiSelect<'a> {
self.clear = val;
self
}
/// Sets a defaults for the menu.
pub fn defaults(&mut self, val: &[bool]) -> &mut MultiSelect<'a> {
self.defaults = val
.to_vec()
.iter()
.cloned()
.chain(repeat(false))
.take(self.items.len())
.collect();
self
}
/// Add a single item to the selector.
#[inline]
pub fn item<T: ToString>(&mut self, item: T) -> &mut MultiSelect<'a> {
self.item_checked(item, false)
}
/// Add a single item to the selector with a default checked state.
pub fn item_checked<T: ToString>(&mut self, item: T, checked: bool) -> &mut MultiSelect<'a> {
self.items.push(item.to_string());
self.defaults.push(checked);
self
}
/// Adds multiple items to the selector.
pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut MultiSelect<'a> {
for item in items {
self.items.push(item.to_string());
self.defaults.push(false);
}
self
}
/// Adds multiple items to the selector with checked state
pub fn items_checked<T: ToString>(&mut self, items: &[(T, bool)]) -> &mut MultiSelect<'a> {
for &(ref item, checked) in items {
self.items.push(item.to_string());
self.defaults.push(checked);
}
self
}
/// Prefaces the menu with a prompt.
///
/// When a prompt is set the system also prints out a confirmation after
/// the selection.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut MultiSelect<'a> {
self.prompt = Some(prompt.into());
self
}
/// Enables user interaction and returns the result.
///
/// The user can select the items with the space bar and on enter
/// the selected items will be returned.
pub fn interact(&self) -> io::Result<Vec<usize>> {
self.interact_on(&Term::stderr())
}
/// Like [interact](#method.interact) but allows a specific terminal to be set.
pub fn interact_on(&self, term: &Term) -> io::Result<Vec<usize>> {
let mut page = 0;
if self.items.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"Empty list of items given to `MultiSelect`",
));
}
let capacity = if self.paged {
term.size().0 as usize - 1
} else {
self.items.len()
};
let pages = (self.items.len() as f64 / capacity as f64).ceil() as usize;
let mut render = TermThemeRenderer::new(term, self.theme);
let mut sel = 0;
if let Some(ref prompt) = self.prompt {
render.multi_select_prompt(prompt)?;
}
let mut size_vec = Vec::new();
for items in self
.items
.iter()
.flat_map(|i| i.split('\n'))
.collect::<Vec<_>>()
{
let size = &items.len();
size_vec.push(*size);
}
let mut checked: Vec<bool> = self.defaults.clone();
loop {
for (idx, item) in self
.items
.iter()
.enumerate()
.skip(page * capacity)
.take(capacity)
{
render.multi_select_prompt_item(item, checked[idx], sel == idx)?;
}
term.hide_cursor()?;
term.flush()?;
match term.read_key()? {
Key::ArrowDown | 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::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 self.paged {
if page == 0 {
page = pages - 1;
} else {
page -= 1;
}
sel = page * capacity;
}
}
Key::ArrowRight | Key::Char('l') => {
if self.paged {
if page == pages - 1 {
page = 0;
} else {
page += 1;
}
sel = page * capacity;
}
}
Key::Char(' ') => {
checked[sel] = !checked[sel];
}
Key::Escape => {
if self.clear {
render.clear()?;
}
if let Some(ref prompt) = self.prompt {
render.multi_select_prompt_selection(prompt, &[][..])?;
}
term.show_cursor()?;
term.flush()?;
return Ok(
self
.defaults
.clone()
.into_iter()
.enumerate()
.filter_map(|(idx, checked)| if checked { Some(idx) } else { None })
.collect(),
);
}
Key::Enter => {
if self.clear {
render.clear()?;
}
if let Some(ref prompt) = self.prompt {
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(
checked
.into_iter()
.enumerate()
.filter_map(|(idx, checked)| if checked { Some(idx) } else { None })
.collect(),
);
}
_ => {}
}
if sel < page * capacity || sel >= (page + 1) * capacity {
page = sel / capacity;
}
render.clear_preserve_prompt(&size_vec)?;
}
}
}

View File

@ -1,134 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::io;
use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
use crate::console::Term;
use zeroize::Zeroizing;
/// Renders a password input prompt.
///
/// ## Example usage
///
/// ```rust,no_run
/// # fn test() -> Result<(), Box<std::error::Error>> {
/// use dialoguer::Password;
///
/// let password = Password::new().with_prompt("New Password")
/// .with_confirmation("Confirm password", "Passwords mismatching")
/// .interact()?;
/// println!("Length of the password is: {}", password.len());
/// # Ok(()) } fn main() { test().unwrap(); }
/// ```
pub struct Password<'a> {
prompt: String,
theme: &'a dyn Theme,
allow_empty_password: bool,
confirmation_prompt: Option<(String, String)>,
}
impl<'a> Default for Password<'a> {
fn default() -> Password<'a> {
Password::new()
}
}
impl<'a> Password<'a> {
/// Creates a password input prompt.
pub fn new() -> Password<'static> {
Password::with_theme(&SimpleTheme)
}
/// Creates a password input prompt with a specific theme.
pub fn with_theme(theme: &'a dyn Theme) -> Password<'a> {
Password {
prompt: "".into(),
theme,
allow_empty_password: false,
confirmation_prompt: None,
}
}
/// Sets the password input prompt.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Password<'a> {
self.prompt = prompt.into();
self
}
/// Enables confirmation prompting.
pub fn with_confirmation<A, B>(&mut self, prompt: A, mismatch_err: B) -> &mut Password<'a>
where
A: Into<String>,
B: Into<String>,
{
self.confirmation_prompt = Some((prompt.into(), mismatch_err.into()));
self
}
/// Allows/Disables empty password.
///
/// By default this setting is set to false (i.e. password is not empty).
pub fn allow_empty_password(&mut self, allow_empty_password: bool) -> &mut Password<'a> {
self.allow_empty_password = allow_empty_password;
self
}
/// Enables user interaction and returns the result.
///
/// If the user confirms the result is `true`, `false` otherwise.
/// The dialog is rendered on stderr.
pub fn interact(&self) -> io::Result<String> {
self.interact_on(&Term::stderr())
}
/// Like `interact` but allows a specific terminal to be set.
pub fn interact_on(&self, term: &Term) -> io::Result<String> {
let mut render = TermThemeRenderer::new(term, self.theme);
render.set_prompts_reset_height(false);
loop {
let password = Zeroizing::new(self.prompt_password(&mut render, &self.prompt)?);
if let Some((ref prompt, ref err)) = self.confirmation_prompt {
let pw2 = Zeroizing::new(self.prompt_password(&mut render, prompt)?);
if *password == *pw2 {
render.clear()?;
render.password_prompt_selection(&self.prompt)?;
term.flush()?;
return Ok((*password).clone());
}
render.error(err)?;
} else {
render.clear()?;
render.password_prompt_selection(&self.prompt)?;
term.flush()?;
return Ok((*password).clone());
}
}
}
fn prompt_password(
&self,
render: &mut TermThemeRenderer<'_>,
prompt: &str,
) -> io::Result<String> {
loop {
render.password_prompt(prompt)?;
render.term().flush()?;
let input = render.term().read_secure_line()?;
render.add_line();
if !input.is_empty() || self.allow_empty_password {
return Ok(input);
}
}
}
}

View File

@ -1,418 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{io, ops::Rem};
use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
use crate::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::{
/// Select,
/// theme::ColorfulTheme
/// };
/// use console::Term;
///
/// fn main() -> std::io::Result<()> {
/// let items = vec!["Item 1", "item 2"];
/// let selection = Select::with_theme(&ColorfulTheme::default())
/// .items(&items)
/// .default(0)
/// .interact_on_opt(&Term::stderr())?;
///
/// match selection {
/// Some(index) => println!("User selected item : {}", items[index]),
/// None => println!("User did not select anything")
/// }
///
/// Ok(())
/// }
/// ```
pub struct Select<'a> {
default: usize,
items: Vec<String>,
prompt: Option<String>,
clear: bool,
theme: &'a dyn Theme,
paged: bool,
}
impl<'a> Default for Select<'a> {
fn default() -> Select<'a> {
Select::new()
}
}
impl<'a> Select<'a> {
/// Creates a select prompt builder with default theme.
pub fn new() -> Select<'static> {
Select::with_theme(&SimpleTheme)
}
/// 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) -> Select<'a> {
Select {
default: !0,
items: vec![],
prompt: None,
clear: true,
theme,
paged: false,
}
}
/// Enables or disables paging
///
/// Paging is disabled by default
pub fn paged(&mut self, val: bool) -> &mut Select<'a> {
self.paged = val;
self
}
/// 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 Select<'a> {
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 Select<'a> {
self.default = val;
self
}
/// Add a single item to the selector.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::Select;
///
/// fn main() -> std::io::Result<()> {
/// let selection: usize = Select::new()
/// .item("Item 1")
/// .item("Item 2")
/// .interact()?;
///
/// Ok(())
/// }
/// ```
pub fn item<T: ToString>(&mut self, item: T) -> &mut Select<'a> {
self.items.push(item.to_string());
self
}
/// Adds multiple items to the selector.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::Select;
///
/// fn main() -> std::io::Result<()> {
/// let items = vec!["Item 1", "Item 2"];
/// let selection: usize = Select::new()
/// .items(&items)
/// .interact()?;
///
/// println!("{}", items[selection]);
///
/// Ok(())
/// }
/// ```
pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Select<'a> {
for item in items {
self.items.push(item.to_string());
}
self
}
/// Sets the select prompt.
///
/// When a prompt is set the system also prints out a confirmation after
/// the selection.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::Select;
///
/// fn main() -> std::io::Result<()> {
/// let selection = Select::new()
/// .with_prompt("Which option do you prefer?")
/// .item("Option A")
/// .item("Option B")
/// .interact()?;
///
/// Ok(())
/// }
/// ```
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Select<'a> {
self.prompt = Some(prompt.into());
self
}
/// Enables user interaction and returns the result.
///
/// Similar to [interact_on](#method.interact_on) except for the fact that it does not allow selection of the terminal.
/// The dialog is rendered on stderr.
/// Result contains index of a selected item.
pub fn interact(&self) -> io::Result<usize> {
self.interact_on(&Term::stderr())
}
/// Enables user interaction and returns the result.
///
/// This method is similar to [interact_on_opt](#method.interact_on_opt) except for the fact that it does not allow selection of the terminal.
/// The dialog is rendered on stderr.
/// Result contains `Some(index)` if user selected one of items or `None` if user cancelled with 'Esc' or 'q'.
pub fn interact_opt(&self) -> io::Result<Option<usize>> {
self.interact_on_opt(&Term::stderr())
}
/// Like [interact](#method.interact) but allows a specific terminal to be set.
///
/// ## Examples
///```rust,no_run
/// use dialoguer::Select;
/// use console::Term;
///
/// 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(())
/// }
///```
pub fn interact_on(&self, term: &Term) -> io::Result<usize> {
self
._interact_on(term, false)?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))
}
/// Like [interact_opt](#method.interact_opt) but allows a specific terminal to be set.
///
/// ## Examples
/// ```rust,no_run
/// use dialoguer::Select;
/// use console::Term;
///
/// 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")
/// }
///
/// Ok(())
/// }
/// ```
#[inline]
pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<usize>> {
self._interact_on(term, true)
}
/// Like `interact` but allows a specific terminal to be set.
fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<usize>> {
let mut page = 0;
if self.items.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"Empty list of items given to `Select`",
));
}
let capacity = if self.paged {
term.size().0 as usize - 1
} else {
self.items.len()
};
let pages = (self.items.len() as f64 / capacity as f64).ceil() as usize;
let mut render = TermThemeRenderer::new(term, self.theme);
let mut sel = self.default;
if let Some(ref prompt) = self.prompt {
render.select_prompt(prompt)?;
}
let mut size_vec = Vec::new();
for items in self
.items
.iter()
.flat_map(|i| i.split('\n'))
.collect::<Vec<_>>()
{
let size = &items.len();
size_vec.push(*size);
}
loop {
for (idx, item) in self
.items
.iter()
.enumerate()
.skip(page * capacity)
.take(capacity)
{
render.select_prompt_item(item, sel == idx)?;
}
term.hide_cursor()?;
term.flush()?;
match term.read_key()? {
Key::ArrowDown | 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 {
term.clear_last_lines(self.items.len())?;
term.show_cursor()?;
term.flush()?;
}
return Ok(None);
}
}
Key::ArrowUp | 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 self.paged {
if page == 0 {
page = pages - 1;
} else {
page -= 1;
}
sel = page * capacity;
}
}
Key::ArrowRight | Key::Char('l') => {
if self.paged {
if page == pages - 1 {
page = 0;
} else {
page += 1;
}
sel = page * capacity;
}
}
Key::Enter | Key::Char(' ') if sel != !0 => {
if self.clear {
render.clear()?;
}
if let Some(ref prompt) = self.prompt {
render.select_prompt_selection(prompt, &self.items[sel])?;
}
term.show_cursor()?;
term.flush()?;
return Ok(Some(sel));
}
_ => {}
}
if sel != !0 && (sel < page * capacity || sel >= (page + 1) * capacity) {
page = sel / capacity;
}
render.clear_preserve_prompt(&size_vec)?;
}
}
}
#[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
);
}
}

View File

@ -1,269 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{io, ops::Rem};
use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
use crate::console::{Key, Term};
/// Renders a sort prompt.
///
/// Returns list of indices in original items list sorted according to user input.
///
/// ## Example usage
/// ```rust,no_run
/// use dialoguer::Sort;
///
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// let items_to_order = vec!["Item 1", "Item 2", "Item 3"];
/// let ordered = Sort::new()
/// .with_prompt("Order the items")
/// .items(&items_to_order)
/// .interact()?;
/// # Ok(())
/// # }
/// ```
pub struct Sort<'a> {
items: Vec<String>,
prompt: Option<String>,
clear: bool,
theme: &'a dyn Theme,
paged: bool,
}
impl<'a> Default for Sort<'a> {
fn default() -> Sort<'a> {
Sort::new()
}
}
impl<'a> Sort<'a> {
/// Creates a sort prompt.
pub fn new() -> Sort<'static> {
Sort::with_theme(&SimpleTheme)
}
/// Creates a sort prompt with a specific theme.
pub fn with_theme(theme: &'a dyn Theme) -> Sort<'a> {
Sort {
items: vec![],
clear: true,
prompt: None,
theme,
paged: false,
}
}
/// Enables or disables paging
pub fn paged(&mut self, val: bool) -> &mut Sort<'a> {
self.paged = val;
self
}
/// 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 Sort<'a> {
self.clear = val;
self
}
/// Add a single item to the selector.
pub fn item<T: ToString>(&mut self, item: T) -> &mut Sort<'a> {
self.items.push(item.to_string());
self
}
/// Adds multiple items to the selector.
pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Sort<'a> {
for item in items {
self.items.push(item.to_string());
}
self
}
/// Prefaces the menu with a prompt.
///
/// When a prompt is set the system also prints out a confirmation after
/// the selection.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Sort<'a> {
self.prompt = Some(prompt.into());
self
}
/// Enables user interaction and returns the result.
///
/// The user can order the items with the space bar and the arrows.
/// On enter the ordered list will be returned.
pub fn interact(&self) -> io::Result<Vec<usize>> {
self.interact_on(&Term::stderr())
}
/// Like [interact](#method.interact) but allows a specific terminal to be set.
pub fn interact_on(&self, term: &Term) -> io::Result<Vec<usize>> {
let mut page = 0;
if self.items.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"Empty list of items given to `Sort`",
));
}
let capacity = if self.paged {
term.size().0 as usize - 1
} else {
self.items.len()
};
let pages = (self.items.len() as f64 / capacity as f64).ceil() as usize;
let mut render = TermThemeRenderer::new(term, self.theme);
let mut sel = 0;
if let Some(ref prompt) = self.prompt {
render.sort_prompt(prompt)?;
}
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;
loop {
for (idx, item) in order
.iter()
.enumerate()
.skip(page * capacity)
.take(capacity)
{
render.sort_prompt_item(&self.items[*item], checked, sel == idx)?;
}
term.hide_cursor()?;
term.flush()?;
match term.read_key()? {
Key::ArrowDown | 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::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 self.paged {
let old_sel = sel;
let old_page = page;
if page == 0 {
page = pages - 1;
} else {
page -= 1;
}
sel = page * capacity;
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 self.paged {
let old_sel = sel;
let old_page = page;
if page == pages - 1 {
page = 0;
} else {
page += 1;
}
sel = page * capacity;
if checked {
let indexes: Vec<_> = if old_page == 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;
}
// TODO: Key::Escape
Key::Enter => {
if self.clear {
render.clear()?;
}
if let Some(ref prompt) = self.prompt {
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(order);
}
_ => {}
}
if sel < page * capacity || sel >= (page + 1) * capacity {
page = sel / capacity;
}
render.clear_preserve_prompt(&size_vec)?;
}
}
}

View File

@ -1,726 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! Customizes the rendering of the elements.
use std::{fmt, io};
use crate::console::{style, Style, StyledObject, Term};
/// Implements a theme for dialoguer.
pub trait Theme {
/// Formats a prompt.
#[inline]
fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
write!(f, "{}:", prompt)
}
/// Formats out an error.
#[inline]
fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
write!(f, "error: {}", err)
}
/// Formats a confirm prompt.
fn format_confirm_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<bool>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(f, "{} ", &prompt)?;
}
match default {
None => write!(f, "[y/n] ")?,
Some(true) => write!(f, "[Y/n] ")?,
Some(false) => write!(f, "[y/N] ")?,
}
Ok(())
}
/// Formats a confirm prompt after selection.
fn format_confirm_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selection: Option<bool>,
) -> fmt::Result {
let selection = selection.map(|b| if b { "yes" } else { "no" });
match selection {
Some(selection) if prompt.is_empty() => {
write!(f, "{}", selection)
}
Some(selection) => {
write!(f, "{} {}", &prompt, selection)
}
None if prompt.is_empty() => Ok(()),
None => {
write!(f, "{}", &prompt)
}
}
}
/// Formats an input prompt.
fn format_input_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<&str>,
) -> fmt::Result {
match default {
Some(default) if prompt.is_empty() => write!(f, "[{}]: ", default),
Some(default) => write!(f, "{} [{}]: ", prompt, default),
None => write!(f, "{}: ", prompt),
}
}
/// Formats an input prompt after selection.
#[inline]
fn format_input_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
sel: &str,
) -> fmt::Result {
write!(f, "{}: {}", prompt, sel)
}
/// Formats a password prompt.
#[inline]
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]
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
)
}
}
/// The default theme.
pub struct SimpleTheme;
impl Theme for SimpleTheme {}
/// A colorful theme
pub struct ColorfulTheme {
/// The style for default values
pub defaults_style: Style,
/// The style for prompt
pub prompt_style: Style,
/// Prompt prefix value and style
pub prompt_prefix: StyledObject<String>,
/// Prompt suffix value and style
pub prompt_suffix: StyledObject<String>,
/// Prompt on success prefix value and style
pub success_prefix: StyledObject<String>,
/// Prompt on success suffix value and style
pub success_suffix: StyledObject<String>,
/// Error prefix value and style
pub error_prefix: StyledObject<String>,
/// The style for error message
pub error_style: Style,
/// The style for hints
pub hint_style: Style,
/// The style for values on prompt success
pub values_style: Style,
/// The style for active items
pub active_item_style: Style,
/// The style for inactive items
pub inactive_item_style: Style,
/// Active item in select prefix value and style
pub active_item_prefix: StyledObject<String>,
/// Inctive item in select prefix value and style
pub inactive_item_prefix: StyledObject<String>,
/// Checked item in multi select prefix value and style
pub checked_item_prefix: StyledObject<String>,
/// Unchecked item in multi select prefix value and style
pub unchecked_item_prefix: StyledObject<String>,
/// Picked item in sort prefix value and style
pub picked_item_prefix: StyledObject<String>,
/// Unpicked item in sort prefix value and style
pub unpicked_item_prefix: StyledObject<String>,
/// 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(),
inline_selections: true,
}
}
}
impl Theme for ColorfulTheme {
/// Formats a prompt.
fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
write!(f, "{}", &self.prompt_suffix)
}
/// Formats an error
fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
write!(
f,
"{} {}",
&self.error_prefix,
self.error_style.apply_to(err)
)
}
/// Formats an input prompt.
fn format_input_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<&str>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
match default {
Some(default) => write!(
f,
"{} {} ",
self.hint_style.apply_to(&format!("({})", default)),
&self.prompt_suffix
),
None => write!(f, "{} ", &self.prompt_suffix),
}
}
/// Formats a confirm prompt.
fn format_confirm_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<bool>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
match default {
None => write!(
f,
"{} {}",
self.hint_style.apply_to("(y/n)"),
&self.prompt_suffix
),
Some(true) => write!(
f,
"{} {} {}",
self.hint_style.apply_to("(y/n)"),
&self.prompt_suffix,
self.defaults_style.apply_to("yes")
),
Some(false) => write!(
f,
"{} {} {}",
self.hint_style.apply_to("(y/n)"),
&self.prompt_suffix,
self.defaults_style.apply_to("no")
),
}
}
/// Formats a confirm prompt after selection.
fn format_confirm_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selection: Option<bool>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.success_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
let selection = selection.map(|b| if b { "yes" } else { "no" });
match selection {
Some(selection) => {
write!(
f,
"{} {}",
&self.success_suffix,
self.values_style.apply_to(selection)
)
}
None => {
write!(f, "{}", &self.success_suffix)
}
}
}
/// Formats an input prompt after selection.
fn format_input_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
sel: &str,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.success_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
write!(
f,
"{} {}",
&self.success_suffix,
self.values_style.apply_to(sel)
)
}
/// Formats a password prompt after selection.
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 = match active {
true => (
&self.active_item_prefix,
self.active_item_style.apply_to(text),
),
false => (
&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)
}
}
/// Helper struct to conveniently render a theme ot 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,
}
}
pub fn set_prompts_reset_height(&mut self, val: bool) {
self.prompts_reset_height = val;
}
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)
}
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(())
}
pub fn error(&mut self, err: &str) -> io::Result<()> {
self.write_formatted_line(|this, buf| this.theme.format_error(buf, err))
}
pub fn confirm_prompt(&mut self, prompt: &str, default: Option<bool>) -> io::Result<()> {
self.write_formatted_str(|this, buf| this.theme.format_confirm_prompt(buf, prompt, default))
}
pub fn confirm_prompt_selection(&mut self, prompt: &str, sel: Option<bool>) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_confirm_prompt_selection(buf, prompt, sel)
})
}
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)
})
}
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)
})
}
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) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| this.theme.format_select_prompt(buf, prompt))
}
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))
}
pub fn multi_select_prompt(&mut self, prompt: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| this.theme.format_multi_select_prompt(buf, prompt))
}
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) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| this.theme.format_sort_prompt(buf, prompt))
}
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;
Ok(())
}
pub fn clear_preserve_prompt(&mut self, size_vec: &[usize]) -> io::Result<()> {
let mut new_height = self.height;
//Check each item size, increment on finding an overflow
for size in size_vec {
if *size > self.term.size().1 as usize {
new_height += 1;
}
}
self.term.clear_last_lines(new_height)?;
self.height = 0;
Ok(())
}
}

View File

@ -1,28 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! Provides validation for text inputs
use std::fmt::{Debug, Display};
/// Trait for input validators.
///
/// A generic implementation for `Fn(&str) -> Result<(), E>` is provided
/// to facilitate development.
pub trait Validator<T> {
type Err: Debug + Display;
/// Invoked with the value to validate.
///
/// If this produces `Ok(())` then the value is used and parsed, if
/// an error is returned validation fails with that error.
fn validate(&mut self, input: &T) -> Result<(), Self::Err>;
}
impl<T, F: FnMut(&T) -> Result<(), E>, E: Debug + Display> Validator<T> for F {
type Err = E;
fn validate(&mut self, input: &T) -> Result<(), Self::Err> {
self(input)
}
}

View File

@ -16,13 +16,6 @@ mod interface;
mod plugin; mod plugin;
mod sign; mod sign;
// temporary fork from https://github.com/mitsuhiko/console until 0.14.1+ release
#[allow(dead_code)]
mod console;
// temporary fork from https://github.com/mitsuhiko/dialoguer until 0.8.0+ release
#[allow(dead_code)]
mod dialoguer;
use helpers::framework::{infer_from_package_json as infer_framework, Framework}; use helpers::framework::{infer_from_package_json as infer_framework, Framework};
use std::{env::current_dir, fs::read_to_string, path::PathBuf}; use std::{env::current_dir, fs::read_to_string, path::PathBuf};