From 1e7b30e8d6608904548d511219fb9b58249230e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20L=C3=BCdecke?= Date: Thu, 5 Oct 2023 12:36:34 +0200 Subject: [PATCH] Adding alert as new message type --- examples/alert.rs | 16 +++++ src/lib.rs | 3 +- src/prompts/alert.rs | 148 +++++++++++++++++++++++++++++++++++++++++++ src/prompts/mod.rs | 1 + src/theme/mod.rs | 19 ++++++ src/theme/render.rs | 6 ++ 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 examples/alert.rs create mode 100644 src/prompts/alert.rs diff --git a/examples/alert.rs b/examples/alert.rs new file mode 100644 index 00000000..b8a91880 --- /dev/null +++ b/examples/alert.rs @@ -0,0 +1,16 @@ +use dialoguer::{theme::ColorfulTheme, Alert}; + +fn main() { + let _ = Alert::with_theme(&ColorfulTheme::default()) + .with_prompt("Something went wrong! Press enter to continue.") + .interact(); + + let _ = Alert::with_theme(&ColorfulTheme::default()) + .with_alert_text("This is an alert, press enter to continue.") + .interact(); + + let _ = Alert::with_theme(&ColorfulTheme::default()) + .with_alert_text("Strange things happened: .") + .with_prompt("Press enter to continue.") + .interact(); +} diff --git a/src/lib.rs b/src/lib.rs index 46bf30cb..a76967d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,8 @@ pub use prompts::fuzzy_select::FuzzySelect; #[cfg(feature = "password")] pub use prompts::password::Password; pub use prompts::{ - confirm::Confirm, input::Input, multi_select::MultiSelect, select::Select, sort::Sort, + alert::Alert, confirm::Confirm, input::Input, multi_select::MultiSelect, select::Select, + sort::Sort, }; #[cfg(feature = "completion")] diff --git a/src/prompts/alert.rs b/src/prompts/alert.rs new file mode 100644 index 00000000..ed45b98c --- /dev/null +++ b/src/prompts/alert.rs @@ -0,0 +1,148 @@ +use std::io; + +use console::{Key, Term}; + +use crate::{ + theme::{render::TermThemeRenderer, SimpleTheme, Theme}, + Result, +}; + +/// Renders an alert prompt. +/// +/// ## Example +/// +/// ```rust,no_run +/// use dialoguer::{theme::ColorfulTheme, Alert}; +/// +/// fn main() { +/// let _ = Alert::with_theme(&ColorfulTheme::default()) +/// .with_prompt("Something went wrong! Press enter to continue.") +/// .interact(); +/// +/// let _ = Alert::with_theme(&ColorfulTheme::default()) +/// .with_alert_text("This is an alert, press enter to continue.") +/// .interact(); +/// +/// let _ = Alert::with_theme(&ColorfulTheme::default()) +/// .with_alert_text("Strange things happened: .") +/// .with_prompt("Press enter to continue.") +/// .interact(); +/// } +/// ``` +#[derive(Clone)] +pub struct Alert<'a> { + alert_text: String, + prompt: String, + theme: &'a dyn Theme, +} + +impl Default for Alert<'static> { + fn default() -> Self { + Self::new() + } +} + +impl Alert<'static> { + /// Creates a alert prompt with default theme. + pub fn new() -> Self { + Self::with_theme(&SimpleTheme) + } +} + +impl Alert<'_> { + /// Sets the alert content message. + pub fn with_alert_text>(mut self, alert_text: S) -> Self { + self.alert_text = alert_text.into(); + self + } + + /// Sets the alert prompt. + pub fn with_prompt>(mut self, prompt: S) -> Self { + self.prompt = prompt.into(); + self + } + + /// Enables user interaction. + /// + /// The dialog is rendered on stderr. + #[inline] + pub fn interact(self) -> Result> { + self.interact_on(&Term::stderr()) + } + + /// Like [`interact`](Self::interact) but allows a specific terminal to be set. + #[inline] + pub fn interact_on(self, term: &Term) -> Result> { + Ok(Some(self._interact_on(term)?.ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case") + })?)) + } + + fn _interact_on(self, term: &Term) -> Result> { + if !term.is_term() { + return Err(io::Error::new(io::ErrorKind::NotConnected, "not a terminal").into()); + } + + let mut render = TermThemeRenderer::new(term, self.theme); + + render.alert_prompt(&self.alert_text, &self.prompt)?; + + term.hide_cursor()?; + term.flush()?; + + // Default behavior: wait for user to hit the Enter key. + loop { + let input = term.read_key()?; + match input { + Key::Enter => (), + _ => { + continue; + } + }; + + break; + } + + term.write_line("")?; + term.show_cursor()?; + term.flush()?; + + Ok(Some(())) + } +} + +impl<'a> Alert<'a> { + /// Creates an alert prompt with a specific theme. + /// + /// ## Example + /// + /// ```rust,no_run + /// use dialoguer::{theme::ColorfulTheme, Alert}; + /// + /// fn main() { + /// let alert = Alert::with_theme(&ColorfulTheme::default()) + /// .interact(); + /// } + /// ``` + pub fn with_theme(theme: &'a dyn Theme) -> Self { + Self { + alert_text: "".into(), + prompt: "".into(), + theme, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_clone() { + let alert = Alert::new() + .with_alert_text("FYI: ground gets wet if it rains.") + .with_prompt("Press enter continue"); + + let _ = alert.clone(); + } +} diff --git a/src/prompts/mod.rs b/src/prompts/mod.rs index 1c131855..2842ce56 100644 --- a/src/prompts/mod.rs +++ b/src/prompts/mod.rs @@ -1,5 +1,6 @@ #![allow(clippy::needless_doctest_main)] +pub mod alert; pub mod confirm; pub mod input; pub mod multi_select; diff --git a/src/theme/mod.rs b/src/theme/mod.rs index d22001cf..c1f462f6 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -27,6 +27,25 @@ pub trait Theme { write!(f, "error: {}", err) } + /// Formats an alert prompt. + fn format_alert_prompt( + &self, + f: &mut dyn fmt::Write, + alert_text: &str, + prompt: &str, + ) -> fmt::Result { + if !alert_text.is_empty() { + write!(f, "⚠ {}", &alert_text)?; + } + if !prompt.is_empty() { + if !alert_text.is_empty() { + writeln!(f, "")?; + } + write!(f, "{}", &prompt)?; + } + Ok(()) + } + /// Formats a confirm prompt. fn format_confirm_prompt( &self, diff --git a/src/theme/render.rs b/src/theme/render.rs index e6f3addf..06d076a5 100644 --- a/src/theme/render.rs +++ b/src/theme/render.rs @@ -87,6 +87,12 @@ impl<'a> TermThemeRenderer<'a> { self.write_formatted_line(|this, buf| this.theme.format_error(buf, err)) } + pub fn alert_prompt(&mut self, alert_text: &str, prompt: &str) -> Result { + self.write_formatted_str(|this, buf| { + this.theme.format_alert_prompt(buf, alert_text, prompt) + }) + } + pub fn confirm_prompt(&mut self, prompt: &str, default: Option) -> Result { self.write_formatted_str(|this, buf| this.theme.format_confirm_prompt(buf, prompt, default)) }