Skip to content

Commit

Permalink
added confirm
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Dec 21, 2023
1 parent 95447ac commit 5bd0719
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 0 deletions.
9 changes: 9 additions & 0 deletions examples/confirm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use demand::Confirm;

fn main() {
let ms = Confirm::new("Are you sure?")
.affirmative("Yes!")
.negative("No.");
let yes = ms.run().expect("error running confirm");
println!("yes: {}", yes);
}
203 changes: 203 additions & 0 deletions src/confirm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use crate::theme::Theme;

use console::{Key, Term};


use std::io;
use std::io::Write;
use termcolor::{Buffer, WriteColor};

/// Select multiple options from a list
///
/// # Example
/// ```rust
/// use demand::Confirm;
///
/// let ms = Confirm::new("Are you sure?")
/// .affirmative("Yes!")
/// .negative("No.");
/// let yes = ms.run().expect("error running confirm");
/// println!("yes: {}", yes);
/// ```
pub struct Confirm {
/// The title of the selector
pub title: String,
/// The colors/style of the selector
pub theme: Theme,
/// A description to display above the selector
pub description: String,
/// The text to display for the affirmative option
pub affirmative: String,
/// The text to display for the negative option
pub negative: String,
/// If true, the affirmative option is selected by default
pub selected: bool,
term: Term,
height: usize,
}

impl Confirm {
/// Create a new multi select with the given title
pub fn new<S: Into<String>>(title: S) -> Self {
Self {
title: title.into(),
description: String::new(),
theme: Theme::default(),
term: Term::stderr(),
affirmative: "Yes".to_string(),
negative: "No".to_string(),
selected: true,
height: 0,
}
}

/// Set the description of the selector
pub fn description(mut self, description: &str) -> Self {
self.description = description.to_string();
self
}

/// Set the label of the affirmative option
pub fn affirmative<S: Into<String>>(mut self, affirmative: S) -> Self {
self.affirmative = affirmative.into();
self
}

/// Set the label of the negative option
pub fn negative<S: Into<String>>(mut self, negative: S) -> Self {
self.negative = negative.into();
self
}

/// Set whether the affirmative option is selected by default
pub fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}

/// Set the theme of the dialog
pub fn theme(mut self, theme: Theme) -> Self {
self.theme = theme;
self
}

/// Displays the dialog to the user and returns their response
pub fn run(mut self) -> io::Result<bool> {
let affirmative_char = self.affirmative.to_lowercase().chars().next().unwrap();
let negative_char = self.negative.to_lowercase().chars().next().unwrap();
loop {
self.clear()?;
let output = self.render()?;
self.height = output.lines().count() - 1;
self.term.write_all(output.as_bytes())?;
self.term.flush()?;
match self.term.read_key()? {
Key::ArrowLeft | Key::Char('h') => self.handle_left(),
Key::ArrowRight | Key::Char('l') => self.handle_right(),
Key::Char(c) if c == affirmative_char => {
self.selected = true;
return self.handle_submit();
}
Key::Char(c) if c == negative_char => {
self.selected = false;
return self.handle_submit();
}
Key::Enter => {
return self.handle_submit();
}
_ => {}
}
}
}

fn handle_submit(mut self) -> io::Result<bool> {
self.clear()?;
let output = self.render_success()?;
self.term.write_all(output.as_bytes())?;
Ok(self.selected)
}

fn handle_left(&mut self) {
if !self.selected {
self.selected = true;
}
}

fn handle_right(&mut self) {
if self.selected {
self.selected = false;
}
}

fn render(&self) -> io::Result<String> {
let mut out = Buffer::ansi();

out.set_color(&self.theme.title)?;
write!(out, " {}", self.title)?;

if !self.description.is_empty() {
out.set_color(&self.theme.description)?;
write!(out, " {}", self.description)?;
writeln!(out)?;
}
writeln!(out, "\n")?;

write!(out, " ")?;
if self.selected {
out.set_color(&self.theme.focused_button)?;
} else {
out.set_color(&self.theme.blurred_button)?;
}
write!(out, " {} ", self.affirmative)?;
out.reset()?;
write!(out, " ")?;
if self.selected {
out.set_color(&self.theme.blurred_button)?;
} else {
out.set_color(&self.theme.focused_button)?;
}
write!(out, " {} ", self.negative)?;
out.reset()?;
writeln!(out, "\n")?;

let mut help_keys = vec![("←/→", "toggle")];
let affirmative_char = self.affirmative.to_lowercase().chars().next().unwrap();
let negative_char = self.negative.to_lowercase().chars().next().unwrap();
let submit_keys = format!("{affirmative_char}/{negative_char}/enter");
help_keys.push((&submit_keys, "submit"));
for (i, (key, desc)) in help_keys.iter().enumerate() {
if i > 0 {
out.set_color(&self.theme.help_sep)?;
write!(out, " • ")?;
}
out.set_color(&self.theme.help_key)?;
write!(out, "{}", key)?;
out.set_color(&self.theme.help_desc)?;
write!(out, " {}", desc)?;
}

out.reset()?;
Ok(std::str::from_utf8(out.as_slice()).unwrap().to_string())
}

fn render_success(&self) -> io::Result<String> {
let mut out = Buffer::ansi();
out.set_color(&self.theme.title)?;
write!(out, " {}", self.title)?;
out.set_color(&self.theme.selected_option)?;
if self.selected {
writeln!(out, " {}", self.affirmative)?;
} else {
writeln!(out, " {}", self.negative)?;
}
out.reset()?;
Ok(std::str::from_utf8(out.as_slice()).unwrap().to_string())
}

fn clear(&mut self) -> io::Result<()> {
self.term.clear_to_end_of_screen()?;
self.term.clear_last_lines(self.height)?;
self.height = 0;
Ok(())
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! A prompt library for Rust. Based on [huh? for Go](https://github.com/charmbracelet/huh).
pub use confirm::Confirm;
pub use multiselect::MultiSelect;
pub use option::DemandOption;
pub use theme::Theme;

mod confirm;
mod multiselect;
mod option;
mod theme;
15 changes: 15 additions & 0 deletions src/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub struct Theme {
pub help_key: ColorSpec,
pub help_desc: ColorSpec,
pub help_sep: ColorSpec,

pub focused_button: ColorSpec,
pub blurred_button: ColorSpec,
}

impl Theme {
Expand All @@ -39,6 +42,8 @@ impl Theme {
help_key: ColorSpec::new(),
help_desc: ColorSpec::new(),
help_sep: ColorSpec::new(),
focused_button: ColorSpec::new(),
blurred_button: ColorSpec::new(),
}
}

Expand All @@ -49,10 +54,17 @@ impl Theme {
let red = Color::Rgb(255, 70, 114);
let fuchsia = Color::Rgb(247, 128, 226);
let green = Color::Rgb(2, 191, 135);
let cream = Color::Rgb(255, 253, 245);

let mut title = make_color(indigo);
title.set_bold(true);

let mut focused_button = make_color(cream);
focused_button.set_bg(Some(fuchsia));

let mut blurred_button = make_color(normal);
blurred_button.set_bg(Some(Color::Ansi256(238)));

Self {
title,
error_indicator: make_color(red),
Expand All @@ -71,6 +83,9 @@ impl Theme {
help_key: make_color(Color::Rgb(98, 98, 98)),
help_desc: make_color(Color::Rgb(74, 74, 74)),
help_sep: make_color(Color::Rgb(60, 60, 60)),

focused_button,
blurred_button,
}
}
}
Expand Down

0 comments on commit 5bd0719

Please sign in to comment.