use std::io::{self, Write};
use std::sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
};
use std::thread;
use std::time::Duration;
use anyhow::Result;
use colored::{ColoredString, Colorize};
use inquire::validator::Validation;
const SPINNER_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
pub struct Spinner {
running: Arc<AtomicBool>,
thread: Mutex<Option<thread::JoinHandle<()>>>,
}
pub fn spinner() -> Spinner {
Spinner {
running: Arc::new(AtomicBool::new(false)),
thread: Mutex::new(None),
}
}
impl Spinner {
pub fn start(&self, msg: &str) {
let running = Arc::clone(&self.running);
running.store(true, Ordering::SeqCst);
let msg = msg.to_string();
let handle = thread::spawn(move || {
let mut i = 0usize;
while running.load(Ordering::SeqCst) {
print!(
"\r{} {}",
SPINNER_FRAMES[i % SPINNER_FRAMES.len()].cyan(),
msg
);
let _ = io::stdout().flush();
i += 1;
thread::sleep(Duration::from_millis(80));
}
});
*self.thread.lock().unwrap() = Some(handle);
}
fn finish(&self, symbol: ColoredString, msg: &str) {
self.running.store(false, Ordering::SeqCst);
if let Some(handle) = self.thread.lock().unwrap().take() {
let _ = handle.join();
}
println!("\r{} {}\x1b[K", symbol, msg);
}
pub fn stop(&self, msg: &str) {
self.finish("✓".green(), msg);
}
pub fn error(&self, msg: &str) {
self.finish("✗".red(), msg);
}
}
fn colorize_backticks(message: &str) -> String {
let mut result = String::new();
let mut rest = message;
while let Some(start) = rest.find('`') {
result.push_str(&rest[..start]);
let after = &rest[start + 1..];
if let Some(end) = after.find('`') {
result.push_str(&format!("{}", after[..end].yellow()));
rest = &after[end + 1..];
} else {
result.push_str(rest);
return result;
}
}
result.push_str(rest);
result
}
pub fn success(message: &str) {
let mut lines = message.lines();
if let Some(first) = lines.next() {
println!("{} {}", "✓".green(), colorize_backticks(first));
for line in lines {
println!(" {} {}", "›".blue(), colorize_backticks(line));
}
}
}
pub fn warn(message: &str) {
let mut lines = message.lines();
if let Some(first) = lines.next() {
println!("{} {}", "!".yellow(), colorize_backticks(first));
for line in lines {
println!(" {} {}", "›".blue(), colorize_backticks(line));
}
}
}
pub fn error(message: &str) {
let mut lines = message.lines();
if let Some(first) = lines.next() {
eprintln!("{} {}", "✗".red(), colorize_backticks(first));
for line in lines {
eprintln!(" {} {}", "›".blue(), colorize_backticks(line));
}
}
}
pub fn confirm(prompt: &str) -> Result<bool> {
let answer = inquire::Confirm::new(prompt).with_default(false).prompt()?;
Ok(answer)
}
pub fn input<F>(prompt: &str, validator: F) -> Result<String>
where
F: Fn(&str) -> std::result::Result<(), &'static str> + Clone + 'static,
{
let answer = inquire::Text::new(prompt)
.with_validator(move |input: &str| match validator(input) {
Ok(()) => Ok(Validation::Valid),
Err(msg) => Ok(Validation::Invalid(msg.into())),
})
.prompt()?;
Ok(answer)
}
pub fn input_with_placeholder<F>(prompt: &str, placeholder: &str, validator: F) -> Result<String>
where
F: Fn(&str) -> std::result::Result<(), &'static str> + Clone + 'static,
{
let answer = inquire::Text::new(prompt)
.with_default(placeholder)
.with_validator(move |input: &str| match validator(input) {
Ok(()) => Ok(Validation::Valid),
Err(msg) => Ok(Validation::Invalid(msg.into())),
})
.prompt()?;
Ok(answer)
}
pub fn select(prompt: &str, items: Vec<String>) -> Result<String> {
let answer = inquire::Select::new(prompt, items).prompt()?;
Ok(answer)
}
pub fn select_or_input<F>(prompt: &str, suggestions: Vec<String>, validator: F) -> Result<String>
where
F: Fn(&str) -> std::result::Result<(), &'static str> + Clone + 'static,
{
let answer = inquire::Text::new(prompt)
.with_autocomplete(SuggestionsHelper(suggestions))
.with_validator(move |input: &str| match validator(input) {
Ok(()) => Ok(Validation::Valid),
Err(msg) => Ok(Validation::Invalid(msg.into())),
})
.prompt()?;
Ok(answer)
}
#[derive(Clone)]
struct SuggestionsHelper(Vec<String>);
impl inquire::autocompletion::Autocomplete for SuggestionsHelper {
fn get_suggestions(
&mut self,
input: &str,
) -> std::result::Result<Vec<String>, inquire::CustomUserError> {
let matches = self
.0
.iter()
.filter(|s| s.contains(input))
.cloned()
.collect();
Ok(matches)
}
fn get_completion(
&mut self,
_input: &str,
highlighted_suggestion: Option<String>,
) -> std::result::Result<inquire::autocompletion::Replacement, inquire::CustomUserError> {
Ok(highlighted_suggestion)
}
}